南京理工-分级核对需求:

1.工会组织管理页面仅给‘机关党委工会’的成员单位列表加三级单位管理员设置按钮;
2.新增‘分级核对管理’(校工会管理员可见)和‘单位数据核对’(三级单位管理员和分工会管理员可见)菜单页面;
This commit is contained in:
2026-10-14 16:26:16 +08:00
parent f9a87d12e2
commit eaac21eebc
17 changed files with 2853 additions and 4 deletions
@@ -16,6 +16,11 @@ public interface Roles {
*/
String UNION_MANGER = "5d342e614e9a48288d50154bcdcc75d3";
/**
* 三级单位管理员
*/
String THIRD_UNIT_MANGER = "e8d957d1ab9f4e56a60b5a53929d18bb";
/**
* 体育活动联合工会管理员
*/
@@ -0,0 +1,12 @@
package io.v.nutz.levelcheck.constant;
/**
* 分级核对操作类型
*/
public interface LevelCheckAction {
String DISTRIBUTE = "DISTRIBUTE";
String CONFIRM = "CONFIRM";
String SUBMIT_REVISION = "SUBMIT_REVISION";
String APPROVE = "APPROVE";
String REJECT = "REJECT";
}
@@ -0,0 +1,12 @@
package io.v.nutz.levelcheck.constant;
/**
* 分级核对状态
*/
public interface LevelCheckState {
int PENDING_CHECK = 0;
int CONFIRMED = 1;
int PENDING_AUDIT = 2;
int REJECTED = 3;
int APPROVED = 4;
}
@@ -0,0 +1,184 @@
package io.v.nutz.levelcheck.controller;
import cn.wizzer.framework.base.Result;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.levelcheck.service.LevelCheckService;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.mvc.annotation.*;
import org.nutz.mvc.upload.TempFile;
import org.nutz.mvc.upload.UploadAdaptor;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/**
* 校工会分级核对管理
*/
@IocBean
@At("/platform/levelCheck/school")
@Ok("json:full")
@RequiresAuthentication
public class LevelCheckSchoolController {
@Inject
private LevelCheckService levelCheckService;
@At("")
@Ok("beetl:/platform/levelCheck/school.html")
public void index() {
levelCheckService.requireSchoolAdmin();
}
@At
@ViReturn
public Object taskData(String taskName, String taskDate, int pageNumber, int pageSize) {
try {
levelCheckService.requireSchoolAdmin();
return Result.success().addData(levelCheckService.schoolTasks(
taskName, taskDate, pageNumber, pageSize));
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
@ViReturn
public Object unitTaskData(String taskId, String unitName, Integer state, Integer managerState,
int pageNumber, int pageSize) {
try {
levelCheckService.requireSchoolAdmin();
return Result.success().addData(levelCheckService.schoolUnitTasks(
taskId, unitName, state, managerState, pageNumber, pageSize));
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
@ViReturn
public Object unitDataPage(String unitTaskId, String versionType, String searchName, String searchKeyword,
Integer state,
int pageNumber, int pageSize) {
try {
return Result.success().addData(levelCheckService.unitDataPage(
unitTaskId, versionType, searchName, searchKeyword, state, pageNumber, pageSize));
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
@ViReturn
public Object currentDataPage(String taskId, String searchName, String searchKeyword, Integer state,
int pageNumber, int pageSize) {
try {
return Result.success().addData(levelCheckService.currentDataPage(
taskId, searchName, searchKeyword, state, pageNumber, pageSize));
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
@POST
@ViReturn
@Aop(TransAop.READ_COMMITTED)
public Object doDelete(String taskId) {
try {
levelCheckService.deleteTask(taskId);
return null;
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
@POST
@ViReturn
@Aop(TransAop.READ_COMMITTED)
@AdaptBy(type = UploadAdaptor.class, args = {"ioc:fileUpload"})
public Object doUpload(String taskName, String description,
@Param("files") TempFile[] files) {
try {
levelCheckService.requireSchoolAdmin();
NutMap result = levelCheckService.createTask(taskName, description, files);
String message = result.getInt("errorCount") > 0 ? "导入完成,部分数据未分发" : "分发成功";
return Result.success(message).addData(result);
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
public void exportImportErrors(String cacheKey, HttpServletResponse response) {
try {
levelCheckService.requireSchoolAdmin();
byte[] bytes = levelCheckService.getImportErrorFile(cacheKey);
String fileName = "分级核对导入错误.xlsx";
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment;filename="
+ URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
response.getOutputStream().write(bytes);
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
}
@At
public void downloadTemplate(HttpServletResponse response) {
try {
byte[] bytes = levelCheckService.getImportTemplate();
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment;filename="
+ URLEncoder.encode("分级核对导入模板.xlsx", StandardCharsets.UTF_8.name()));
response.getOutputStream().write(bytes);
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
}
@At
@ViReturn
@Aop(TransAop.READ_COMMITTED)
public Object doAudit(String unitTaskId, boolean approved, String opinion) {
try {
levelCheckService.audit(unitTaskId, approved, opinion);
return null;
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
public void download(String unitTaskId, String versionType, HttpServletResponse response) {
try {
byte[] bytes = levelCheckService.getDownloadBytes(unitTaskId, versionType, true);
String fileName = levelCheckService.getDownloadFileName(unitTaskId, versionType, true);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment;filename="
+ URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
response.getOutputStream().write(bytes);
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
}
@At
public void exportCurrent(String taskId, HttpServletResponse response) {
try {
byte[] bytes = levelCheckService.exportCurrent(taskId);
String fileName = "分级核对当前生效数据.xlsx";
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment;filename="
+ URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
response.getOutputStream().write(bytes);
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
}
}
@@ -0,0 +1,97 @@
package io.v.nutz.levelcheck.controller;
import cn.wizzer.framework.base.Result;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.levelcheck.service.LevelCheckService;
import org.nutz.aop.interceptor.ioc.TransAop;
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.*;
import org.nutz.mvc.upload.TempFile;
import org.nutz.mvc.upload.UploadAdaptor;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/**
* 单位分级核对
*/
@IocBean
@At("/platform/levelCheck/unit")
@Ok("json:full")
@RequiresAuthentication
public class LevelCheckUnitController {
@Inject
private LevelCheckService levelCheckService;
@At("")
@Ok("beetl:/platform/levelCheck/unit.html")
public void index() {
}
@At
@ViReturn
public Object taskData(String keyword, Integer state, int pageNumber, int pageSize) {
try {
return Result.success().addData(levelCheckService.myUnitTasks(
keyword, state, pageNumber, pageSize));
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
@ViReturn
@Aop(TransAop.READ_COMMITTED)
public Object doConfirm(String unitTaskId) {
try {
levelCheckService.confirm(unitTaskId);
return null;
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
@ViReturn
public Object dataPage(String unitTaskId, String searchName, String searchKeyword, Integer state,
int pageNumber, int pageSize) {
try {
return Result.success().addData(levelCheckService.myUnitDataPage(
unitTaskId, searchName, searchKeyword, state, pageNumber, pageSize));
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
@POST
@ViReturn
@Aop(TransAop.READ_COMMITTED)
@AdaptBy(type = UploadAdaptor.class, args = {"ioc:fileUpload"})
public Object doUploadRevision(String unitTaskId, @Param("file") TempFile file) {
try {
levelCheckService.submitRevision(unitTaskId, file);
return null;
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
public void download(String unitTaskId, HttpServletResponse response) {
try {
byte[] bytes = levelCheckService.getDownloadBytes(unitTaskId, "current", false);
String fileName = levelCheckService.getDownloadFileName(unitTaskId, "current", false);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment;filename="
+ URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
response.getOutputStream().write(bytes);
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
}
}
@@ -0,0 +1,49 @@
package io.v.nutz.levelcheck.model;
import io.v.nutz.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@Table("level_check_action_log")
@TableIndexes({@Index(name = "IDX_LEVEL_CHECK_LOG_UNIT", fields = {"unitTaskId"}, unique = false)})
public class LevelCheckActionLog extends BaseModel {
@Name
@PrevInsert(uu32 = true)
@ColDefine(type = ColType.VARCHAR, width = 32)
private String id;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String unitTaskId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 30)
private String action;
@Column
private Integer fromState;
@Column
private Integer toState;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String operatorId;
@Column
private Long operateAt;
@Column
@ColDefine(type = ColType.VARCHAR, width = 500)
private String opinion;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String fileVersionId;
}
@@ -0,0 +1,54 @@
package io.v.nutz.levelcheck.model;
import io.v.nutz.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
/**
* 分级核对固定模板数据明细
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@Table("level_check_data_row")
@TableIndexes({@Index(name = "IDX_LEVEL_CHECK_DATA_VERSION", fields = {"versionId", "sequenceNo"}, unique = true)})
public class LevelCheckDataRow extends BaseModel {
@Name
@PrevInsert(uu32 = true)
@ColDefine(type = ColType.VARCHAR, width = 32)
private String id;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String versionId;
@Column
private Integer sequenceNo;
@Column
@ColDefine(customType = "text")
private String salaryUnitCode;
@Column
@ColDefine(customType = "text")
private String employeeNo;
@Column
@ColDefine(customType = "text")
private String employeeName;
@Column
@ColDefine(customType = "text")
private String unionFee;
@Column
@ColDefine(customType = "text")
private String yearMonth;
@Column
@ColDefine(customType = "text")
private String unitName;
}
@@ -0,0 +1,53 @@
package io.v.nutz.levelcheck.model;
import io.v.nutz.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@Table("level_check_file_version")
@TableIndexes({@Index(name = "IDX_LEVEL_CHECK_FILE_UNIT", fields = {"unitTaskId", "versionNo"}, unique = true)})
public class LevelCheckFileVersion extends BaseModel {
@Name
@PrevInsert(uu32 = true)
@ColDefine(type = ColType.VARCHAR, width = 32)
private String id;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String unitTaskId;
@Column
private Integer versionNo;
@Column
@ColDefine(type = ColType.VARCHAR, width = 20)
private String versionType;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String sysFileId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 200)
private String originalFileName;
@Column
private Integer rowCount;
@Column
@ColDefine(type = ColType.VARCHAR, width = 64)
private String sha256;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String uploadedBy;
@Column
private Long uploadedAt;
}
@@ -0,0 +1,41 @@
package io.v.nutz.levelcheck.model;
import io.v.nutz.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@Table("level_check_task")
public class LevelCheckTask extends BaseModel {
@Name
@PrevInsert(uu32 = true)
@ColDefine(type = ColType.VARCHAR, width = 32)
private String id;
@Column
@ColDefine(type = ColType.VARCHAR, width = 100)
@Comment("任务名称")
private String name;
@Column
@Comment("任务日期")
private Long taskDate;
@Column
@Comment("总单位数")
private Integer totalUnits;
@Column
@Comment("已完成单位数")
private Integer finishedUnits;
@Column
@ColDefine(type = ColType.VARCHAR, width = 500)
@Comment("任务说明")
private String description;
}
@@ -0,0 +1,85 @@
package io.v.nutz.levelcheck.model;
import io.v.nutz.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@Table("level_check_unit_task")
@TableIndexes({
@Index(name = "IDX_LEVEL_CHECK_UNIT_TASK", fields = {"taskId", "unitId"}, unique = true),
@Index(name = "IDX_LEVEL_CHECK_UNIT_STATE", fields = {"state"}, unique = false)
})
public class LevelCheckUnitTask extends BaseModel {
@Name
@PrevInsert(uu32 = true)
@ColDefine(type = ColType.VARCHAR, width = 32)
private String id;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String taskId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String unitId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 100)
private String unitName;
@Column
private Integer unitLevel;
@Column
@ColDefine(type = ColType.VARCHAR, width = 20)
private String managerType;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String unionId;
@Column
private Integer state;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String originalVersionId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String currentVersionId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String pendingVersionId;
@Column
private Integer rowCount;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String checkedBy;
@Column
private Long checkedAt;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String auditedBy;
@Column
private Long auditedAt;
@Column
@ColDefine(type = ColType.VARCHAR, width = 500)
private String auditOpinion;
@Column
private Integer dataVersion;
}
File diff suppressed because it is too large Load Diff
@@ -33,8 +33,17 @@ public class ProposalUnderTakeListener implements SysCommonDataChange {
case UPDATE -> {
Sys_unit sys_unit = dao.fetch(Sys_unit.class, bizId);
ProposalUndertake undertake = dao.fetch(ProposalUndertake.class, Cnd.where("unitCode", "=", sys_unit.getUnitcode()));
undertake.setUnitName(sys_unit.getName());
dao.updateIgnoreNull(undertake);
if (undertake == null) {
// 兼容历史单位缺少提案承办单位记录的情况
undertake = new ProposalUndertake();
undertake.setUnitCode(sys_unit.getUnitcode());
undertake.setUnitName(sys_unit.getName());
undertake.setIsEnable(Boolean.TRUE);
dao.insert(undertake);
} else {
undertake.setUnitName(sys_unit.getName());
dao.updateIgnoreNull(undertake);
}
}
case DELETE -> {
dao.clear(ProposalUndertake.class, Cnd.where("id", "=", bizId));
@@ -347,12 +347,120 @@ public class SysUnionMangeCon {
@At
public Object getUnitsByUnion(String unionid) {
try {
return Result.success().addData(sysUnitService.query(Cnd.where("unionid", "=", unionid).and("unitlevel", "=", 2)));
Sql sql = Sqls.create("""
SELECT un.*,
GROUP_CONCAT(DISTINCT CONCAT(u.username, '(', u.loginname, ')')) thirdManagerNames
FROM sys_unit un
LEFT JOIN sys_user_role sur ON sur.unitid = un.id AND sur.roleId = @roleId
LEFT JOIN sys_user u ON u.id = sur.userId
WHERE un.unionid = @unionId AND un.unitlevel = 2
GROUP BY un.id
ORDER BY un.location
""");
sql.setParam("roleId", Roles.THIRD_UNIT_MANGER);
sql.setParam("unionId", unionid);
return Result.success().addData(sysUnitService.list(sql));
} catch (Exception e) {
return Result.error();
}
}
/**
* 查询机关党委工会成员单位的可选人员和已设置管理员
*/
@At
public Object findThirdUnitManagerUsers(String unionId, String unitId) {
try {
checkThirdUnitManagerScope(unionId, unitId);
Sql userSql = Sqls.create("""
SELECT id, CONCAT(username, '(', loginname, ')') name
FROM `user`
WHERE unionid = @unionId
AND unitid = @unitId
ORDER BY username
""");
userSql.setParam("unionId", unionId);
userSql.setParam("unitId", unitId);
Sql selectedSql = Sqls.create("""
SELECT userId
FROM sys_user_role
WHERE roleId = @roleId AND unitid = @unitId
""");
selectedSql.setParam("roleId", Roles.THIRD_UNIT_MANGER);
selectedSql.setParam("unitId", unitId);
List<Record> selectedUsers = sysUnionService.list(selectedSql);
List<String> selectedUserIds = selectedUsers.stream()
.map(v -> v.getString("userId")).collect(Collectors.toList());
return Result.success().addData(Record.create()
.set("allUsers", sysUnionService.list(userSql))
.set("selectedUserIds", selectedUserIds));
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
/**
* 设置机关党委工会成员单位管理员
*/
@At
@Aop(TransAop.READ_COMMITTED)
public Object doSetThirdUnitManagerUsers(String unionId, String unitId, String userIds) {
try {
checkThirdUnitManagerScope(unionId, unitId);
List<String> ids = Strings.isBlank(userIds)
? new java.util.ArrayList<>() : Json.fromJsonAsList(String.class, userIds);
for (String userId : ids) {
Sql userCheckSql = Sqls.create("""
SELECT id
FROM `user`
WHERE id = @userId
AND unionid = @unionId
AND unitid = @unitId
LIMIT 1
""");
userCheckSql.setParam("userId", userId);
userCheckSql.setParam("unionId", unionId);
userCheckSql.setParam("unitId", unitId);
if (sysUnionService.list(userCheckSql).isEmpty()) {
return Result.error("所选人员不属于当前成员单位");
}
}
List<Sys_user_role> userRoles = new java.util.ArrayList<>();
for (String userId : ids) {
Sys_user_role userRole = new Sys_user_role();
userRole.setRoleId(Roles.THIRD_UNIT_MANGER);
userRole.setUserId(userId);
userRole.setUnitid(unitId);
userRoles.add(userRole);
}
sysUserRoleService.clear(Cnd.where("roleId", "=", Roles.THIRD_UNIT_MANGER)
.and("unitid", "=", unitId));
if (!userRoles.isEmpty()) {
sysUserRoleService.dao().insert(userRoles);
}
sysUserService.clearCache();
sysRoleService.clearCache();
return Result.success();
} catch (Exception e) {
log.error(e);
return Result.error(e.getMessage());
}
}
private void checkThirdUnitManagerScope(String unionId, String unitId) {
if (!checkRole()) {
throw new SecurityException("仅校工会管理员可设置三级管理员");
}
Sys_union union = sysUnionService.fetch(unionId);
if (union == null || !"机关党委工会".equals(Strings.trim(union.getUnionname()))) {
throw new IllegalArgumentException("仅机关党委工会的成员单位可设置三级管理员");
}
Sys_unit unit = sysUnitService.fetch(unitId);
if (unit == null || !unionId.equals(unit.getUnionid()) || unit.getUnitlevel() != 2) {
throw new IllegalArgumentException("成员单位不属于机关党委工会");
}
}
@At // 会员档案
public Object hyPageData(String unitid, String unionid, String searchName, String searchKeyword, int pageNumber, int pageSize, String pageOrderName, String pageOrderBy) {