Merge remote-tracking branch 'refs/remotes/origin/feature_主分支合并UI优化' into feature_教代会代表证

# Conflicts:
#	src/main/java/com/budwk/app/sys/controller/SysConfController.java
This commit is contained in:
2026-08-29 16:27:12 +08:00
577 changed files with 39023 additions and 8239 deletions
@@ -196,6 +196,8 @@ public class ActivityCultureApplyActivityController {
}
private void cleanNewActivityData(ActivityTissue tissue) {
// 从模板另存或直接新建的活动必须进入活动列表,不能继承来源活动的模板状态。
tissue.setTemplateStatus(ActivityTissue.TEMPLATE_STATUS_UNSET);
tissue.setId(null);
tissue.setAuditId(null);
tissue.setState(null);
@@ -4,6 +4,7 @@ import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.param.PageForm;
@@ -69,8 +70,9 @@ public class ActivityCultureInfoManageController {
public Result pageData(PageForm page, String year,
String name,
String unionId,
@Valid Integer activity_type) {
return Result.success(activityCultureService.pageData(page, year, name, unionId, activity_type));
@Valid Integer activity_type,
Integer templateStatus) {
return Result.success(activityCultureService.pageData(page, year, name, unionId, activity_type, templateStatus));
}
@At
@@ -90,47 +92,25 @@ public class ActivityCultureInfoManageController {
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("activity.culture.infoManage.school")
@SaCheckPermission(value = {"activity.culture.infoManage.school", "activity.culture.infoManage.union"}, mode = SaMode.OR)
public Result setTemplate(@Valid String id) {
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
return Result.error("无权操作");
}
ActivityTissue tissue = activityCultureService.fetch(id);
if (tissue == null) {
return Result.error("活动不存在");
String errorMessage = activityCultureService.setTemplate(id);
if (StrUtil.isNotBlank(errorMessage)) {
return Result.error(errorMessage);
}
if (tissue.getActivity_type() == null || tissue.getActivity_type() != 40001) {
return Result.error("仅校工会文化活动可设为模板");
}
Sys_home_template oldHomeTemplate = activityCultureService.dao().fetch(Sys_home_template.class, id);
Sys_home_template sysHomeTemplate = tissue.covertToSysHomeTemplate();
if (oldHomeTemplate != null) {
sysHomeTemplate.setTop(oldHomeTemplate.getTop());
sysHomeTemplate.setPush(oldHomeTemplate.getPush());
sysHomeTemplate.setSortNo(oldHomeTemplate.getSortNo());
if (oldHomeTemplate.getTemplateName() != null) {
sysHomeTemplate.setTemplateName(oldHomeTemplate.getTemplateName());
}
if (oldHomeTemplate.getTemplateIcon() != null) {
sysHomeTemplate.setTemplateIcon(oldHomeTemplate.getTemplateIcon());
}
if (oldHomeTemplate.getTemplateFile() != null) {
sysHomeTemplate.setTemplateFile(oldHomeTemplate.getTemplateFile());
}
}
activityCultureService.dao().insertOrUpdate(sysHomeTemplate);
return Result.success();
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("activity.culture.infoManage.school")
@SaCheckPermission(value = {"activity.culture.infoManage.school", "activity.culture.infoManage.union"}, mode = SaMode.OR)
public Result cancelTemplate(@Valid String id) {
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
return Result.error("无权操作");
}
activityCultureService.dao().delete(Sys_home_template.class, id);
activityCultureService.cancelTemplate(id);
return Result.success();
}
@@ -28,6 +28,12 @@ import java.util.List;
@Table("activity_tissue")
public class ActivityTissue extends BaseModel implements Serializable, SysHomeConvert {
/** 未设置为活动模板。 */
public static final int TEMPLATE_STATUS_UNSET = 0;
/** 已设置为活动模板。 */
public static final int TEMPLATE_STATUS_SET = 1;
@Column
@Name
@Comment("ID")
@@ -236,6 +242,12 @@ public class ActivityTissue extends BaseModel implements Serializable, SysHomeCo
@ColDefine(type = ColType.BOOLEAN)
private Boolean isUnseal;
@Column
@Comment("模板设置状态(0:未设置,1:已设置)")
@ColDefine(type = ColType.INT, width = 1)
@Default("0")
private Integer templateStatus;
@Column
@Comment("物品")
@ColDefine(type = ColType.MYSQL_JSON)
@@ -21,7 +21,33 @@ public interface ActivityCultureService extends BaseService<ActivityTissue> {
void doEditActivity(ActivityTissue tissue);
Object pageData(PageForm page, String year, String name, String unionId, Integer activity_type);
/**
* 分页查询文化活动。
*
* @param page 分页参数
* @param year 年度
* @param name 活动名称
* @param unionId 工会ID
* @param activity_type 活动类型
* @param templateStatus 模板设置状态,为空时不限制
* @return 文化活动分页数据
*/
Object pageData(PageForm page, String year, String name, String unionId, Integer activity_type, Integer templateStatus);
/**
* 将校工会或分工会文化活动设置为工作模板,并同步模板状态。
*
* @param id 活动ID
* @return 空字符串表示成功,否则返回业务错误信息
*/
String setTemplate(String id);
/**
* 取消校工会或分工会文化活动模板,并同步模板状态。
*
* @param id 活动ID
*/
void cancelTemplate(String id);
NutMap findOne(String id);
@@ -6,6 +6,7 @@ import com.budwk.app.base.model.Audit;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.models.Sys_home_template;
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;
@@ -14,6 +15,8 @@ import com.budwk.app.zhgh.activity.culture.models.ActivityTissuePerson;
import com.budwk.app.zhgh.activity.culture.service.ActivityCultureService;
import com.budwk.app.zhgh.club.model.SysClub;
import com.budwk.app.zhgh.club.service.SysClubService;
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;
@@ -21,6 +24,7 @@ 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.ioc.aop.Aop;
import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;
@@ -85,7 +89,7 @@ public class ActivityCultureServiceImpl extends BaseServiceImpl<ActivityTissue>
}
@Override
public Object pageData(PageForm page, String year, String name, String unionId, Integer activity_type) {
public Object pageData(PageForm page, String year, String name, String unionId, Integer activity_type, Integer templateStatus) {
Cnd cnd = Cnd.NEW();
Sql sql = Sqls.create("""
@@ -110,7 +114,7 @@ public class ActivityCultureServiceImpl extends BaseServiceImpl<ActivityTissue>
t.variable taskVariable,
IF((SELECT taskName FROM wf_process_task tt WHERE tt.id = t.taskParentId) = 'startTask', 1, 0) canRevoke,
(select MAX(id) from wf_process_task where processInstanceId = ins.id and taskName = 'startTask') AS startTaskId,
IF(sht.id IS NULL, 0, 1) AS isTemplate
tissue.templateStatus AS isTemplate
FROM
activity_tissue tissue
LEFT JOIN sys_union uni ON uni.id = tissue.unionId
@@ -119,7 +123,6 @@ public class ActivityCultureServiceImpl extends BaseServiceImpl<ActivityTissue>
LEFT JOIN `wf_process_instance` ins ON ins.businessNo = tissue.id
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
LEFT JOIN sys_home_template sht ON sht.id = tissue.id
$condition
""");
@@ -127,6 +130,8 @@ public class ActivityCultureServiceImpl extends BaseServiceImpl<ActivityTissue>
cnd.andEX("tissue.projectTypeCode", "!=", "50004");
cnd.andEX("tissue.unionId", "=", unionId);
cnd.andEX("tissue.activity_type", "=", activity_type);
// 校工会管理页按页签传入模板状态;其他文化活动页面不传时保持原查询范围。
cnd.andEX("tissue.templateStatus", "=", templateStatus);
cnd.groupBy("tissue.id");
if (Strings.isNotBlank(name)) {
@@ -159,6 +164,61 @@ public class ActivityCultureServiceImpl extends BaseServiceImpl<ActivityTissue>
return this.listPageMap(page.getPageNumber(), page.getPageSize(), sql);
}
/**
* 将校工会或分工会文化活动转换为工作模板,并保留模板中心已维护的展示配置。
*
* @param id 活动ID
* @return 空字符串表示成功,否则返回业务错误信息
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public String setTemplate(String id) {
ActivityTissue tissue = fetch(id);
if (tissue == null) {
return "活动不存在";
}
if (tissue.getActivity_type() == null || !List.of(40001, 40002).contains(tissue.getActivity_type())) {
return "仅校工会或分工会文化活动可设为模板";
}
Sys_home_template oldHomeTemplate = dao().fetch(Sys_home_template.class, id);
Sys_home_template sysHomeTemplate = tissue.covertToSysHomeTemplate();
if (oldHomeTemplate != null) {
// 重复设置时保留模板中心人工配置,避免覆盖置顶、推送、排序及模板素材。
sysHomeTemplate.setTop(oldHomeTemplate.getTop());
sysHomeTemplate.setPush(oldHomeTemplate.getPush());
sysHomeTemplate.setSortNo(oldHomeTemplate.getSortNo());
if (oldHomeTemplate.getTemplateName() != null) {
sysHomeTemplate.setTemplateName(oldHomeTemplate.getTemplateName());
}
if (oldHomeTemplate.getTemplateIcon() != null) {
sysHomeTemplate.setTemplateIcon(oldHomeTemplate.getTemplateIcon());
}
if (oldHomeTemplate.getTemplateFile() != null) {
sysHomeTemplate.setTemplateFile(oldHomeTemplate.getTemplateFile());
}
}
dao().insertOrUpdate(sysHomeTemplate);
dao().update(ActivityTissue.class,
Chain.make("templateStatus", ActivityTissue.TEMPLATE_STATUS_SET),
Cnd.where("id", "=", id));
return "";
}
/**
* 删除模板中心记录并将文化活动恢复为未设置模板状态。
*
* @param id 活动ID
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public void cancelTemplate(String id) {
dao().delete(Sys_home_template.class, id);
dao().update(ActivityTissue.class,
Chain.make("templateStatus", ActivityTissue.TEMPLATE_STATUS_UNSET),
Cnd.where("id", "=", id));
}
@Override
public NutMap findOne(String id) {
Sql sql = Sqls.create("""
@@ -87,6 +87,9 @@ public class FamilyActivityApplyController {
Cnd cnd = Cnd.NEW();
cnd.andEX("id", "=", id);
cnd.andEX("`year`", "=", year);
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
cnd.and("activityName", "like", "%" + pageForm.getSearchKeyword().trim() + "%");
}
//查询报名中
if (activityType == 2) {
cnd.and(new Static("now() < activitySignUpEndTime"));
@@ -117,6 +120,7 @@ public class FamilyActivityApplyController {
@Param(value = "courseTypeId") String courseTypeId,
@Param(value = "activityId") String activityId,
@Param(value = "assortTypes") String[] assortTypes,
@Param(value = "searchKeyword") String searchKeyword,
@Param(value = "dataType") String dataType) {
FamilyActivity activity = dao.fetch(FamilyActivity.class, activityId);
Sql sql = Sqls.create("""
@@ -131,6 +135,9 @@ public class FamilyActivityApplyController {
Cnd cnd = Cnd.NEW();
cnd.andEX("type.id", "=", courseTypeId);
cnd.and("tsuc.activityId", "=", activityId);
if (StrUtil.isNotBlank(searchKeyword)) {
cnd.and("tsuc.courseName", "like", "%" + searchKeyword.trim() + "%");
}
if(Lang.isNotEmpty(assortTypes)) {
cnd.and("tsuc.assort", "in", assortTypes);
}
@@ -6,7 +6,6 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.sys.models.Sys_home_activity;
@@ -68,20 +67,15 @@ public class FamilyActivityController {
@SaCheckPermission("family.manage")
public Result pageData(PageForm pageForm,
@Param(value = "year") Integer year,
@Param(value = "activityName") String activityName) {
@Param(value = "activityName") String activityName,
@Param(value = "templateStatus") Integer templateStatus) {
Cnd cnd = Cnd.NEW();
cnd.andEX("year", "=", year);
cnd.and(Cnd.likeEX("activityName", activityName));
// 有模板管理角色时前端按页签传入状态;普通用户不传时保持原列表范围。
cnd.andEX("templateStatus", "=", templateStatus);
cnd.orderBy("createdAt", "desc");
Pagination<FamilyActivity> pagination = familyActivityManageService.pageData(pageForm, cnd);
List<FamilyActivity> list = pagination.getList(FamilyActivity.class);
if (list != null && !list.isEmpty()) {
List<String> ids = list.stream().map(FamilyActivity::getId).toList();
List<Sys_home_template> templateList = dao.query(Sys_home_template.class, Cnd.where("id", "in", ids));
List<String> templateIds = templateList.stream().map(Sys_home_template::getId).toList();
list.forEach(activity -> activity.setIsTemplate(templateIds.contains(activity.getId())));
}
return Result.success().addData(pagination);
return Result.success().addData(familyActivityManageService.pageData(pageForm, cnd));
}
@At
@@ -91,27 +85,9 @@ public class FamilyActivityController {
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
return Result.error("无权操作");
}
FamilyActivity activity = familyActivityManageService.fetch(id);
if (activity == null) {
if (!familyActivityManageService.setTemplate(id)) {
return Result.error("活动不存在");
}
Sys_home_template oldHomeTemplate = dao.fetch(Sys_home_template.class, id);
Sys_home_template sysHomeTemplate = activity.covertToSysHomeTemplate();
if (oldHomeTemplate != null) {
sysHomeTemplate.setTop(oldHomeTemplate.getTop());
sysHomeTemplate.setPush(oldHomeTemplate.getPush());
sysHomeTemplate.setSortNo(oldHomeTemplate.getSortNo());
if (oldHomeTemplate.getTemplateName() != null) {
sysHomeTemplate.setTemplateName(oldHomeTemplate.getTemplateName());
}
if (oldHomeTemplate.getTemplateIcon() != null) {
sysHomeTemplate.setTemplateIcon(oldHomeTemplate.getTemplateIcon());
}
if (oldHomeTemplate.getTemplateFile() != null) {
sysHomeTemplate.setTemplateFile(oldHomeTemplate.getTemplateFile());
}
}
dao.insertOrUpdate(sysHomeTemplate);
return Result.success();
}
@@ -122,7 +98,7 @@ public class FamilyActivityController {
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
return Result.error("无权操作");
}
dao.delete(Sys_home_template.class, id);
familyActivityManageService.cancelTemplate(id);
return Result.success();
}
@@ -224,6 +200,8 @@ public class FamilyActivityController {
if (activity == null) {
return;
}
// 直接新建或从模板另存时明确重置状态,防止前端历史字段被带回。
activity.setTemplateStatus(FamilyActivity.TEMPLATE_STATUS_UNSET);
activity.setId(null);
activity.setIsTemplate(false);
activity.setCreatedBy(null);
@@ -25,6 +25,12 @@ import java.util.List;
@EqualsAndHashCode(callSuper = false)
public class FamilyActivity extends BaseModel implements Serializable, SysHomeConvert {
/** 未设置为活动模板。 */
public static final int TEMPLATE_STATUS_UNSET = 0;
/** 已设置为活动模板。 */
public static final int TEMPLATE_STATUS_SET = 1;
@Name
@PrevInsert(uu32 = true)
private String id;
@@ -114,6 +120,12 @@ public class FamilyActivity extends BaseModel implements Serializable, SysHomeCo
private Boolean isTemplate;
@Column
@Comment("模板设置状态(0:未设置,1:已设置)")
@ColDefine(type = ColType.INT, width = 1)
@Default("0")
private Integer templateStatus;
@Column
@Comment("活动类型")
@ColDefine(type = ColType.VARCHAR, width = 20)
@@ -50,6 +50,21 @@ public interface FamilyActivityService extends BaseService<FamilyActivity> {
*/
Pagination pageData(PageForm pageForm, Cnd cnd);
/**
* 将亲子活动设置为工作模板,并同步模板状态。
*
* @param id 活动ID
* @return true 表示活动存在且设置成功,false 表示活动不存在
*/
boolean setTemplate(String id);
/**
* 取消亲子活动模板,并同步模板状态。
*
* @param id 活动ID
*/
void cancelTemplate(String id);
/**
* 手机端分页查询
@@ -6,6 +6,7 @@ import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.models.Sys_home_template;
import com.budwk.app.sys.views.View_user;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.family.models.*;
@@ -48,7 +49,8 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
@Override
@Aop(TransAop.READ_COMMITTED)
public void add(FamilyActivity activity, FamilyCourse course) {
// 新建活动(包括从模板另存)统一归入活动列表,不能继承来源活动的模板状态。
activity.setTemplateStatus(FamilyActivity.TEMPLATE_STATUS_UNSET);
dao().insert(activity);
//插入类型限制
@@ -215,6 +217,57 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
return pagination;
}
/**
* 将亲子活动转换为工作模板,并保留模板中心已维护的展示配置。
*
* @param id 活动ID
* @return true 表示活动存在且设置成功,false 表示活动不存在
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public boolean setTemplate(String id) {
FamilyActivity activity = fetch(id);
if (activity == null) {
return false;
}
Sys_home_template oldHomeTemplate = dao().fetch(Sys_home_template.class, id);
Sys_home_template sysHomeTemplate = activity.covertToSysHomeTemplate();
if (oldHomeTemplate != null) {
// 重复设置时保留模板中心人工配置,避免覆盖置顶、推送、排序及模板素材。
sysHomeTemplate.setTop(oldHomeTemplate.getTop());
sysHomeTemplate.setPush(oldHomeTemplate.getPush());
sysHomeTemplate.setSortNo(oldHomeTemplate.getSortNo());
if (oldHomeTemplate.getTemplateName() != null) {
sysHomeTemplate.setTemplateName(oldHomeTemplate.getTemplateName());
}
if (oldHomeTemplate.getTemplateIcon() != null) {
sysHomeTemplate.setTemplateIcon(oldHomeTemplate.getTemplateIcon());
}
if (oldHomeTemplate.getTemplateFile() != null) {
sysHomeTemplate.setTemplateFile(oldHomeTemplate.getTemplateFile());
}
}
dao().insertOrUpdate(sysHomeTemplate);
dao().update(FamilyActivity.class,
Chain.make("templateStatus", FamilyActivity.TEMPLATE_STATUS_SET),
Cnd.where("id", "=", id));
return true;
}
/**
* 删除模板中心记录并将亲子活动恢复为未设置模板状态。
*
* @param id 活动ID
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public void cancelTemplate(String id) {
dao().delete(Sys_home_template.class, id);
dao().update(FamilyActivity.class,
Chain.make("templateStatus", FamilyActivity.TEMPLATE_STATUS_UNSET),
Cnd.where("id", "=", id));
}
@Override
public Pagination mPageData(PageForm pageForm, Integer year, int activityStatus, Integer activityType) {
Cnd cnd = Cnd.NEW();
@@ -65,7 +65,8 @@ public class ActivitySportsInfoManageController {
@Param(value = "gameStyle") String gameStyle,
@Param(value = "query") String query,
@Param(value = "activityId") String activityId,
@Param(value = "activityLevel") String activityLevel) {
@Param(value = "activityLevel") String activityLevel,
@Param(value = "templateStatus") Integer templateStatus) {
Cnd cnd = Cnd.NEW();
Sql sql = Sqls.create("""
SELECT
@@ -91,13 +92,12 @@ public class ActivitySportsInfoManageController {
school.activityCode,
school.foundDate,
school.applyType,
IF(sht.id IS NULL, 0, 1) isTemplate,
school.templateStatus isTemplate,
(SELECT COUNT(1) FROM activity_school_apply asa WHERE asa.activityId = school.id and status=2) applyNum
FROM
activity_school school
LEFT JOIN activity_basic_settings ba ON ba.`code` = school.activityLevel
LEFT JOIN sys_union un ON un.id = school.belongUnionId
LEFT JOIN sys_home_template sht ON sht.id = school.id
$condition
""");
@@ -121,6 +121,8 @@ public class ActivitySportsInfoManageController {
cnd.andEX("school.activityLevel", "=", activityLevel);
cnd.andEX("school.gameStyle", "=", gameStyle);
// 管理员页签传入模板状态;未展示页签的用户不传该参数,继续查询原完整列表。
cnd.andEX("school.templateStatus", "=", templateStatus);
cnd.andEX("YEAR(school.applyStartTime)", "=", year);
if (StrUtil.isNotBlank(name)) {
cnd.and(Cnd.likeEX("school.name", name));
@@ -141,27 +143,9 @@ public class ActivitySportsInfoManageController {
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
return Result.error("无权操作");
}
ActivitySchool activitySchool = activitySportsService.fetch(id);
if (activitySchool == null) {
if (!activitySportsService.setTemplate(id)) {
return Result.error("活动不存在");
}
Sys_home_template oldHomeTemplate = dao.fetch(Sys_home_template.class, id);
Sys_home_template sysHomeTemplate = activitySchool.covertToSysHomeTemplate();
if (oldHomeTemplate != null) {
sysHomeTemplate.setTop(oldHomeTemplate.getTop());
sysHomeTemplate.setPush(oldHomeTemplate.getPush());
sysHomeTemplate.setSortNo(oldHomeTemplate.getSortNo());
if (oldHomeTemplate.getTemplateName() != null) {
sysHomeTemplate.setTemplateName(oldHomeTemplate.getTemplateName());
}
if (oldHomeTemplate.getTemplateIcon() != null) {
sysHomeTemplate.setTemplateIcon(oldHomeTemplate.getTemplateIcon());
}
if (oldHomeTemplate.getTemplateFile() != null) {
sysHomeTemplate.setTemplateFile(oldHomeTemplate.getTemplateFile());
}
}
dao.insertOrUpdate(sysHomeTemplate);
return Result.success();
}
@@ -172,7 +156,7 @@ public class ActivitySportsInfoManageController {
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
return Result.error("无权操作");
}
dao.delete(Sys_home_template.class, id);
activitySportsService.cancelTemplate(id);
return Result.success();
}
@@ -64,8 +64,8 @@ public class H5ActivitySportsApplyUserController {
@At
@SaCheckPermission("h5.activity.sports.applyUser")
public Result activityData(Integer year, Integer isActivity, Integer applyStatus) {
return Result.success(activitySportsApplyUserService.activityData(year, isActivity, applyStatus));
public Result activityData(Integer year, Integer isActivity, Integer applyStatus, String searchKeyword) {
return Result.success(activitySportsApplyUserService.activityData(year, isActivity, applyStatus, searchKeyword));
}
@@ -171,6 +171,18 @@ public class ActivitySchool extends BaseModel implements Serializable , SysHomeC
@ColDefine(type = ColType.BOOLEAN)
private Integer isSave;
/** 未设置为工作模板。 */
public static final int TEMPLATE_STATUS_UNSET = 0;
/** 已设置为工作模板。 */
public static final int TEMPLATE_STATUS_SET = 1;
@Column
@Comment("模板设置状态(0:未设置 1:已设置)")
@ColDefine(type = ColType.INT, width = 1)
@Default("0")
private Integer templateStatus;
@Column
@Comment("小程序活动封面")
@ColDefine(type = ColType.VARCHAR,width = 100)
@@ -18,6 +18,7 @@ public class ActivitySportsApplyUserPageParam extends PageForm {
private String applyType;
private String isAudit;
private String groupName;
private String searchKeyword;
private String activityId;
private String eventId;
private String year;
@@ -19,6 +19,17 @@ public interface ActivitySportsApplyUserService extends BaseService<ActivityScho
Pagination activityData(Integer year, Integer isActivity,Integer applyStatus);
/**
* 根据年份、活动状态、报名状态和活动名称查询体育活动。
*
* @param year 活动年份
* @param isActivity 活动状态
* @param applyStatus 当前用户报名状态
* @param searchKeyword 活动名称模糊查询关键词
* @return 体育活动分页数据
*/
Pagination activityData(Integer year, Integer isActivity, Integer applyStatus, String searchKeyword);
List<NutMap> getEvents(String activityId);
Object getUnionCoachLeaderHead(String activityId,String unionId);
@@ -18,5 +18,20 @@ public interface ActivitySportsService extends BaseService<ActivitySchool> {
void doDelete(String id);
/**
* 将体育活动设置为工作模板,并同步活动表中的模板设置状态。
*
* @param id 体育活动ID
* @return 活动存在且设置成功时返回 true,活动不存在时返回 false
*/
boolean setTemplate(String id);
/**
* 取消体育活动的工作模板,并同步活动表中的模板设置状态。
*
* @param id 体育活动ID
*/
void cancelTemplate(String id);
void exportXlsx(String id, String unionId, HttpServletResponse response);
}
@@ -122,6 +122,9 @@ public class ActivitySportsApplyUserServiceImpl extends BaseServiceImpl<Activity
cnd.and(new Static("(SELECT COUNT(1) FROM activity_school_apply WHERE eventId = ase.eventId AND activityId = ase.activityId AND userId='%s')%s0".formatted(SecurityUtil.getUserId(), (pageParam.getIsAudit().equals("true") ? ">" : "="))));
}
cnd.and("ase.activityId", "=", pageParam.getActivityId());
if (StrUtil.isNotBlank(pageParam.getSearchKeyword())) {
cnd.and("eve.allName", "like", "%" + pageParam.getSearchKeyword().trim() + "%");
}
cnd.andEX("ase.eventId", "=", pageParam.getEventId());
cnd.andEX("YEAR(school.applyStartTime)", "=", pageParam.getYear());
@@ -144,6 +147,20 @@ public class ActivitySportsApplyUserServiceImpl extends BaseServiceImpl<Activity
@Override
public Pagination activityData(Integer year, Integer isActivity, Integer applyStatus) {
return activityData(year, isActivity, applyStatus, null);
}
/**
* 根据筛选条件及活动名称查询当前用户可见的体育活动。
*
* @param year 活动年份
* @param isActivity 活动状态
* @param applyStatus 当前用户报名状态
* @param searchKeyword 活动名称模糊查询关键词
* @return 体育活动分页数据
*/
@Override
public Pagination activityData(Integer year, Integer isActivity, Integer applyStatus, String searchKeyword) {
Sql sql = Sqls.create("""
SELECT
@@ -183,6 +200,10 @@ public class ActivitySportsApplyUserServiceImpl extends BaseServiceImpl<Activity
cnd.and("school.isSave", "!=", true);
if (Lang.isNotEmpty(searchKeyword)) {
cnd.and("school.NAME", "like", "%" + searchKeyword.trim() + "%");
}
//查询报名中
if (isActivity == 2) {
cnd.and(new Static("now() >applyStartTime and now() < applyEndTime"));
@@ -8,6 +8,7 @@ import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.base.utils.DateUtil;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.models.Sys_home_template;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnit;
import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnion;
@@ -61,6 +62,8 @@ public class ActivitySportsServiceImpl extends BaseServiceImpl<ActivitySchool> i
}
activitySchool.setFoundDate(DateUtil.getDate());
activitySchool.setClose(false);
// 新建活动只能从“未设置模板”状态开始,防止客户端提交模板状态造成数据不一致。
activitySchool.setTemplateStatus(ActivitySchool.TEMPLATE_STATUS_UNSET);
ActivitySchool school = insert(activitySchool);
addTeam(school.getId(), events);
@@ -190,6 +193,59 @@ public class ActivitySportsServiceImpl extends BaseServiceImpl<ActivitySchool> i
dao().delete(Sys_home_activity.class, id);
}
/**
* 将活动内容转换为首页工作模板,并在同一事务内更新活动模板状态。
* 重复设置时保留管理员在工作模板管理中维护的名称、图标、文件及排序配置。
*
* @param id 体育活动ID
* @return 活动存在且设置成功时返回 true,否则返回 false
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public boolean setTemplate(String id) {
ActivitySchool activitySchool = fetch(id);
if (activitySchool == null) {
return false;
}
Sys_home_template oldHomeTemplate = dao().fetch(Sys_home_template.class, id);
Sys_home_template sysHomeTemplate = activitySchool.covertToSysHomeTemplate();
if (oldHomeTemplate != null) {
// 模板业务数据随活动刷新,模板管理页面维护的展示配置继续沿用。
sysHomeTemplate.setTop(oldHomeTemplate.getTop());
sysHomeTemplate.setPush(oldHomeTemplate.getPush());
sysHomeTemplate.setSortNo(oldHomeTemplate.getSortNo());
if (oldHomeTemplate.getTemplateName() != null) {
sysHomeTemplate.setTemplateName(oldHomeTemplate.getTemplateName());
}
if (oldHomeTemplate.getTemplateIcon() != null) {
sysHomeTemplate.setTemplateIcon(oldHomeTemplate.getTemplateIcon());
}
if (oldHomeTemplate.getTemplateFile() != null) {
sysHomeTemplate.setTemplateFile(oldHomeTemplate.getTemplateFile());
}
}
dao().insertOrUpdate(sysHomeTemplate);
dao().update(ActivitySchool.class,
Chain.make("templateStatus", ActivitySchool.TEMPLATE_STATUS_SET),
Cnd.where("id", "=", id));
return true;
}
/**
* 删除首页工作模板,并在同一事务内将活动恢复为未设置模板状态。
*
* @param id 体育活动ID
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public void cancelTemplate(String id) {
dao().delete(Sys_home_template.class, id);
dao().update(ActivitySchool.class,
Chain.make("templateStatus", ActivitySchool.TEMPLATE_STATUS_UNSET),
Cnd.where("id", "=", id));
}
@Override
public void exportXlsx(String id, String unionId, HttpServletResponse response) {
try {
@@ -93,6 +93,9 @@ public class TrainSignUpApplyController {
Cnd cnd = Cnd.NEW();
cnd.andEX("id", "=", id);
cnd.andEX("`year`", "=", year);
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
cnd.and(Cnd.likeEX("activityName", pageForm.getSearchKeyword()));
}
//查询报名中
if (activityType == 2) {
cnd.and(new Static("now() < activitySignUpEndTime"));
@@ -123,6 +126,7 @@ public class TrainSignUpApplyController {
@Param(value = "courseTypeId") String courseTypeId,
@Param(value = "activityId") String activityId,
@Param(value = "assortTypes") String[] assortTypes,
@Param(value = "searchKeyword") String searchKeyword,
@Param(value = "dataType") String dataType) {
Sql sql = Sqls.create("""
SELECT
@@ -156,6 +160,9 @@ public class TrainSignUpApplyController {
Cnd cnd = Cnd.NEW();
cnd.andEX("type.id", "=", courseTypeId);
cnd.and("tsuc.activityId", "=", activityId);
if (StrUtil.isNotBlank(searchKeyword)) {
cnd.and("tsuc.courseName", "like", "%" + searchKeyword.trim() + "%");
}
if (Lang.isNotEmpty(assortTypes)) {
cnd.and("tsuc.assort", "in", assortTypes);
@@ -6,7 +6,6 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.flow.entity.ProcessInstance;
@@ -72,20 +71,15 @@ public class TrainSignUpManageController {
@SaCheckPermission("trainSignUp.manage")
public Result pageData(PageForm pageForm,
@Param(value = "year") Integer year,
@Param(value = "activityName") String activityName) {
@Param(value = "activityName") String activityName,
@Param(value = "templateStatus") Integer templateStatus) {
Cnd cnd = Cnd.NEW();
cnd.andEX("year", "=", year);
cnd.and(Cnd.likeEX("activityName", activityName));
// 具备模板管理角色时,前端按页签传入状态;普通用户不传时仍保持原列表范围。
cnd.andEX("templateStatus", "=", templateStatus);
cnd.orderBy("createdAt", "desc");
Pagination<TrainSignUpActivity> pagination = trainSignUpActivityManageService.pageData(pageForm, cnd);
List<TrainSignUpActivity> list = pagination.getList(TrainSignUpActivity.class);
if (list != null && !list.isEmpty()) {
List<String> ids = list.stream().map(TrainSignUpActivity::getId).toList();
List<Sys_home_template> templateList = dao.query(Sys_home_template.class, Cnd.where("id", "in", ids));
List<String> templateIds = templateList.stream().map(Sys_home_template::getId).toList();
list.forEach(activity -> activity.setIsTemplate(templateIds.contains(activity.getId())));
}
return Result.success().addData(pagination);
return Result.success().addData(trainSignUpActivityManageService.pageData(pageForm, cnd));
}
@At
@@ -95,27 +89,9 @@ public class TrainSignUpManageController {
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
return Result.error("无权操作");
}
TrainSignUpActivity activity = trainSignUpActivityManageService.fetch(id);
if (activity == null) {
if (!trainSignUpActivityManageService.setTemplate(id)) {
return Result.error("活动不存在");
}
Sys_home_template oldHomeTemplate = dao.fetch(Sys_home_template.class, id);
Sys_home_template sysHomeTemplate = activity.covertToSysHomeTemplate();
if (oldHomeTemplate != null) {
sysHomeTemplate.setTop(oldHomeTemplate.getTop());
sysHomeTemplate.setPush(oldHomeTemplate.getPush());
sysHomeTemplate.setSortNo(oldHomeTemplate.getSortNo());
if (oldHomeTemplate.getTemplateName() != null) {
sysHomeTemplate.setTemplateName(oldHomeTemplate.getTemplateName());
}
if (oldHomeTemplate.getTemplateIcon() != null) {
sysHomeTemplate.setTemplateIcon(oldHomeTemplate.getTemplateIcon());
}
if (oldHomeTemplate.getTemplateFile() != null) {
sysHomeTemplate.setTemplateFile(oldHomeTemplate.getTemplateFile());
}
}
dao.insertOrUpdate(sysHomeTemplate);
return Result.success();
}
@@ -126,7 +102,7 @@ public class TrainSignUpManageController {
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
return Result.error("无权操作");
}
dao.delete(Sys_home_template.class, id);
trainSignUpActivityManageService.cancelTemplate(id);
return Result.success();
}
@@ -22,6 +22,12 @@ import java.util.List;
@EqualsAndHashCode(callSuper = false)
public class TrainSignUpActivity extends BaseModel implements Serializable, SysHomeConvert {
/** 未设置为活动模板。 */
public static final int TEMPLATE_STATUS_UNSET = 0;
/** 已设置为活动模板。 */
public static final int TEMPLATE_STATUS_SET = 1;
@Name
@PrevInsert(uu32 = true)
private String id;
@@ -112,6 +118,12 @@ public class TrainSignUpActivity extends BaseModel implements Serializable, SysH
private Boolean isTemplate;
@Column
@Comment("模板设置状态(0:未设置,1:已设置)")
@ColDefine(type = ColType.INT, width = 1)
@Default("0")
private Integer templateStatus;
@Column
@Comment("活动类型")
@ColDefine(type = ColType.VARCHAR, width = 20)
@@ -59,6 +59,21 @@ public interface TrainSignUpActivityService extends BaseService<TrainSignUpActiv
*/
Pagination pageData(PageForm pageForm, Cnd cnd);
/**
* 将品牌活动设置为工作模板,并同步活动表中的模板状态。
*
* @param id 活动ID
* @return true 表示活动存在且设置成功,false 表示活动不存在
*/
boolean setTemplate(String id);
/**
* 取消品牌活动工作模板,并同步活动表中的模板状态。
*
* @param id 活动ID
*/
void cancelTemplate(String id);
/**
* 手机端分页查询
@@ -5,6 +5,7 @@ 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.sys.models.Sys_home_activity;
import com.budwk.app.sys.models.Sys_home_template;
import com.budwk.app.sys.views.View_user;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.family.models.FamilyActivity;
@@ -53,7 +54,8 @@ public class TrainSignUpActivityServiceImpl extends BaseServiceImpl<TrainSignUpA
@Override
@Aop(TransAop.READ_COMMITTED)
public void add(TrainSignUpActivity activity, TrainSignUpCourse course) {
// 新建活动(包括从模板另存)统一归入活动列表,不能继承来源活动的模板状态。
activity.setTemplateStatus(TrainSignUpActivity.TEMPLATE_STATUS_UNSET);
dao().insert(activity);
//插入类型限制
@@ -289,6 +291,58 @@ public class TrainSignUpActivityServiceImpl extends BaseServiceImpl<TrainSignUpA
return pagination;
}
/**
* 将品牌活动转换为工作模板,并原样保留模板中心已维护的展示配置。
*
* @param id 活动ID
* @return true 表示活动存在且设置成功,false 表示活动不存在
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public boolean setTemplate(String id) {
TrainSignUpActivity activity = fetch(id);
if (activity == null) {
return false;
}
Sys_home_template oldHomeTemplate = dao().fetch(Sys_home_template.class, id);
Sys_home_template sysHomeTemplate = activity.covertToSysHomeTemplate();
if (oldHomeTemplate != null) {
// 重复设置时保留模板中心人工配置,避免覆盖置顶、推送、排序及模板素材。
sysHomeTemplate.setTop(oldHomeTemplate.getTop());
sysHomeTemplate.setPush(oldHomeTemplate.getPush());
sysHomeTemplate.setSortNo(oldHomeTemplate.getSortNo());
if (oldHomeTemplate.getTemplateName() != null) {
sysHomeTemplate.setTemplateName(oldHomeTemplate.getTemplateName());
}
if (oldHomeTemplate.getTemplateIcon() != null) {
sysHomeTemplate.setTemplateIcon(oldHomeTemplate.getTemplateIcon());
}
if (oldHomeTemplate.getTemplateFile() != null) {
sysHomeTemplate.setTemplateFile(oldHomeTemplate.getTemplateFile());
}
}
dao().insertOrUpdate(sysHomeTemplate);
dao().update(TrainSignUpActivity.class,
Chain.make("templateStatus", TrainSignUpActivity.TEMPLATE_STATUS_SET),
Cnd.where("id", "=", id));
return true;
}
/**
* 删除模板中心记录并将活动恢复为未设置模板状态。
*
* @param id 活动ID
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public void cancelTemplate(String id) {
dao().delete(Sys_home_template.class, id);
dao().update(TrainSignUpActivity.class,
Chain.make("templateStatus", TrainSignUpActivity.TEMPLATE_STATUS_UNSET),
Cnd.where("id", "=", id));
}
@Override
public Pagination mPageData(PageForm pageForm, Integer year, int activityStatus, Integer activityType) {
Cnd cnd = Cnd.NEW();
@@ -7,10 +7,8 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HtmlUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.models.Sys_home_template;
import com.budwk.app.sys.models.Sys_user;
@@ -53,8 +51,6 @@ public class ActivityWorksCollectionManageController {
@Inject
private ActivityWorksCollectionService activityWorksCollectionService;
@Inject
private BaseService baseService;
@Inject
private SysMsgService sysMsgService;
@Inject
private ActivityBasicScopeService activityBasicScopeService;
@@ -67,31 +63,8 @@ public class ActivityWorksCollectionManageController {
@At
@SaCheckPermission("activity.workscollection.manage")
public Result pageData(@Valid PageForm pageForm, Long year) {
Sql sql = Sqls.create("""
select
wc.id,
wc.name,
wc.createdAt,
wc.startDateTime,
wc.endDateTime,
wc.enable,
IF(sht.id IS NULL, 0, 1) isTemplate,
u.username as userName
from
activity_works_collection wc
LEFT JOIN vw_user u on u.id = wc.createdBy
LEFT JOIN sys_home_template sht ON sht.id = wc.id
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.andEX("year(startDateTime)", "=", year);
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
cnd.where().andLike("name", pageForm.getSearchKeyword());
}
sql.setCondition(cnd);
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
public Result pageData(@Valid PageForm pageForm, Long year, Integer templateStatus) {
return Result.success(activityWorksCollectionService.pageData(pageForm, year, templateStatus));
}
@At
@@ -100,27 +73,9 @@ public class ActivityWorksCollectionManageController {
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
return Result.error("无权操作");
}
Activity_works_collection worksCollection = dao.fetch(Activity_works_collection.class, id);
if (worksCollection == null) {
if (!activityWorksCollectionService.setTemplate(id)) {
return Result.error("活动不存在");
}
Sys_home_template oldHomeTemplate = dao.fetch(Sys_home_template.class, id);
Sys_home_template sysHomeTemplate = worksCollection.covertToSysHomeTemplate();
if (oldHomeTemplate != null) {
sysHomeTemplate.setTop(oldHomeTemplate.getTop());
sysHomeTemplate.setPush(oldHomeTemplate.getPush());
sysHomeTemplate.setSortNo(oldHomeTemplate.getSortNo());
if (oldHomeTemplate.getTemplateName() != null) {
sysHomeTemplate.setTemplateName(oldHomeTemplate.getTemplateName());
}
if (oldHomeTemplate.getTemplateIcon() != null) {
sysHomeTemplate.setTemplateIcon(oldHomeTemplate.getTemplateIcon());
}
if (oldHomeTemplate.getTemplateFile() != null) {
sysHomeTemplate.setTemplateFile(oldHomeTemplate.getTemplateFile());
}
}
dao.insertOrUpdate(sysHomeTemplate);
return Result.success();
}
@@ -130,7 +85,7 @@ public class ActivityWorksCollectionManageController {
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
return Result.error("无权操作");
}
dao.delete(Sys_home_template.class, id);
activityWorksCollectionService.cancelTemplate(id);
return Result.success();
}
@@ -165,6 +120,8 @@ public class ActivityWorksCollectionManageController {
if (worksCollection == null) {
return;
}
// 新建或从模板另存的活动必须进入活动列表,不能继承来源活动的模板状态。
worksCollection.setTemplateStatus(Activity_works_collection.TEMPLATE_STATUS_UNSET);
worksCollection.setId(null);
worksCollection.setCreatedBy(null);
worksCollection.setCreatedAt(null);
@@ -22,6 +22,12 @@ import java.util.List;
@Comment("作品征集活动")
public class Activity_works_collection extends BaseModel implements SysHomeConvert {
/** 未设置为活动模板。 */
public static final int TEMPLATE_STATUS_UNSET = 0;
/** 已设置为活动模板。 */
public static final int TEMPLATE_STATUS_SET = 1;
@Name
@Comment("ID")
@PrevInsert(uu32 = true)
@@ -110,6 +116,12 @@ public class Activity_works_collection extends BaseModel implements SysHomeConve
@Default(value = "1")
private Boolean isSubmit;
@Column
@Comment("模板设置状态(0:未设置,1:已设置)")
@ColDefine(type = ColType.INT, width = 1)
@Default("0")
private Integer templateStatus;
@Column
@Comment("主办单位")
@ColDefine(type = ColType.MYSQL_JSON)
@@ -1,11 +1,39 @@
package com.budwk.app.zhgh.activity.workscollection.service;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.activity.workscollection.models.Activity_works_collection;
/**
* 作品征集活动配置服务。
*/
public interface ActivityWorksCollectionService {
public interface ActivityWorksCollectionService extends BaseService<Activity_works_collection> {
/**
* 分页查询作品征集活动。
*
* @param pageForm 分页及关键字参数
* @param year 活动年度
* @param templateStatus 模板设置状态,为空时不限制
* @return 作品征集活动分页数据
*/
Pagination pageData(PageForm pageForm, Long year, Integer templateStatus);
/**
* 将作品征集活动设置为工作模板,并同步模板状态。
*
* @param id 活动ID
* @return true 表示设置成功,false 表示活动不存在
*/
boolean setTemplate(String id);
/**
* 取消作品征集活动模板,并同步模板状态。
*
* @param id 活动ID
*/
void cancelTemplate(String id);
/**
* 校验编辑活动时删除的主题类型、作品类型是否已被投稿引用。
@@ -2,13 +2,22 @@ package com.budwk.app.zhgh.activity.workscollection.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
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.sys.models.Sys_home_template;
import com.budwk.app.zhgh.activity.workscollection.models.Activity_works_collection;
import com.budwk.app.zhgh.activity.workscollection.models.Activity_works_collection_upload;
import com.budwk.app.zhgh.activity.workscollection.models.Activity_works_subjectType;
import com.budwk.app.zhgh.activity.workscollection.models.Activity_works_worksType;
import com.budwk.app.zhgh.activity.workscollection.service.ActivityWorksCollectionService;
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.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.Collections;
@@ -20,14 +29,93 @@ import java.util.stream.Collectors;
* 作品征集活动配置服务实现。
*/
@IocBean(args = {"refer:dao"})
public class ActivityWorksCollectionServiceImpl implements ActivityWorksCollectionService {
public class ActivityWorksCollectionServiceImpl extends BaseServiceImpl<Activity_works_collection> implements ActivityWorksCollectionService {
private final Dao dao;
public ActivityWorksCollectionServiceImpl(Dao dao) {
super(dao);
this.dao = dao;
}
/**
* 按年度、关键字及模板状态分页查询作品征集活动。
*/
@Override
public Pagination pageData(PageForm pageForm, Long year, Integer templateStatus) {
Sql sql = Sqls.create("""
select
wc.id,
wc.name,
wc.createdAt,
wc.startDateTime,
wc.endDateTime,
wc.enable,
wc.templateStatus,
wc.templateStatus isTemplate,
u.username as userName
from
activity_works_collection wc
LEFT JOIN vw_user u on u.id = wc.createdBy
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.andEX("year(wc.startDateTime)", "=", year);
cnd.andEX("wc.templateStatus", "=", templateStatus);
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
cnd.where().andLike("wc.name", pageForm.getSearchKeyword());
}
cnd.desc("wc.createdAt");
sql.setCondition(cnd);
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
}
/**
* 将作品征集活动转换为工作模板,并保留模板中心已维护的展示配置。
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public boolean setTemplate(String id) {
Activity_works_collection worksCollection = dao.fetch(Activity_works_collection.class, id);
if (worksCollection == null) {
return false;
}
Sys_home_template oldHomeTemplate = dao.fetch(Sys_home_template.class, id);
Sys_home_template sysHomeTemplate = worksCollection.covertToSysHomeTemplate();
if (oldHomeTemplate != null) {
// 重复设置时保留模板中心人工配置,避免覆盖置顶、推送、排序及模板素材。
sysHomeTemplate.setTop(oldHomeTemplate.getTop());
sysHomeTemplate.setPush(oldHomeTemplate.getPush());
sysHomeTemplate.setSortNo(oldHomeTemplate.getSortNo());
if (oldHomeTemplate.getTemplateName() != null) {
sysHomeTemplate.setTemplateName(oldHomeTemplate.getTemplateName());
}
if (oldHomeTemplate.getTemplateIcon() != null) {
sysHomeTemplate.setTemplateIcon(oldHomeTemplate.getTemplateIcon());
}
if (oldHomeTemplate.getTemplateFile() != null) {
sysHomeTemplate.setTemplateFile(oldHomeTemplate.getTemplateFile());
}
}
dao.insertOrUpdate(sysHomeTemplate);
dao.update(Activity_works_collection.class,
Chain.make("templateStatus", Activity_works_collection.TEMPLATE_STATUS_SET),
Cnd.where("id", "=", id));
return true;
}
/**
* 删除模板中心记录并将作品征集活动恢复为未设置模板状态。
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public void cancelTemplate(String id) {
dao.delete(Sys_home_template.class, id);
dao.update(Activity_works_collection.class,
Chain.make("templateStatus", Activity_works_collection.TEMPLATE_STATUS_UNSET),
Cnd.where("id", "=", id));
}
@Override
public String validateReferencedTypesBeforeUpdate(Activity_works_collection worksCollection) {
if (worksCollection == null || StrUtil.isBlank(worksCollection.getId())) {
@@ -76,7 +76,8 @@ public class ClubExamineApplyController {
if(year == null) {
return Result.error("年份信息为空,请核查");
}
List<SysClubExamineRegister> list = dao.query(SysClubExamineRegister.class, Cnd.where("clubId", "=", clubId).and("year(registerDate)", "=", year).andEX("id", "!=", id));
// 同一社团的年度重复申请校验以登记年度为准,不能按申请时间判断。
List<SysClubExamineRegister> list = dao.query(SysClubExamineRegister.class, Cnd.where("clubId", "=", clubId).and("year", "=", year).andEX("id", "!=", id));
List<ProcessInstance> instanceList = dao.query(
ProcessInstance.class,
Cnd.where(ProcessInstance::getBusinessNo, "in", list.stream().map(SysClubExamineRegister::getId).toList())
@@ -146,8 +147,9 @@ public class ClubExamineApplyController {
if(StrUtil.isBlank(clubId)) {
return Result.error("社团信息为空,请核查");
}
// 上年度结余按登记年度查询,避免申请时间跨年导致取错数据。
SysClubExamineRegister register = dao.fetch(SysClubExamineRegister.class, Cnd.where("clubId", "=", clubId)
.and("YEAR(registerDate)", "=", DateUtil.thisYear() - 1));
.and("year", "=", DateUtil.thisYear() - 1));
if (Lang.isNotEmpty(register)) {
List<JSONObject> list = register.getIncomeCensus();
float surplus = list.get(0).getFloat("surplus");
@@ -87,7 +87,8 @@ public class ClubExamineMineController {
nutMap.setv("clubName", register.getClubName());
nutMap.setv("create_time", register.getFoundTime());
nutMap.setv("dues_standard", register.getDue());
nutMap.setv("year", register.getRegisterDate().substring(0, 4));
// 生成材料中的年度与列表展示的登记年度保持一致。
nutMap.setv("year", register.getYear());
nutMap.setv("registerDate", register.getRegisterDate());
nutMap.setv("incomeCensus", incomeCensus);
nutMap.setv("jgUser", jgUser);
@@ -90,10 +90,12 @@ public class ClubAuditManagerController {
cnd.andEX("year(info.creatTime)", "=", pageForm.getYear());
cnd.and("t.taskName", "=", "ef81777f-22fb-4fe4-9800-909e6c681210");
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
if (approval) {
// 已审核是全量历史,不能因历史工作流参与人不同而遗漏参考库迁移记录。
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
} else {
// 未审核只展示当前登录人实际可以处理的待办任务。
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
}
@@ -2,10 +2,14 @@ package com.budwk.app.zhgh.club.controller.infoManage;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.sys.models.Sys_file;
import com.budwk.app.sys.utils.SysFileMinIoUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.club.param.ClubUserPageForm;
import com.budwk.app.zhgh.club.service.SysClubInfoManageService;
@@ -14,12 +18,24 @@ import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* @Author: JyuHsin
@@ -32,6 +48,8 @@ import java.util.List;
@At("/platform/club/infoManage/schoolAuditReport")
public class ClubSchoolAuditReportController {
private static final Logger log = LoggerFactory.getLogger(ClubSchoolAuditReportController.class);
@Inject
private SysClubInfoManageService clubInfoManageService;
@@ -44,6 +62,71 @@ public class ClubSchoolAuditReportController {
@SaCheckPermission("club.infoManage.schoolAuditReport")
public Result pageData(@Valid ClubUserPageForm pageForm,
@Param(value = "approval") Boolean approval) {
Sql sql = buildPageDataSql(pageForm, approval);
Pagination pagination = clubInfoManageService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
}
/**
* 导出当前筛选范围内的换届报告附件压缩包。
*
* @param pageForm 页面查询条件
* @param approval 审核状态,与列表页保持一致
* @param response HTTP 下载响应
*/
@At
@Ok("void")
@SaCheckPermission("club.infoManage.schoolAuditReport")
public void exportRefreshReportZip(@Valid ClubUserPageForm pageForm,
@Param(value = "approval") Boolean approval,
HttpServletResponse response) throws IOException {
List<NutMap> reportList = clubInfoManageService.listMap(buildPageDataSql(pageForm, approval));
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment;filename=" + URLUtil.encode("换届报告.zip"));
// 显式指定 UTF-8,确保压缩包内中文目录和文件名正常显示。
try (ZipOutputStream zipOutputStream = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream()), StandardCharsets.UTF_8)) {
Set<String> entryNames = new HashSet<>();
for (NutMap report : reportList) {
String filesJson = report.getString("files");
if (StrUtil.isBlank(filesJson)) {
continue;
}
List<JSONObject> files = Json.fromJsonAsList(JSONObject.class, filesJson);
String folderName = buildFolderName(report);
for (int index = 0; index < files.size(); index++) {
JSONObject fileInfo = files.get(index);
Sys_file sysFile = findSysFile(fileInfo);
if (sysFile == null) {
log.warn("换届报告附件不存在,报告ID:{},附件:{}", report.getString("id"), fileInfo);
continue;
}
byte[] fileBytes = SysFileMinIoUtil.getFileBytes(sysFile.getBucket(), sysFile.getStoragePath());
if (fileBytes == null || fileBytes.length == 0) {
log.warn("换届报告附件内容为空,报告ID:{},附件ID:{}", report.getString("id"), sysFile.getId());
continue;
}
String fileName = StrUtil.blankToDefault(fileInfo.getStr("name"), fileInfo.getStr("filename"));
if (StrUtil.isBlank(fileName)) {
fileName = sysFile.getName();
}
String entryName = buildUniqueEntryName(folderName + "/" + sanitizeFileName(fileName), entryNames);
zipOutputStream.putNextEntry(new ZipEntry(entryName));
zipOutputStream.write(fileBytes);
zipOutputStream.closeEntry();
}
}
}
}
/**
* 构建列表与导出共用的换届报告查询,确保“查什么导什么”。
*
* @param pageForm 页面查询条件
* @param approval 审核状态
* @return 已绑定筛选条件的查询对象
*/
private Sql buildPageDataSql(ClubUserPageForm pageForm, Boolean approval) {
Sql sql = Sqls.create("""
SELECT
info.*,
@@ -79,7 +162,7 @@ public class ClubSchoolAuditReportController {
cnd.and("t.taskName", "=", "d4323546-8d09-419e-88d4-7b15862ca29d");
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
if (approval) {
if (Boolean.TRUE.equals(approval)) {
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
} else {
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
@@ -96,8 +179,67 @@ public class ClubSchoolAuditReportController {
cnd.groupBy("t.id");
sql.setCondition(cnd);
return sql;
}
Pagination pagination = clubInfoManageService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
/**
* 兼容新旧附件 JSON 结构,按附件 ID 或下载地址查找文件元数据。
*
* @param fileInfo 附件 JSON 信息
* @return 系统文件记录;未找到时返回 null
*/
private Sys_file findSysFile(JSONObject fileInfo) {
String fileId = fileInfo.getStr("id");
if (StrUtil.isNotBlank(fileId)) {
return clubInfoManageService.dao().fetch(Sys_file.class, fileId);
}
String fileUrl = fileInfo.getStr("url");
if (StrUtil.isBlank(fileUrl)) {
return null;
}
return clubInfoManageService.dao().fetch(Sys_file.class, Cnd.where(Sys_file::getDownloadPath, "=", fileUrl));
}
/**
* 使用社团名称建立目录,方便按换届报告来源查阅。
*
* @param report 换届报告记录
* @return Zip 内目录名称
*/
private String buildFolderName(NutMap report) {
return sanitizeFileName(report.getString("clubName"));
}
/**
* 保留附件原始文件名;同一社团目录存在同名文件时追加序号,避免覆盖。
*
* @param entryName 原始 Zip 条目名称
* @param entryNames 已写入的 Zip 条目名称
* @return 唯一的 Zip 条目名称
*/
private String buildUniqueEntryName(String entryName, Set<String> entryNames) {
if (entryNames.add(entryName)) {
return entryName;
}
int extensionIndex = entryName.lastIndexOf('.');
String name = extensionIndex > entryName.lastIndexOf('/') ? entryName.substring(0, extensionIndex) : entryName;
String extension = extensionIndex > entryName.lastIndexOf('/') ? entryName.substring(extensionIndex) : "";
int index = 2;
String uniqueEntryName;
do {
uniqueEntryName = name + "(" + index + ")" + extension;
index++;
} while (!entryNames.add(uniqueEntryName));
return uniqueEntryName;
}
/**
* 去除 Zip 条目路径分隔符,避免附件名影响压缩包目录结构。
*
* @param fileName 原始文件名
* @return 可安全写入 Zip 的文件名
*/
private String sanitizeFileName(String fileName) {
return StrUtil.blankToDefault(fileName, "未命名文件").replace("/", "_").replace("\\", "_");
}
}
@@ -95,6 +95,14 @@ public class SysClubExamineRegister extends BaseModel {
@ColDefine(type = ColType.MYSQL_JSON)
private List<JSONObject> yearActivityList;
/**
* 旧版年审表保存的年度活动明细,保留该字段用于查看迁移历史数据。
*/
@Column
@Comment("旧版年度活动明细")
@ColDefine(type = ColType.MYSQL_JSON)
private List<JSONObject> yearActivitys;
@Column
@Comment("财务收支情况统计json")
@ColDefine(type = ColType.MYSQL_JSON)
@@ -24,6 +24,10 @@ public class ClubUserPageForm extends PageForm {
private Integer auditType;
private Integer source;
private Integer year;
/** 社团评优申报开始年度筛选条件。 */
private String applyStartYear;
/** 社团评优申报结束年度筛选条件。 */
private String applyEndYear;
private Boolean giveMoney;
private Integer radioType;
}
@@ -71,7 +71,9 @@ public class SysClubEvaluateServiceImpl extends BaseServiceImpl<SysClubEvaluate>
Cnd cnd = Cnd.NEW();
cnd.andEX("info.clubId", "=", pageForm.getClubId());
cnd.andEX("year(info.applyTime)", "=", pageForm.getYear());
// 评优年度以申报填写的起止年度为准,不能使用申请提交时间所在年份代替。
cnd.andEX("info.applyStartYear", "=", pageForm.getApplyStartYear());
cnd.andEX("info.applyEndYear", "=", pageForm.getApplyEndYear());
cnd.and("info.userId", "=", SecurityUtil.getUserId());
cnd.groupBy("info.id");
@@ -144,7 +146,9 @@ public class SysClubEvaluateServiceImpl extends BaseServiceImpl<SysClubEvaluate>
""");
cnd.andEX("ce.clubId", "=", pageForm.getClubId());
cnd.andEX("year(ce.applyTime)", "=", pageForm.getYear());
// 审核列表与参考项目保持一致,分别按申报开始年度和申报结束年度筛选。
cnd.andEX("ce.applyStartYear", "=", pageForm.getApplyStartYear());
cnd.andEX("ce.applyEndYear", "=", pageForm.getApplyEndYear());
if (pageForm.getAudit()) {
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
@@ -8,6 +8,7 @@ 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.base.utils.PageUtil;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
@@ -33,8 +34,10 @@ import org.nutz.lang.util.NutMap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@IocBean(args = {"refer:dao"})
@@ -119,6 +122,7 @@ public class SysClubExamineServiceImpl extends BaseServiceImpl<SysClubExamineReg
Sql applySql = Sqls.create("""
SELECT
cu.userId,
cu.id,
cu.mode,
cu.applyDate,
cu.joinTime,
@@ -129,17 +133,25 @@ public class SysClubExamineServiceImpl extends BaseServiceImpl<SysClubExamineReg
LEFT JOIN sys_user u ON u.id = cu.userId
WHERE cu.clubId = @clubId
AND COALESCE(cu.delFlag, 0) = 0
AND EXISTS (
SELECT 1
FROM wf_process_instance ins
WHERE ins.businessNo = cu.id
AND ins.state = @finishedState
)
ORDER BY cu.applyDate, cu.mode
""")
.setParam("clubId", clubId)
.setParam("finishedState", ProcessInstanceStateEnum.FINISHED.getCode());
.setParam("clubId", clubId);
List<NutMap> applications = listMap(applySql);
if (!applications.isEmpty()) {
// 流程状态改为按当前社团申请批量查询,避免相关子查询对流程实例表重复扫描。
List<String> applicationIds = applications.stream()
.map(application -> application.getString("id"))
.filter(StrUtil::isNotBlank)
.collect(Collectors.toList());
List<ProcessInstance> finishedInstances = applicationIds.isEmpty() ? new ArrayList<>()
: dao().query(ProcessInstance.class, Cnd.where(ProcessInstance::getBusinessNo, "in", applicationIds)
.and(ProcessInstance::getState, "=", ProcessInstanceStateEnum.FINISHED.getCode()));
Set<String> finishedBusinessNos = finishedInstances.stream()
.map(ProcessInstance::getBusinessNo)
.filter(StrUtil::isNotBlank)
.collect(Collectors.toCollection(HashSet::new));
applications.removeIf(application -> !finishedBusinessNos.contains(application.getString("id")));
}
applications.sort(Comparator.comparing(row -> row.getTime("applyDate"), Comparator.nullsLast(Date::compareTo)));
for (NutMap application : applications) {
@@ -259,6 +271,10 @@ public class SysClubExamineServiceImpl extends BaseServiceImpl<SysClubExamineReg
}
private String normalizeUserState(String userState) {
// 历史成员数据可能未同步人员状态,空值不参与在职、退休人数统计。
if (StrUtil.isBlank(userState)) {
return null;
}
if (List.of("在职", "在岗").contains(userState)) {
return "在职";
}
@@ -476,6 +492,8 @@ public class SysClubExamineServiceImpl extends BaseServiceImpl<SysClubExamineReg
// 查询主表数据并关联明细数据
ClubExamineVo clubExamineVo = fetchVO(sql, ClubExamineVo.class);
if (Lang.isNotEmpty(clubExamineVo)) {
// 详情页统一使用V4字段,兼容尚未改写JSON内容的V3年审记录。
normalizeActivityData(clubExamineVo);
List<SysClubExamineRegisterDetailed> detailedList = dao().query(SysClubExamineRegisterDetailed.class,
Cnd.where("registerId", "=", id).asc("location"));
clubExamineVo.setDetailedList(detailedList);
@@ -512,6 +530,116 @@ public class SysClubExamineServiceImpl extends BaseServiceImpl<SysClubExamineReg
return clubExamineVo;
}
/**
* 将旧版年审活动和计划字段转换为查看页统一使用的V4字段,避免历史数据在页面中显示为空。
*
* @param examineVo 年审详情
*/
private void normalizeActivityData(ClubExamineVo examineVo) {
examineVo.setSummaryFiles(normalizeSummaryFiles(examineVo.getSummaryFiles()));
examineVo.setChangeUserNum(normalizeChangeUserNum(examineVo.getChangeUserNum()));
List<JSONObject> yearActivitySource = examineVo.getYearActivityList();
if (yearActivitySource == null || yearActivitySource.isEmpty()) {
yearActivitySource = examineVo.getYearActivitys();
}
examineVo.setYearActivityList(yearActivitySource == null ? new ArrayList<>()
: yearActivitySource.stream()
.map(item -> normalizeActivityItem(item, true))
.collect(Collectors.toList()));
List<JSONObject> planSource = examineVo.getPlans();
examineVo.setPlans(planSource == null ? new ArrayList<>()
: planSource.stream()
.map(item -> normalizeActivityItem(item, false))
.collect(Collectors.toList()));
}
/**
* 将V3年审记录中的年度减少字段转换为查看页统一使用的V4字段。
* 历史年审必须展示申请时保存的数据,不能按当前会员状态重新统计。
*
* @param changeUserNum 年审记录保存的成员变化数据
* @return 兼容V3、V4字段的成员变化数据
*/
private List<JSONObject> normalizeChangeUserNum(List<JSONObject> changeUserNum) {
if (changeUserNum == null || changeUserNum.isEmpty()) {
return new ArrayList<>();
}
return changeUserNum.stream()
.filter(Objects::nonNull)
.peek(item -> {
if (!item.containsKey("yearReduceNum") && item.containsKey("yearEditNum")) {
item.set("yearReduceNum", item.get("yearEditNum"));
}
})
.collect(Collectors.toList());
}
/**
* 将V3工作总结附件的文件主键包装为V4文件预览组件所需的response.data格式。
*
* @param summaryFiles 原始工作总结附件
* @return 可供文件预览组件读取的附件数据
*/
private List<JSONObject> normalizeSummaryFiles(List<JSONObject> summaryFiles) {
if (summaryFiles == null || summaryFiles.isEmpty()) {
return new ArrayList<>();
}
return summaryFiles.stream()
.filter(Objects::nonNull)
.map(file -> {
JSONObject response = file.getJSONObject("response");
if (response != null && StrUtil.isNotBlank(response.getStr("data"))) {
return file;
}
String fileId = file.getStr("id");
if (StrUtil.isBlank(fileId)) {
return null;
}
JSONObject normalizedFile = new JSONObject();
normalizedFile.set("response", new JSONObject().set("data", fileId));
return normalizedFile;
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
/**
* 兼容V3活动字段(hdsj、mc、zbdw、hddd、cjrs、hjqk)和V4活动字段。
*
* @param source 原始活动或计划数据
* @param includeResult 是否包含本年度活动的参加人数和获奖情况
* @return 供查看页使用的统一字段数据
*/
private JSONObject normalizeActivityItem(JSONObject source, boolean includeResult) {
JSONObject item = new JSONObject();
item.set("activityName", getActivityValue(source, "activityName", "mc"));
item.set("activityUnit", getActivityValue(source, "activityUnit", "zbdw"));
item.set("activityAddress", getActivityValue(source, "activityAddress", "hddd"));
if (includeResult) {
item.set("activityTime", getActivityValue(source, "activityTime", "hdsj"));
item.set("joinNum", getActivityValue(source, "joinNum", "cjrs"));
item.set("prize", getActivityValue(source, "prize", "hjqk"));
}
return item;
}
/**
* 优先读取V4字段;历史记录没有该字段时回退读取对应的V3字段。
*
* @param source 原始JSON数据
* @param currentField V4字段名
* @param legacyField V3字段名
* @return 兼容后的字段值
*/
private String getActivityValue(JSONObject source, String currentField, String legacyField) {
if (source == null) {
return null;
}
String currentValue = source.getStr(currentField);
return StrUtil.isNotBlank(currentValue) ? currentValue : source.getStr(legacyField);
}
@Override
public List<NutMap> getYearActivityExportData(String id) {
ClubExamineVo examineVo = this.findOne(id);
@@ -619,7 +747,8 @@ public class SysClubExamineServiceImpl extends BaseServiceImpl<SysClubExamineReg
*/
private void buildPageBaseCondition(Cnd cnd, ClubUserPageForm pageForm, String tableAlias) {
if (pageForm.getYear() != null) {
cnd.andEX("YEAR(" + tableAlias + ".registerDate)", "=", pageForm.getYear());
// 年度查询应与列表展示的登记年度保持一致,不能使用申请时间推算年度。
cnd.andEX(tableAlias + ".year", "=", pageForm.getYear());
}
if (StrUtil.isNotBlank(pageForm.getClubId())) {
cnd.andEX(tableAlias + ".clubId", "=", pageForm.getClubId());
@@ -17,6 +17,10 @@ public class ClubEvaluatePageVo {
private String foundTime;
private String typeName;
private Date applyTime;
/** 社团评优申报开始年度。 */
private String applyStartYear;
/** 社团评优申报结束年度。 */
private String applyEndYear;
private String instanceId;
private String businessNo;
@@ -11,6 +11,7 @@ import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.nutz.dao.Cnd;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
@@ -43,22 +44,26 @@ public class ArticleAnalysisController {
@At
@SaCheckPermission("article.analysis")
@ApiOperation(value = "统计分析")
public Result overview() {
public Result overview(Date startDate, Date endDate, String unionId, String clubId) {
List<ArticleAnalysisOverviewVO> list = new ArrayList<>();
// 获取总投稿量
list.add(ArticleAnalysisOverviewVO.builder()
.label("总投稿量")
.value(articleService.count())
.value(articleService.count(buildStatisticsCondition(startDate, endDate, unionId, clubId)))
.icon("el-icon-document")
.bgColor("rgba(24, 103, 176, 0.1)")
.trend(null)
.build());
// 获取本月投稿量
long monthlyCount = articleService.count(Cnd.where("MONTH(submitTime)", "=", DateUtil.thisMonth() + 1));
Cnd monthlyCondition = buildStatisticsCondition(startDate, endDate, unionId, clubId);
monthlyCondition.and("MONTH(submitTime)", "=", DateUtil.thisMonth() + 1);
long monthlyCount = articleService.count(monthlyCondition);
//上月投稿量
long lastMonthCount = articleService.count(Cnd.where("MONTH(submitTime)", "=", DateUtil.offsetMonth(new Date(), -1)));
Cnd lastMonthCondition = buildStatisticsCondition(startDate, endDate, unionId, clubId);
lastMonthCondition.and("MONTH(submitTime)", "=", DateUtil.month(DateUtil.offsetMonth(new Date(), -1)) + 1);
long lastMonthCount = articleService.count(lastMonthCondition);
list.add(ArticleAnalysisOverviewVO.builder()
.label("本月投稿")
@@ -70,7 +75,9 @@ public class ArticleAnalysisController {
// 获取活跃投稿人数
DateTime dateTime = DateUtil.offsetMonth(new Date(), -1);
long activeUsers = Math.max(articleService.count(Cnd.where("submitTime", ">=", dateTime).groupBy("userId")), 0L);
Cnd activeUserCondition = buildStatisticsCondition(startDate, endDate, unionId, clubId);
activeUserCondition.and("submitTime", ">=", dateTime).groupBy("userId");
long activeUsers = Math.max(articleService.count(activeUserCondition), 0L);
list.add(ArticleAnalysisOverviewVO.builder()
.label("活跃投稿人")
.value(activeUsers)
@@ -81,7 +88,11 @@ public class ArticleAnalysisController {
// 获取参与分工会数量
long unionCount = articleService.count(Sqls.create("SELECT COUNT(distinct unionId) FROM article WHERE unionId IS NOT NULL"));
Sql unionCountSql = Sqls.create("SELECT COUNT(distinct unionId) FROM article $condition");
Cnd unionCountCondition = buildStatisticsCondition(startDate, endDate, unionId, clubId);
unionCountCondition.and("unionId", "IS NOT", null);
unionCountSql.setCondition(unionCountCondition);
long unionCount = articleService.count(unionCountSql);
list.add(ArticleAnalysisOverviewVO.builder()
.label("参与分工会")
.value(unionCount)
@@ -91,7 +102,11 @@ public class ArticleAnalysisController {
.build());
// 获取参与社团数量
long clubCount = articleService.count(Sqls.create("SELECT COUNT(distinct clubId) FROM article WHERE clubId IS NOT NULL"));
Sql clubCountSql = Sqls.create("SELECT COUNT(distinct clubId) FROM article $condition");
Cnd clubCountCondition = buildStatisticsCondition(startDate, endDate, unionId, clubId);
clubCountCondition.and("clubId", "IS NOT", null);
clubCountSql.setCondition(clubCountCondition);
long clubCount = articleService.count(clubCountSql);
list.add(ArticleAnalysisOverviewVO.builder()
.label("参与社团")
.value(clubCount)
@@ -102,22 +117,43 @@ public class ArticleAnalysisController {
return Result.success(list);
}
/**
* 构建概览统计的筛选条件,与图表接口保持相同的时间范围、分工会和社团口径。
*/
private Cnd buildStatisticsCondition(Date startDate, Date endDate, String unionId, String clubId) {
Cnd cnd = Cnd.NEW();
if (startDate != null && endDate != null) {
cnd.and(Cnd.cri().where().andBetween("submitTime", startDate, endDate));
}
cnd.andEX("unionId", "=", unionId);
cnd.andEX("clubId", "=", clubId);
return cnd;
}
@At
@SaCheckPermission("article.analysis")
@ApiOperation(value = "投稿趋势")
public Result trend(@Valid String type, Date startDate, Date endDate) {
List<NutMap> submissionTrend = articleAnalysisService.getSubmissionTrend(type, startDate, endDate);
public Result trend(@Valid String type, Date startDate, Date endDate, String unionId, String clubId) {
List<NutMap> submissionTrend = articleAnalysisService.getSubmissionTrend(type, startDate, endDate, unionId, clubId);
return Result.success(submissionTrend);
}
@At
@SaCheckPermission("article.analysis")
@ApiOperation(value = "分工会投稿分布")
public Result distribution(Date startDate, Date endDate) {
List<NutMap> unionDistribution = articleAnalysisService.getUnionDistribution(startDate, endDate);
public Result distribution(Date startDate, Date endDate, String unionId, String clubId) {
List<NutMap> unionDistribution = articleAnalysisService.getUnionDistribution(startDate, endDate, unionId, clubId);
return Result.success(unionDistribution);
}
@At
@SaCheckPermission("article.analysis")
@ApiOperation(value = "社团投稿分布")
public Result clubDistribution(Date startDate, Date endDate, String unionId, String clubId) {
List<NutMap> clubDistribution = articleAnalysisService.getClubDistribution(startDate, endDate, unionId, clubId);
return Result.success(clubDistribution);
}
@At
@SaCheckPermission("article.analysis")
@ApiOperation(value = "投稿排名")
@@ -52,6 +52,7 @@ public class ArticleMineController {
@SaCheckPermission("article.mine")
@ApiOperation("分页查询")
public Result pageData(@Valid ArticleInfoPageParam pageForm) {
// 每篇投稿仅关联最新的一条待办任务,避免多办理人或并行待办将同一投稿重复展示。
Sql sql = Sqls.create("""
SELECT
info.id,
@@ -79,8 +80,11 @@ public class ArticleMineController {
FROM
article info
LEFT JOIN `wf_process_instance` ins ON ins.businessNo = info.id
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
LEFT JOIN wf_process_task t ON t.id = (
SELECT MAX(currentTask.id)
FROM wf_process_task currentTask
WHERE currentTask.processInstanceId = ins.id AND currentTask.taskState = 10
)
$condition
""");
Cnd cnd = Cnd.NEW();
@@ -13,12 +13,17 @@ public interface ArticleAnalysisService extends BaseService<Article> {
/**
* 获取投稿趋势数据
*/
List<NutMap> getSubmissionTrend(String type, Date startDate, Date endDate);
List<NutMap> getSubmissionTrend(String type, Date startDate, Date endDate, String unionId, String clubId);
/**
* 获取分工会投稿分布
*/
List<NutMap> getUnionDistribution(Date startDate, Date endDate);
List<NutMap> getUnionDistribution(Date startDate, Date endDate, String unionId, String clubId);
/**
* 获取社团投稿分布
*/
List<NutMap> getClubDistribution(Date startDate, Date endDate, String unionId, String clubId);
/**
* 获取排行榜数据
@@ -22,7 +22,7 @@ public class ArticleAnalysisServiceImpl extends BaseServiceImpl<Article> impleme
}
@Override
public List<NutMap> getSubmissionTrend(String type, Date startDate, Date endDate) {
public List<NutMap> getSubmissionTrend(String type, Date startDate, Date endDate, String unionId, String clubId) {
String groupFormat = switch (type) {
case "week" -> "%Y-%u"; // 按周分组
case "month" -> "%Y-%m"; // 按月分组
@@ -40,10 +40,8 @@ public class ArticleAnalysisServiceImpl extends BaseServiceImpl<Article> impleme
""");
sql.setParam("groupFormat",groupFormat);
Cnd cnd = Cnd.NEW();
if(ObjectUtil.isAllNotEmpty(startDate,endDate)){
cnd.and(Cnd.cri().where().andBetween("submitTime", startDate, endDate));
}
Cnd cnd = buildStatisticsCondition(startDate, endDate, unionId, clubId);
cnd.groupBy("DATE_FORMAT(submitTime, @groupFormat)");
sql.setCondition(cnd);
return listMap(sql);
@@ -53,7 +51,7 @@ public class ArticleAnalysisServiceImpl extends BaseServiceImpl<Article> impleme
* 获取分工会投稿分布
*/
@Override
public List<NutMap> getUnionDistribution(Date startDate, Date endDate) {
public List<NutMap> getUnionDistribution(Date startDate, Date endDate, String unionId, String clubId) {
Sql sql = Sqls.create("""
SELECT
unionName AS type,
@@ -62,10 +60,7 @@ public class ArticleAnalysisServiceImpl extends BaseServiceImpl<Article> impleme
article
$condition
""");
Cnd cnd = Cnd.NEW();
if(ObjectUtil.isAllNotEmpty(startDate,endDate)){
cnd.and(Cnd.cri().where().andBetween("submitTime", startDate, endDate));
}
Cnd cnd = buildStatisticsCondition(startDate, endDate, unionId, clubId);
cnd.groupBy("unionId","unionName");
cnd.desc("value");
sql.setCondition(cnd);
@@ -73,6 +68,47 @@ public class ArticleAnalysisServiceImpl extends BaseServiceImpl<Article> impleme
return listMap(sql);
}
/**
* 获取社团投稿分布
*/
@Override
public List<NutMap> getClubDistribution(Date startDate, Date endDate, String unionId, String clubId) {
Sql sql = Sqls.create("""
SELECT
clubName AS type,
COUNT(*) AS value
FROM
article
$condition
""");
Cnd cnd = buildStatisticsCondition(startDate, endDate, unionId, clubId);
cnd.and("clubId", "IS NOT", null);
cnd.groupBy("clubId", "clubName");
cnd.desc("value");
sql.setCondition(cnd);
return listMap(sql);
}
/**
* 为统计图表统一拼接时间范围和分工会筛选条件,保证各图表的查询口径一致。
*
* @param startDate 查询开始日期,为空时不限制开始时间
* @param endDate 查询结束日期,为空时不限制结束时间
* @param unionId 分工会 ID,为空时查询全部分工会
* @param clubId 社团 ID,为空时查询全部社团
* @return 可直接设置到统计 SQL 的查询条件
*/
private Cnd buildStatisticsCondition(Date startDate, Date endDate, String unionId, String clubId) {
Cnd cnd = Cnd.NEW();
if (ObjectUtil.isAllNotEmpty(startDate, endDate)) {
cnd.and(Cnd.cri().where().andBetween("submitTime", startDate, endDate));
}
cnd.andEX("unionId", "=", unionId);
cnd.andEX("clubId", "=", clubId);
return cnd;
}
/**
* 获取排行榜数据
*/
@@ -9,9 +9,11 @@ import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.asset.model.Asset;
import com.budwk.app.zhgh.dayofficework.asset.model.AssetDepreciationRecord;
@@ -27,6 +29,7 @@ import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
@@ -157,9 +160,16 @@ public class AssetManageController {
@SaCheckPermission(value = {"asset.manage", "asset.stocktaking", "h5.asset.stocktaking"}, mode = SaMode.OR)
public Result searchAssetUseUser(String query) {
Cnd cnd = Cnd.NEW();
cnd.where().orLike("username", query);
cnd.where().orLike("loginname", query);
cnd.where().orLike("id", query);
// 三个关键词条件必须整体分组,避免 OR 条件绕过分工会数据范围。
SqlExpressionGroup searchGroup = new SqlExpressionGroup();
searchGroup.orLike("username", query);
searchGroup.orLike("loginname", query);
searchGroup.orLike("id", query);
cnd.and(searchGroup);
// 分工会侧盘点人员只能选择本分工会成员,校工会和系统管理员保留全校查询范围。
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
cnd.and("unionId", "=", SecurityUtil.getUnionId());
}
Sql sql = Sqls.create("""
SELECT
id,
@@ -1,12 +1,8 @@
package com.budwk.app.zhgh.dayofficework.birthdayWishes.task;
import cn.hutool.core.date.DateUtil;
import com.budwk.app.sys.models.Sys_msg;
import com.budwk.app.sys.services.SysMsgService;
import com.budwk.app.zhgh.staffmanage.birthday.service.UserBirthdayService;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
@@ -15,16 +11,12 @@ import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.util.List;
@IocBean
@Slf4j
public class BirthdayWishesTask implements Job {
@Inject
protected SysMsgService sysMsgService;
@Inject
protected Dao dao;
private UserBirthdayService userBirthdayService;
@Override
@@ -34,28 +26,12 @@ public class BirthdayWishesTask implements Job {
log.info("=================================参数:{}", Json.toJson(dataMap));
String planName = dataMap.getString("name");
String today = DateUtil.today();
String title = planName + today;
String content = dataMap.getString("template");
Sys_msg msg = new Sys_msg();
msg.setTitle(planName + today);
msg.setType("user");
msg.setSendType("show");
msg.setSendAt(DateUtil.current());
msg.setNote(dataMap.getString("template"));
// 查询今天生日的会员用户
Sql sql = Sqls.create("SELECT loginname FROM sys_user WHERE member = 1 AND MONTH(birthday) = MONTH(CURRENT_DATE) AND DAY(birthday) = DAY(CURRENT_DATE)");
sql.setCallback(Sqls.callback.strList());
dao.execute(sql);
List<String> users = sql.getList(String.class);
// 转为数组
String[] userArray = users.toArray(String[]::new);
log.info("=================================查询今天生日会员用户:{}", users);
if(userArray.length > 0){
log.info("=================================发送消息:{}", msg);
sysMsgService.saveMsg(msg, userArray, true);
}
// 统一由生日服务查询当天生日会员、生成移动端链接并记录系统推送日志。
int receiverCount = userBirthdayService.sendTodayBirthdayMessages(title, content);
log.info("=================================当天生日通知进入发送队列人数:{}", receiverCount);
}
}
@@ -58,6 +58,10 @@ public class BuildHomeKpiWriteDynamicTableRenderPolicy extends DynamicTableRende
// 处理合并
if(ObjectUtil.isNotEmpty(dataList)){
for (int[] value : groupRanges.values()) {
// 单条明细无需合并;POI 不支持对同一行进行纵向合并。
if (value[0] == value[1]) {
continue;
}
TableTools.mergeCellsVertically(xwpfTable, 0, value[0] + 1, value[1] + 1);
TableTools.mergeCellsVertically(xwpfTable, 1, value[0] + 1, value[1] + 1);
TableTools.mergeCellsVertically(xwpfTable, 4, value[0] + 1, value[1] + 1);
@@ -94,7 +94,7 @@ public class BuildHomeKpiWriteController {
buildHomeKpiWriteService.exportDocx(id, os);
// 下载生成的文件
CommonDownloadUtil.download(
"南京邮电大学分工会教职工之家建设考核评议自评表.docx",
"中国地质大学分工会教职工之家建设考核评议自评表.docx",
os.toByteArray(),
response
);
@@ -136,7 +136,7 @@ public class BuildHomeLogBookController {
template.render(map).writeAndClose(os);
// 下载生成的文件
CommonDownloadUtil.download(
"南京邮电大学分工会建家记录册.docx",
"中国地质大学分工会建家记录册.docx",
os.toByteArray(),
response
);
@@ -2,6 +2,7 @@ package com.budwk.app.zhgh.dayofficework.buildHome.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
@@ -104,8 +105,13 @@ public class BuildHomeMaterialQueryController {
}
@At
@Ok("void")
@SaCheckPermission("buildHome.material.query")
public void exportZip(String actId, HttpServletResponse response) throws IOException {
BuildHomeAct act = buildHomeActService.fetch(actId);
if (act == null) {
throw new IllegalArgumentException("未找到评比事项");
}
Sql sql = Sqls.create("""
SELECT
t1.id,
@@ -123,7 +129,6 @@ public class BuildHomeMaterialQueryController {
AND t2.actId = @actId
LEFT JOIN build_home_act t3 ON t3.id = t2.actId
AND t3.id = @actId
WHERE t1.id = '3742a919fb6847f7a8dbe051a2ef0025'
GROUP BY
t1.id,
t2.unionId
@@ -135,7 +140,9 @@ public class BuildHomeMaterialQueryController {
// 2. 设置响应头
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment; filename=\"工会文件汇总.zip\"");
// 使用评比事项名称作为压缩包名称,避免下载文件无法区分来源。
String zipFileName = sanitizeFileName(act.getName()) + "汇总材料.zip";
response.setHeader("Content-Disposition", "attachment; filename=" + URLUtil.encode(zipFileName));
// 3. 使用try-with-resources创建ZIP输出流
try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
@@ -164,6 +171,18 @@ public class BuildHomeMaterialQueryController {
}
/**
* 清理评比事项名称中的 Windows 文件名非法字符,确保浏览器可正常保存压缩包。
*
* @param fileName 原始评比事项名称
* @return 可用于下载文件名的评比事项名称
*/
private String sanitizeFileName(String fileName) {
String sanitizedFileName = StrUtil.blankToDefault(fileName, "评比事项");
return sanitizedFileName.replaceAll("[\\\\/:*?\"<>|]", "_");
}
private void processFileList(ZipOutputStream zos, String unionName, String fileType, String filesJson) throws IOException {
if (StrUtil.isNotBlank(filesJson)) {
List<NutMap> files = Json.fromJsonAsList(NutMap.class, filesJson);
@@ -110,7 +110,9 @@ public class CadreTrainingMineController {
cnd.where().andLike(CadreTrainingAct::getName, pageForm.getSearchKeyword());
}
cnd.desc("info.applyTime");
cnd.and("info.userId", "=", SecurityUtil.getUserId());
// 本人报名和本人代他人报名的记录都应在“我的报名”中展示。
cnd.and(Cnd.exps("info.userId", "=", SecurityUtil.getUserId())
.or("info.applyUserId", "=", SecurityUtil.getUserId()));
sql.setCondition(cnd);
Pagination<NutMap> pagination = cadreTrainingSignUpService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -67,8 +67,9 @@ public class CadreTrainingSignUpController {
@SaCheckPermission("cadreTraining.branchUnionSignUp")
@ApiOperation("分页查询")
public Result pageData(@Valid PageForm pageForm,Integer year, boolean isEnrolled) {
// 同一活动存在多条代报名记录时,活动列表仅展示一次。
Sql sql = Sqls.create("""
SELECT
SELECT DISTINCT
info.*,
CASE WHEN cts.id IS NOT NULL THEN 1 ELSE 0 END AS isEnrolled
FROM
@@ -101,6 +102,9 @@ public class CadreTrainingSignUpController {
List<ProcessInstance> instances = new ArrayList<>();
for (CadreTrainingSignUp signUp : cadreTrainingSignUps) {
// 代报名记录必须以当前登录人为报名操作人,供“已报名”和“我的报名”按操作人查询。
signUp.setApplyUserId(SecurityUtil.getUserId());
signUp.setApplyUserUserName(SecurityUtil.getUserUsername());
if (StrUtil.isBlank(signUp.getId())) {
signUp.setApplyTime(new Date());
}
@@ -79,21 +79,27 @@ public class HonorSummaryController {
@At
@SaCheckPermission("honor.honorLevel.Summary")
public Result honorLevelData(HonorPageForm pageForm) {
List<HonorBasicSettings> query = dao.query(HonorBasicSettings.class, Cnd.where("queryTypeCode", "in",
List.of(HonorTypeOrigin.HONOR_SCHOOL_LEVEL.name(), HonorTypeOrigin.HONOR_PROVINCIAL_LEVEL.name(), HonorTypeOrigin.HONOR_NATIONAL_LEVEL.name())));
String schoolHonorId = query.stream().filter(v->v.getQueryTypeCode().equals(HonorTypeOrigin.HONOR_SCHOOL_LEVEL.name())).findFirst().orElse(new HonorBasicSettings()).getId();
String provincialHonorId = query.stream().filter(v->v.getQueryTypeCode().equals(HonorTypeOrigin.HONOR_PROVINCIAL_LEVEL.name())).findFirst().orElse(new HonorBasicSettings()).getId();
String nationalHonorId = query.stream().filter(v->v.getQueryTypeCode().equals(HonorTypeOrigin.HONOR_NATIONAL_LEVEL.name())).findFirst().orElse(new HonorBasicSettings()).getId();
HonorBasicSettings levelRoot = dao.fetch(HonorBasicSettings.class,
Cnd.where("queryTypeCode", "=", HonorTypeOrigin.HONOR_LEVEL.name()));
List<HonorBasicSettings> levelList = levelRoot == null ? List.of() : dao.query(HonorBasicSettings.class,
Cnd.where("parentId", "=", levelRoot.getId()));
String schoolHonorId = levelList.stream().filter(item -> "校内".equals(item.getName()))
.findFirst().orElse(new HonorBasicSettings()).getId();
String provincialHonorId = levelList.stream().filter(item -> "省部级".equals(item.getName()))
.findFirst().orElse(new HonorBasicSettings()).getId();
String nationalHonorId = levelList.stream().filter(item -> "国家级".equals(item.getName()))
.findFirst().orElse(new HonorBasicSettings()).getId();
Cnd cnd = Cnd.NEW();
// honor.honorLevel 直接保存荣誉等级项 ID,按“荣誉等级”下的具体等级项统计。
// 历史荣誉记录可能未保存 unionId,统计时仅在 unionId 为空时按 unionName 回退关联。
Sql sql = Sqls.create("""
SELECT
id,
name as unionname,
( SELECT COUNT( 1 ) FROM honor h WHERE h.honorLevel = @nationalLevel AND h.unionId = un.id $cnd) nationalLevelNum,
( SELECT COUNT( 1 ) FROM honor h WHERE h.honorLevel = @provincial AND h.unionId = un.id $cnd) provincialNum ,
( SELECT COUNT( 1 ) FROM honor h WHERE h.honorLevel = @schoolLevel AND h.unionId = un.id $cnd) schoolLevelNum
( SELECT COUNT( 1 ) FROM honor h WHERE h.honorLevel = @nationalLevel AND (h.unionId = un.id OR ((h.unionId IS NULL OR h.unionId = '') AND h.unionName = un.name)) $cnd) nationalLevelNum,
( SELECT COUNT( 1 ) FROM honor h WHERE h.honorLevel = @provincial AND (h.unionId = un.id OR ((h.unionId IS NULL OR h.unionId = '') AND h.unionName = un.name)) $cnd) provincialNum ,
( SELECT COUNT( 1 ) FROM honor h WHERE h.honorLevel = @schoolLevel AND (h.unionId = un.id OR ((h.unionId IS NULL OR h.unionId = '') AND h.unionName = un.name)) $cnd) schoolLevelNum
FROM
sys_union un $condition
""").setParam("nationalLevel", nationalHonorId).setParam("provincial", provincialHonorId).setParam("schoolLevel", schoolHonorId);
@@ -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() + "%");
@@ -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));
}
@@ -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);
}
@@ -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();
@@ -0,0 +1,31 @@
package com.budwk.app.zhgh.dayofficework.outlay.outlayManage.service;
import com.budwk.app.base.service.BaseService;
import org.nutz.lang.util.NutMap;
/**
* 分工会预算统计服务
*
* <p>统一封装分工会预算使用情况和经费收支情况的全量汇总查询
* 汇总口径与列表查询保持一致不受分页参数影响</p>
*/
public interface OutlayManageUnionStatisticsService extends BaseService {
/**
* 查询分工会预算使用情况的全量合计
*
* @param year 统计年度
* @param unionId 分工会主键管理员可为空查询全部
* @return 包含 totalQuotausedQuotasurplusQuota 的合计数据
*/
NutMap queryUseDetailSummary(Integer year, String unionId);
/**
* 查询分工会经费收支使用情况的全量合计
*
* @param year 统计年度
* @param unionId 分工会主键管理员可为空查询全部
* @return 包含各项收入支出和余额字段的合计数据
*/
NutMap queryIncomeExpenseSummary(Integer year, String unionId);
}
@@ -0,0 +1,104 @@
package com.budwk.app.zhgh.dayofficework.outlay.outlayManage.service.impl;
import cn.hutool.core.date.DateUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.service.OutlayManageUnionStatisticsService;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
/**
* 分工会预算统计服务实现
*
* <p>汇总查询在数据库端完成避免只汇总当前分页数据
* 同时复用列表的年度分工会和权限过滤条件</p>
*/
@IocBean(args = {"refer:dao"})
public class OutlayManageUnionStatisticsServiceImpl extends BaseServiceImpl implements OutlayManageUnionStatisticsService {
public OutlayManageUnionStatisticsServiceImpl(Dao dao) {
super(dao);
}
@Override
public NutMap queryUseDetailSummary(Integer year, String unionId) {
Cnd cnd = buildUnionManageCondition(year, unionId, false);
Sql sql = Sqls.create("""
SELECT
COALESCE(SUM(totalQuota), 0) AS totalQuota,
COALESCE(SUM(usedQuota), 0) AS usedQuota,
COALESCE(SUM(COALESCE(totalQuota, 0) - COALESCE(usedQuota, 0)), 0) AS surplusQuota
FROM outlay_manage_union mu $condition
""");
sql.setCondition(cnd);
return executeMap(sql);
}
@Override
public NutMap queryIncomeExpenseSummary(Integer year, String unionId) {
Cnd cnd = buildUnionManageCondition(year, unionId, true);
Sql sql = Sqls.create("""
SELECT
COALESCE(SUM(t.beginMoney), 0) AS beginMoney,
COALESCE(SUM(t.feeJanApr), 0) AS feeJanApr,
COALESCE(SUM(t.feeMayAug), 0) AS feeMayAug,
COALESCE(SUM(t.feeSepDec), 0) AS feeSepDec,
COALESCE(SUM(t.awardMoney), 0) AS awardMoney,
COALESCE(SUM(t.usedMoney), 0) AS usedMoney,
COALESCE(SUM(t.remainMoney), 0) AS remainMoney
FROM (
SELECT
mu.unionId,
COALESCE(SUM(CASE WHEN au.quarterly = 1 THEN au.allocateHeadMoney ELSE 0 END), 0) AS beginMoney,
COALESCE(SUM(CASE WHEN au.quarterly = 1 THEN au.allocateMoney ELSE 0 END), 0) AS feeJanApr,
COALESCE(SUM(CASE WHEN au.quarterly = 2 THEN au.allocateMoney ELSE 0 END), 0) AS feeMayAug,
COALESCE(SUM(CASE WHEN au.quarterly = 3 THEN au.allocateMoney ELSE 0 END), 0) AS feeSepDec,
COALESCE(SUM(CASE WHEN au.quarterly = 4 THEN au.allocateMoney ELSE 0 END), 0) AS awardMoney,
COALESCE(mu.usedQuota, 0) AS usedMoney,
COALESCE(mu.totalQuota, 0) - COALESCE(mu.usedQuota, 0) AS remainMoney
FROM outlay_manage_union mu
LEFT JOIN outlay_allocate_union au ON au.year = mu.year
AND au.unionId = mu.unionId
AND au.delFlag = 0
LEFT JOIN sys_union su ON su.id = mu.unionId
$condition
GROUP BY mu.year, mu.unionId, mu.usedQuota, mu.totalQuota
) t
""");
sql.setCondition(cnd);
return executeMap(sql);
}
/**
* 构造两个列表共用的预算主表过滤条件保证汇总和分页列表权限口径一致
*/
private Cnd buildUnionManageCondition(Integer year, String unionId, boolean includeDelFlag) {
Cnd cnd = Cnd.NEW();
if (includeDelFlag) {
cnd.and("mu.delFlag", "=", false);
}
cnd.and("mu.year", "=", year == null ? DateUtil.thisYear() : year);
cnd.and("mu.totalQuota", "IS NOT", null);
cnd.andEX("mu.unionId", "=", unionId);
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
cnd.and("mu.unionId", "=", SecurityUtil.getUnionId());
}
return cnd;
}
/**
* 执行单行统计查询并在无数据时返回各字段为零的结果方便前端直接展示
*/
private NutMap executeMap(Sql sql) {
sql.setCallback(Sqls.callback.map());
dao().execute(sql);
NutMap result = (NutMap) sql.getResult();
return result == null ? NutMap.NEW() : result;
}
}
@@ -14,6 +14,7 @@ import com.budwk.app.base.service.BaseService;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.service.OutlayManageUnionStatisticsService;
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.union.vo.OutlayUnionIncomeExpenseVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -44,6 +45,8 @@ public class OutlayManageUnionIncomeExpenseController {
@Inject
private BaseService baseService;
@Inject
private OutlayManageUnionStatisticsService outlayManageUnionStatisticsService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/outlay/outlayManage/union/incomeExpense/index.html")
@@ -60,6 +63,16 @@ public class OutlayManageUnionIncomeExpenseController {
return Result.success(pagination);
}
/**
* 查询当前筛选范围内全部分工会经费收支合计不受分页参数影响
*/
@At
@ApiOperation("查询经费收支合计")
@SaCheckPermission("outlay.outlayManage.union.incomeExpense")
public Result summary(Integer year, String unionId) {
return Result.success(outlayManageUnionStatisticsService.queryIncomeExpenseSummary(year, unionId));
}
@At
@Ok("void")
@ApiOperation("导出")
@@ -12,6 +12,7 @@ import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.model.OutlayUseDetail;
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.service.OutlayUseDetailService;
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.service.OutlayManageUnionStatisticsService;
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.union.model.OutLayAllocateUnion;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -46,6 +47,8 @@ public class OutlayManageUnionUseDetailController {
private BaseService baseService;
@Inject
private OutlayUseDetailService outlayUseDetailService;
@Inject
private OutlayManageUnionStatisticsService outlayManageUnionStatisticsService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/outlay/outlayManage/union/useDetail/index.html")
@@ -81,6 +84,16 @@ public class OutlayManageUnionUseDetailController {
return Result.success(pagination);
}
/**
* 查询当前筛选范围内全部分工会预算使用情况合计不受分页参数影响
*/
@At
@ApiOperation("查询预算使用情况合计")
@SaCheckPermission("outlay.outlayManage.union.useDetail")
public Result summary(Integer year, String unionId) {
return Result.success(outlayManageUnionStatisticsService.queryUseDetailSummary(year, unionId));
}
@At
@ApiOperation("某个分工会预算使用详情")
@@ -85,9 +85,12 @@ public class UnionReimburseCollectController {
@Param(value = "reimburseProject") String reimburseProject) {
Sql sql = Sqls.create("""
SELECT
info.*
info.*,
ins.id AS instanceId,
ins.processDefineId AS instanceProcessDefineId
FROM
union_reimburse info
LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id
$condition
""");
Cnd cnd = Cnd.NEW();
@@ -84,12 +84,26 @@ public class ProposalConfigUnitController {
u2.username AS unitLeader
FROM
proposal_undertake t1
LEFT JOIN sys_user_role sur1 ON sur1.underTakeId = t1.id
AND sur1.roleId = ( SELECT r.id FROM sys_role r WHERE r.CODE = @PROPOSAL_BRANCH_SCHOOL_LEADER )
LEFT JOIN sys_user u1 ON u1.id = sur1.userId
LEFT JOIN sys_user_role sur2 ON sur2.underTakeId = t1.id
AND sur2.roleId = ( SELECT r.id FROM sys_role r WHERE r.CODE = @PROPOSAL_UNIT_LEADER)
LEFT JOIN sys_user u2 ON u2.id = sur2.userId
LEFT JOIN (
SELECT
sur.underTakeId,
GROUP_CONCAT(DISTINCT u.username ORDER BY u.username SEPARATOR '、') AS username,
GROUP_CONCAT(DISTINCT u.loginname ORDER BY u.loginname SEPARATOR ',') AS loginname
FROM sys_user_role sur
LEFT JOIN sys_user u ON u.id = sur.userId
WHERE sur.roleId = (SELECT r.id FROM sys_role r WHERE r.CODE = @PROPOSAL_BRANCH_SCHOOL_LEADER)
GROUP BY sur.underTakeId
) u1 ON u1.underTakeId = t1.id
LEFT JOIN (
SELECT
sur.underTakeId,
GROUP_CONCAT(DISTINCT u.username ORDER BY u.username SEPARATOR '、') AS username,
GROUP_CONCAT(DISTINCT u.loginname ORDER BY u.loginname SEPARATOR ',') AS loginname
FROM sys_user_role sur
LEFT JOIN sys_user u ON u.id = sur.userId
WHERE sur.roleId = (SELECT r.id FROM sys_role r WHERE r.CODE = @PROPOSAL_UNIT_LEADER)
GROUP BY sur.underTakeId
) u2 ON u2.underTakeId = t1.id
$condition
""");
sql.setParam("PROPOSAL_BRANCH_SCHOOL_LEADER", RoleConstant.PROPOSAL_BRANCH_SCHOOL_LEADER.name());
@@ -121,7 +135,9 @@ public class ProposalConfigUnitController {
@SLog(tag = "提案", msg = "承办单位同步系统单位")
public Result syncSysUnit() {
Sql sql = Sqls.create("select code from proposal_undertake");
List<Sys_unit> sysUnits = dao.query(Sys_unit.class, Cnd.where(Sys_unit::getUnitcode, "not in", sql).and(Sys_unit::getUnitTypeCode, "=", 1));
// 三位单位编号即提案业务使用的二级单位不再叠加系统单位层级条件
List<Sys_unit> sysUnits = dao.query(Sys_unit.class, Cnd.where(Sys_unit::getUnitcode, "not in", sql)
.and("CHAR_LENGTH(TRIM(unitcode))", "=", 3));
List<ProposalUndertake> proposalUndertakes = sysUnits.stream().map(unit -> {
ProposalUndertake proposalUndertake = new ProposalUndertake();
proposalUndertake.setId(unit.getId());
@@ -1,6 +1,7 @@
package com.budwk.app.zhgh.democratic.proposal.controller.query;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.param.ExportTableColumns;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.democratic.proposal.param.ProposalQueryComprehensiveParam;
@@ -15,6 +16,7 @@ import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
@@ -51,6 +53,23 @@ public class ProposalQueryCollectProgressController {
return Result.success(pagination);
}
/**
* 按征集进度页面筛选条件及当前可见字段导出 Excel
*
* @param pageForm 页面筛选和排序参数
* @param tableColumns 当前可见字段顺序即导出列顺序
* @param response HTTP 响应返回 XSSF Excel
*/
@At
@Ok("void")
@SaCheckPermission("proposal.query.collectProgress")
@ApiOperation(value = "自定义导出征集进度")
public void exportCustomAsExcel(@Valid @Param("pageForm") ProposalQueryComprehensiveParam pageForm,
@Param("tableColumns") ExportTableColumns[] tableColumns,
HttpServletResponse response) {
proposalCommonService.exportCollectProgressAsExcel(pageForm, tableColumns, response);
}
@At
@ApiOperation(value = "获取征集状态")
@SaCheckPermission("proposal.query.collectProgress")
@@ -1,21 +1,13 @@
package com.budwk.app.zhgh.democratic.proposal.controller.query;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.ExportTableColumns;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.democratic.proposal.controller.vo.ProposalInfoPageVO;
import com.budwk.app.zhgh.democratic.proposal.param.ProposalQueryComprehensiveParam;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Cnd;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
@@ -24,9 +16,6 @@ import org.nutz.mvc.annotation.Param;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.Arrays;
import java.util.Map;
@IocBean
@At("/platform/proposal/query/comprehensive")
@Slf4j
@@ -34,25 +23,6 @@ import java.util.Map;
@Api(tags = "提案综合查询")
public class ProposalQueryComprehensiveController {
/**
* 综合查询表格允许排序的字段映射值为查询中的真实字段或安全别名
*/
private static final Map<String, String> COMPREHENSIVE_ORDER_COLUMNS = Map.ofEntries(
Map.entry("code", "info.code"),
Map.entry("name", "info.name"),
Map.entry("createUserName", "info.createUserName"),
Map.entry("typeName", "typeName"),
Map.entry("delegationName", "delegationName"),
Map.entry("caseFilingResult", "info.caseFilingResult"),
Map.entry("caseFilingType", "info.caseFilingType"),
Map.entry("merge", "merge"),
Map.entry("undertakeUnits", "undertakeUnitsOrder"),
Map.entry("curTaskName", "curTaskName"),
Map.entry("instanceState", "instanceState")
);
@Inject
private BaseService baseService;
@Inject
private ProposalCommonService proposalCommonService;
@@ -66,84 +36,24 @@ public class ProposalQueryComprehensiveController {
@SaCheckPermission("proposal.query.comprehensive")
@ApiOperation(value = "分页列表")
public Result pageData(@Valid @Param("pageForm") ProposalQueryComprehensiveParam pageForm) {
Sql sql = Sqls.create("""
SELECT
info.*,
IF(mer.proposalId IS NOT NULL, 1, 0) AS merge,
type.name AS typeName,
tcs.fullName AS sessionName,
tcd.`name` AS delegationName,
ins.id AS instanceId,
ins.businessNo,
ins.state instanceState,
ins.variable instanceVariable,
ins.processDefineId instanceProcessDefineId,
t.displayName curTaskName,
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 1 THEN pru.unitName END) AS masterUnitName,
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 0 THEN pru.unitName END) AS slaveUnitNames,
GROUP_CONCAT(DISTINCT pru.unitName ORDER BY pru.isMaster DESC, pru.unitName) AS undertakeUnitsOrder
FROM
proposal_info info
LEFT JOIN proposal_type type on type.id = info.typeId
LEFT JOIN proposal_merge mer ON mer.proposalId = info.id
LEFT JOIN teacher_congress_session tcs on tcs.id = info.sessionId
LEFT JOIN teacher_congress_delegation tcd on tcd.id = info.delegationId
LEFT JOIN teacher_congress_delegate tcde on tcde.loginName = info.createUserLoginName
LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10
LEFT JOIN proposal_reply_unit pru ON pru.proposalId = info.id
LEFT JOIN wf_process_task replyTask on replyTask.processInstanceId = ins.id
$condition
""");
// AND replyTask.taskState = 20 AND replyTask.taskName in ('master_reply','slave_reply','opinion_master_reply')
Cnd cnd = Cnd.NEW();
if (ArrayUtil.isNotEmpty(pageForm.getOrigins()) && StrUtil.isNotBlank(pageForm.getCommonKeyword())) {
SqlExpressionGroup seg = new SqlExpressionGroup();
return Result.success(proposalCommonService.queryComprehensive(pageForm));
}
// 案名
if (ArrayUtil.contains(pageForm.getOrigins(), "name")) {
for (String keyword : pageForm.getCommonKeyword().split(" ")) {
seg.orLike("info.name", keyword);
}
}
// 案由
if (ArrayUtil.contains(pageForm.getOrigins(), "brief")) {
for (String keyword : pageForm.getCommonKeyword().split(" ")) {
seg.orLike("info.brief", keyword);
}
}
// 提案人
if (ArrayUtil.contains(pageForm.getOrigins(), "createUserName")) {
for (String keyword : pageForm.getCommonKeyword().split(" ")) {
seg.orLike("info.createUserName", keyword);
seg.orLike("info.createUserLoginName", keyword);
}
}
// 答复内容
if (ArrayUtil.contains(pageForm.getOrigins(), "answer")) {
for (String keyword : pageForm.getCommonKeyword().split(" ")) {
seg.orLike("JSON_EXTRACT(replyTask.variable,'$.tf_opinion')", keyword);
}
}
if (!seg.isEmpty()) {
cnd.and(seg);
}
}
if (StrUtil.isNotBlank(pageForm.getUnderTakeUnitId())) {
cnd.andEX("pru.unitId", "=", pageForm.getUnderTakeUnitId());
}
ProposalQueryComprehensiveParam.buildSearch(cnd, pageForm);
cnd.groupBy("info.id");
// 综合查询包含多表字段及聚合别名只允许白名单字段进入排序条件
proposalCommonService.applySafePageOrder(cnd, pageForm, COMPREHENSIVE_ORDER_COLUMNS);
sql.setCondition(cnd);
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
/**
* 按综合查询页面当前筛选条件和列设置导出 Excel
*
* @param pageForm 页面筛选及排序参数
* @param tableColumns 列设置中当前可见的字段顺序即 Excel 列顺序
* @param response HTTP 响应返回 XSSF 格式的 Excel 文件
*/
@At
@Ok("void")
@SaCheckPermission("proposal.query.comprehensive")
@ApiOperation(value = "自定义导出综合查询列表")
public void exportCustomAsExcel(@Valid @Param("pageForm") ProposalQueryComprehensiveParam pageForm,
@Param("tableColumns") ExportTableColumns[] tableColumns,
HttpServletResponse response) {
proposalCommonService.exportComprehensiveAsExcel(pageForm, tableColumns, response);
}
@At
@@ -1,14 +1,11 @@
package com.budwk.app.zhgh.democratic.proposal.controller.query;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.ExportTableColumns;
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_dict;
import com.budwk.app.sys.services.SysDictService;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import io.swagger.annotations.Api;
@@ -21,10 +18,10 @@ 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 java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.Map;
@IocBean
@@ -34,15 +31,6 @@ import java.util.Map;
@Api(tags = "代表团提案统计")
public class ProposalQueryDelegationController {
/**
* 代表团汇总表固定列的安全排序映射动态立案结果列在查询时追加
*/
private static final Map<String, String> DELEGATION_SUMMARY_ORDER_COLUMNS = Map.of(
"dbtName", "dbt.name",
"TOTAL", "TOTAL",
"caseRate", "caseRateOrder"
);
/**
* 代表团提案明细表允许排序的字段映射
*/
@@ -62,10 +50,6 @@ public class ProposalQueryDelegationController {
@Inject
private ProposalCommonService proposalCommonService;
@Inject
private SysDictService sysDictService;
@At("")
@Ok("beetl:/platform/zhgh/democratic/proposal/query/delegation/index.html")
@SaCheckPermission("proposal.query.delegation")
@@ -77,48 +61,26 @@ public class ProposalQueryDelegationController {
@ApiOperation("代表团提案统计")
@SaCheckPermission("proposal.query.delegation")
public Result pageData(PageForm pageForm, String sessionId) {
// 统计必须明确限定届次空参数不能退化为跨届次汇总避免页面异步初始化时展示错误数据
if (StrUtil.isBlank(sessionId)) {
return Result.success(List.of());
}
List<Sys_dict> dictList = sysDictService.getSubListByCode("PROPOSAL_CASE_FILING_RESULT");
Sql sql = Sqls.create("""
SELECT
dbt.id,
dbt.`name` AS dbtName,
(select count(1) from proposal_info info where info.delegationId = dbt.id and info.sessionId = dbt.sessionId ) as TOTAL,
(select count(1) from proposal_info info where info.delegationId = dbt.id and info.sessionId = dbt.sessionId and info.isSubmit = 1 ) as SUBMIT_COUNT
$resultSql,
(select count(1) from proposal_info info where info.delegationId = dbt.id and info.sessionId = dbt.sessionId and info.caseFilingResult = @confirmFiling)
/ NULLIF((select count(1) from proposal_info info where info.delegationId = dbt.id and info.sessionId = dbt.sessionId), 0) AS caseRateOrder
FROM
teacher_congress_delegation dbt
$condition
""");
Map<String, String> summaryOrderColumns = new HashMap<>(DELEGATION_SUMMARY_ORDER_COLUMNS);
List<String> resultSqlParts = new ArrayList<>();
for (Sys_dict dict : dictList) {
String resultCode = dict.getCode();
// 动态别名和立案结果编码仅允许字母数字和下划线变量替换后新增的 @ 参数不会被 Nutz 再次解析
// 因此这里使用已校验的字典编码字面量确保统计条件能正确传递给数据库
if (StrUtil.isBlank(resultCode) || !resultCode.matches("[A-Za-z0-9_]+")) {
continue;
}
resultSqlParts.add(", (select count(1) from proposal_info info where info.delegationId = dbt.id and info.sessionId = dbt.sessionId and info.caseFilingResult = '" + resultCode + "') AS `" + resultCode + "`");
summaryOrderColumns.put(resultCode, "`" + resultCode + "`");
}
sql.setVar("resultSql", String.join("", resultSqlParts));
sql.setParam("confirmFiling", "CONFIRM_FILING");
Cnd cnd = Cnd.NEW();
cnd.andEX("dbt.sessionId", "=", sessionId);
if (!AuthUtil.hasRoleOr("SYSADMIN", "SCHOOL_UNION_ADMIN")) {
}
// 未选择排序时保留原有代表团编码升序选择后仅应用白名单字段
if (!proposalCommonService.applySafePageOrder(cnd, pageForm, summaryOrderColumns)) {
cnd.asc("dbt.`code`");
}
sql.setCondition(cnd);
return Result.success(baseService.listMap(sql));
return Result.success(proposalCommonService.queryDelegationStatistics(pageForm, sessionId));
}
/**
* 按当前届次排序及页面可见字段导出代表团统计
*
* @param pageForm 页面排序参数
* @param sessionId 教代会届次ID
* @param tableColumns 当前可见字段
* @param response HTTP 响应返回 XSSF Excel
*/
@At
@Ok("void")
@ApiOperation("自定义导出代表团提案统计")
@SaCheckPermission("proposal.query.delegation")
public void exportCustomAsExcel(@Valid @Param("pageForm") PageForm pageForm,
@Param("sessionId") String sessionId,
@Param("tableColumns") ExportTableColumns[] tableColumns,
HttpServletResponse response) {
proposalCommonService.exportDelegationStatisticsAsExcel(pageForm, sessionId, tableColumns, response);
}
@@ -1,28 +1,19 @@
package com.budwk.app.zhgh.democratic.proposal.controller.query;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.param.ExportTableColumns;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.flow.engine.util.FlowUtil;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalConfig;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalReplyUnit;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
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 java.util.*;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
/**
* 承办单位办理统计
@@ -33,16 +24,6 @@ import java.util.stream.Collectors;
@Ok("json:full")
public class ProposalQueryUnitReplyController {
/**
* 承办单位办理统计允许排序的字段白名单
*/
private static final Set<String> UNIT_REPLY_ORDER_COLUMNS = Set.of(
"unitName", "sum", "masterSum", "masterReplySum", "masterNoReplySum",
"slaveSum", "slaveReplySum", "slaveNoReplySum"
);
@Inject
private Dao dao;
@Inject
private ProposalCommonService proposalCommonService;
@@ -64,94 +45,30 @@ public class ProposalQueryUnitReplyController {
* caseFilingResult立案结果字典值对应 proposal_info.caseFilingResult为空时不限制立案结果
* 返回值Result.data 中包含 tableData 统计行列表以及 slaveNeedReply 协办单位是否需要答复配置
*/
// 提案配置 协办是否需要答复
ProposalConfig proposalConfig = dao.fetch(ProposalConfig.class, Cnd.NEW());
boolean slaveNeedReply = proposalConfig.getSlaveUnitNeedReply();
return Result.success().addData(proposalCommonService.queryUnitReplyStatistics(
pageForm, sessionId, undertakeUnitId, caseFilingResult));
}
// 当前届次所有的提案ID
Cnd proposalCnd = Cnd.where(ProposalInfo::getSessionId, "=", sessionId);
proposalCnd.andEX(ProposalInfo::getCaseFilingResult, "=", caseFilingResult);
List<String> proposalIds = dao.query(ProposalInfo.class, proposalCnd)
.stream()
.map(ProposalInfo::getId)
.collect(Collectors.toList());
if(proposalIds.isEmpty()){
return Result.success().addData(Map.of("tableData", Collections.EMPTY_LIST, "slaveNeedReply", slaveNeedReply));
}
// 关联所有的实例ID
Sql instSql = Sqls.create("select id from wf_process_instance where businessNo in (@proposalIds)");
instSql.setParam("proposalIds", proposalIds);
instSql.setCallback(Sqls.callback.strList());
dao.execute(instSql);
List<String> instIds = instSql.getList(String.class);
// 提案委员会立案分配的承办单位
Cnd cnd = Cnd.where(ProposalReplyUnit::getProposalId, "in", proposalIds);
cnd.andEX(ProposalReplyUnit::getUnitId, "=", undertakeUnitId);
List<ProposalReplyUnit> replyUnits = dao.query(ProposalReplyUnit.class, cnd);
// 承办单位答复记录
List<ProcessTask> replyTasks = dao.query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "in", instIds).and(ProcessTask::getTaskName, "in", List.of("two_unit_reply","unit_reply","master_reply", "slave_reply", "opinion_master_reply")).and(ProcessTask::getTaskState, "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.DOING.getCode())));
// 承办单位
List<NutMap> tableData = new ArrayList<>();
replyUnits.stream().collect(Collectors.groupingBy(ProposalReplyUnit::getUnitCode)).forEach((k, v) -> {
NutMap tableRow = NutMap.NEW();
tableRow.put("id", k);
v.stream().findAny().ifPresent(item -> {
tableRow.put("unitName", item.getUnitName());
tableRow.put("unitCode", item.getUnitCode());
});
// 提案总数
tableRow.put("sum", v.size());
// 主办提案数
tableRow.put("masterSum", v.stream().filter(unit -> Objects.equals(unit.getUnitCode(), k) && unit.getIsMaster()).count());
// 协办提案数
tableRow.put("slaveSum", v.stream().filter(unit -> Objects.equals(unit.getUnitCode(), k) && !unit.getIsMaster()).count());
// 主办提案已答复数
long masterReplySum = replyTasks.stream().filter(x ->
FlowUtil.variableToNutMap(x.getVariable()).getString("underTakeId", "").equals(k)
&& FlowUtil.variableToNutMap(x.getVariable()).getBoolean("underTakeIsMaster")
&& x.getTaskState().equals(ProcessTaskStateEnum.FINISHED.getCode())
).map(ProcessTask::getProcessInstanceId).distinct().count();
// 主办提案未答复数
long masterNoReplySum = replyTasks.stream().filter(x ->
FlowUtil.variableToNutMap(x.getVariable()).getString("underTakeId", "").equals(k)
&& FlowUtil.variableToNutMap(x.getVariable()).getBoolean("underTakeIsMaster")
&& x.getTaskState().equals(ProcessTaskStateEnum.DOING.getCode())
).map(ProcessTask::getProcessInstanceId).distinct().count();
// 协办提案已答复数
long slaveReplySum = replyTasks.stream().filter(x ->
FlowUtil.variableToNutMap(x.getVariable()).getString("underTakeId", "").equals(k)
&& !FlowUtil.variableToNutMap(x.getVariable()).getBoolean("underTakeIsMaster")
&& x.getTaskState().equals(ProcessTaskStateEnum.FINISHED.getCode())
).map(ProcessTask::getProcessInstanceId).distinct().count();
// 协办提案未答复数
long slaveNoReplySum = replyTasks.stream().filter(x ->
FlowUtil.variableToNutMap(x.getVariable()).getString("underTakeId", "").equals(k)
&& !FlowUtil.variableToNutMap(x.getVariable()).getBoolean("underTakeIsMaster")
&& x.getTaskState().equals(ProcessTaskStateEnum.DOING.getCode())
).map(ProcessTask::getProcessInstanceId).distinct().count();
tableRow.put("masterReplySum", masterReplySum);
tableRow.put("masterNoReplySum", masterNoReplySum);
tableRow.put("slaveReplySum", slaveReplySum);
tableRow.put("slaveNoReplySum", slaveNoReplySum);
tableData.add(tableRow);
});
// 统计结果生成后在服务层按白名单字段排序避免前端字段参与任意业务处理
proposalCommonService.sortStatisticsRows(tableData, pageForm, UNIT_REPLY_ORDER_COLUMNS);
return Result.success().addData(Map.of("tableData", tableData, "slaveNeedReply", slaveNeedReply));
/**
* 按当前筛选条件及页面可见字段导出承办单位办理统计
*
* @param pageForm 页面排序参数
* @param sessionId 教代会届次ID
* @param undertakeUnitId 承办单位ID
* @param caseFilingResult 立案结果编码
* @param tableColumns 当前可见字段
* @param response HTTP 响应返回 XSSF Excel
*/
@At
@Ok("void")
@SaCheckPermission("proposal.query.unitReply")
public void exportCustomAsExcel(@Valid @Param("pageForm") PageForm pageForm,
@Param("sessionId") String sessionId,
@Param("undertakeUnitId") String undertakeUnitId,
@Param("caseFilingResult") String caseFilingResult,
@Param("tableColumns") ExportTableColumns[] tableColumns,
HttpServletResponse response) {
proposalCommonService.exportUnitReplyStatisticsAsExcel(pageForm, sessionId, undertakeUnitId,
caseFilingResult, tableColumns, response);
}
}
@@ -35,6 +35,7 @@ public class ProposalQueryYearReportController {
*/
private static final Map<String, String> YEAR_REPORT_ORDER_COLUMNS = Map.ofEntries(
Map.entry("code", "info.code"),
Map.entry("caseFilingCode", "info.caseFilingCode"),
Map.entry("name", "info.name"),
Map.entry("createUserName", "info.createUserName"),
Map.entry("typeName", "typeName"),
@@ -1,31 +1,19 @@
package com.budwk.app.zhgh.democratic.proposal.controller.query;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.param.ExportTableColumns;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.flow.engine.util.FlowUtil;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.sys.models.Sys_dict;
import com.budwk.app.sys.services.SysDictService;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalConfig;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalReplyUnit;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import io.swagger.annotations.Api;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
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 java.util.*;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
@IocBean
@At("/platform/proposal/query/underTake/satisfaction")
@@ -33,17 +21,6 @@ import java.util.stream.Collectors;
@Api(tags = "提案承办单位满意度")
public class ProposalUnderTakeSatisfactionController {
/**
* 满意度统计固定列的排序白名单动态满意度列在查询时追加
*/
private static final Set<String> SATISFACTION_ORDER_COLUMNS = Set.of(
"unitName", "sum", "masterSum", "slaveSum"
);
@Inject
private Dao dao;
@Inject
private SysDictService sysDictService;
@Inject
private ProposalCommonService proposalCommonService;
@@ -65,80 +42,28 @@ public class ProposalUnderTakeSatisfactionController {
@At
@SaCheckPermission("proposal.query.underTake.satisfaction")
public Result data(PageForm pageForm, String sessionId, String undertakeUnitId) {
List<Sys_dict> feedbackCodes = sysDictService.getSubListByCode("PROPOSAL_FEEDBACK");
Set<String> orderColumns = new HashSet<>(SATISFACTION_ORDER_COLUMNS);
// 动态满意度列仅从后端字典加入白名单不接受前端自行扩展字段
feedbackCodes.stream()
.map(Sys_dict::getCode)
.filter(StrUtil::isNotBlank)
.forEach(orderColumns::add);
// 当前届次所有的提案ID
Sql sql = Sqls.create("select id from proposal_info where sessionId = @sessionId").setParam("sessionId", sessionId);
sql.setCallback(Sqls.callback.strList());
dao.execute(sql);
List<String> proposalIds = sql.getList(String.class);
if (proposalIds.isEmpty()) {
return Result.success(Collections.EMPTY_LIST);
}
// 关联所有的实例ID
Sql instSql = Sqls.create("select id from wf_process_instance where businessNo in (@proposalIds)");
instSql.setParam("proposalIds", proposalIds);
instSql.setCallback(Sqls.callback.strList());
dao.execute(instSql);
List<String> instIds = instSql.getList(String.class);
// 提案委员会立案分配的承办单位
Cnd cnd = Cnd.where(ProposalReplyUnit::getProposalId, "in", proposalIds);
cnd.andEX(ProposalReplyUnit::getUnitId, "=", undertakeUnitId);
List<ProposalReplyUnit> replyUnits = dao.query(ProposalReplyUnit.class, cnd);
// 反馈评分记录
// List<ProcessTask> feedbackTasks = dao.query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "in", instIds)
// .and(ProcessTask::getTaskName, "=", "feedback")
// .and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.FINISHED.getCode()));
// 承办单位
List<NutMap> tableData = new ArrayList<>();
replyUnits.stream().collect(Collectors.groupingBy(ProposalReplyUnit::getUnitCode)).forEach((k, v) -> {
NutMap tableRow = NutMap.NEW();
tableRow.put("id", k);
v.stream().findAny().ifPresent(item -> {
tableRow.put("unitName", item.getUnitName());
tableRow.put("unitCode", item.getUnitCode());
});
// 提案总数
tableRow.put("sum", v.size());
// 主办提案数
tableRow.put("masterSum", v.stream().filter(unit -> Objects.equals(unit.getUnitCode(), k) && unit.getIsMaster()).count());
// 协办提案数
tableRow.put("slaveSum", v.stream().filter(unit -> Objects.equals(unit.getUnitCode(), k) && !unit.getIsMaster()).count());
// 该单位负责的提案
List<String> thisUnitProposalIds = v.stream().map(ProposalReplyUnit::getProposalId).toList();
List<ProcessInstance> instances = dao.query(ProcessInstance.class, Cnd.where(ProcessInstance::getBusinessNo, "in", thisUnitProposalIds));
List<Long> thisUnitInstIds = instances.stream().map(ProcessInstance::getId).toList();
// 反馈评分记录
List<ProcessTask> feedbackTasks = dao.query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "in", thisUnitInstIds)
.and(ProcessTask::getTaskName, "=", "feedback")
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.FINISHED.getCode()));
for (Sys_dict code : feedbackCodes) {
long count = feedbackTasks.stream().filter(task -> FlowUtil.variableToNutMap(task.getVariable()).getString("tf_feedback", "").equals(code.getCode())).map(ProcessTask::getProcessInstanceId).distinct().count();
tableRow.put(code.getCode(), count);
}
tableData.add(tableRow);
});
// 汇总完成后在服务层执行白名单排序数字列按数值顺序处理
proposalCommonService.sortStatisticsRows(tableData, pageForm, orderColumns);
return Result.success(tableData);
return Result.success(proposalCommonService.queryUnderTakeSatisfactionStatistics(
pageForm, sessionId, undertakeUnitId));
}
/**
* 按当前届次承办单位和页面可见字段导出满意度统计
*
* @param pageForm 页面排序参数
* @param sessionId 教代会届次ID
* @param undertakeUnitId 承办单位ID
* @param tableColumns 当前可见字段
* @param response HTTP 响应返回 XSSF Excel
*/
@At
@Ok("void")
@SaCheckPermission("proposal.query.underTake.satisfaction")
public void exportCustomAsExcel(@Valid @Param("pageForm") PageForm pageForm,
@Param("sessionId") String sessionId,
@Param("undertakeUnitId") String undertakeUnitId,
@Param("tableColumns") ExportTableColumns[] tableColumns,
HttpServletResponse response) {
proposalCommonService.exportUnderTakeSatisfactionAsExcel(
pageForm, sessionId, undertakeUnitId, tableColumns, response);
}
}
@@ -101,7 +101,8 @@ public class ProposalExpeditingController {
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("t.taskName", "in", List.of("master_reply", "slave_reply", "opinion_master_reply"));
// 催办列表只展示现行 WF 承办答复环节中尚未完成的主办二次答复及意见答复任务
cnd.and("t.taskName", "in", List.of("unit_reply", "two_unit_reply", "opinion_unit_reply"));
if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name()) && !AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_ADMIN.name())) {
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
@@ -1,6 +1,7 @@
package com.budwk.app.zhgh.democratic.proposal.controller.transact;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.ExportTableColumns;
@@ -55,8 +56,17 @@ public class ProposalCaseCheckController {
public void index() {
}
/**
* 打开提案委员会审查手机端列表页
*/
@At("/h5")
@Ok("beetl:/platform/zhghh5/democratic/proposal/transact/caseCheck/index.html")
@SaCheckPermission("h5.proposal.caseCheck")
public void h5Index() {
}
@At
@SaCheckPermission("proposal.caseCheck")
@SaCheckPermission(value = {"proposal.caseCheck", "h5.proposal.caseCheck"}, mode = SaMode.OR)
@ApiOperation("分页列表")
public Result pageData(@Valid ProposalSearchParam pageForm, boolean approval) {
Sql sql = Sqls.create("""
@@ -129,7 +139,7 @@ public class ProposalCaseCheckController {
}
@At
@SaCheckPermission("proposal.caseCheck")
@SaCheckPermission(value = {"proposal.caseCheck", "h5.proposal.caseCheck"}, mode = SaMode.OR)
@ApiOperation("查询当前提案承办单位")
public Result listReplyUnits(String proposalId) {
return Result.success(proposalCaseCheckService.listReplyUnits(proposalId));
@@ -49,7 +49,6 @@ public class ProposalCommissionerController {
public void h5Index() {
}
@At
@SaCheckPermission("proposal.commissioner")
public Result pageData(@Valid ProposalSearchParam pageForm, boolean approval) {
@@ -125,6 +125,17 @@ public class ProposalCommitteeFilingController {
proposalCommonService.exportWorkflowList("committeeFiling", pageForm, approval, tableColumns, response);
}
@At
@SaCheckPermission("proposal.committeeFiling")
@ApiOperation("执行单案立案审核任务")
@Aop(TransAop.READ_COMMITTED)
public Result executeTask(@Param("data") String data) {
// data 包含流程任务ID提交类型立案结果及主协办单位Service 校验负责人后执行 WF成功返回统一 Result
Dict args = Json.fromJson(Dict.class, data);
proposalCommitteeFilingService.executeFlowTask(args);
return Result.success();
}
@At
@SaCheckPermission("proposal.committeeFiling")
@ApiOperation("并案审核")
@@ -47,6 +47,7 @@ public class ProposalControlController {
@SaCheckPermission("proposal.control")
@ApiOperation("分页列表")
public Result pageData(@Valid ProposalSearchParam pageForm) {
// 状态调整属于提案业务只保留能够关联到 proposal_info WF 任务排除其他业务流程产生的空白行
Sql sql = Sqls.create("""
SELECT
info.*,
@@ -72,7 +73,7 @@ public class ProposalControlController {
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
LEFT JOIN proposal_info info ON info.id = ins.businessNo
INNER JOIN proposal_info info ON info.id = ins.businessNo
LEFT JOIN proposal_type type ON type.id = info.typeId
LEFT JOIN teacher_congress_session tcs ON tcs.id = info.sessionId
LEFT JOIN teacher_congress_delegation tcd ON tcd.id = info.delegationId
@@ -4,7 +4,6 @@ import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.ExportTableColumns;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.bpm.enums.BpmProcessTaskStatusEnum;
@@ -30,10 +29,8 @@ 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.validation.Valid;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
@@ -67,7 +64,6 @@ public class ProposalDelegationController {
public void h5Index() {
}
@At
@SaCheckPermission("proposal.delegation")
@ApiOperation("分页列表")
@@ -129,13 +125,4 @@ public class ProposalDelegationController {
return Result.success(pagination);
}
@At
@Ok("void")
@SaCheckPermission("proposal.delegation")
@ApiOperation("导出列表")
public void exportList(@Valid @Param("pageForm") ProposalSearchParam pageForm, boolean approval,
@Param("tableColumns") ExportTableColumns[] tableColumns, HttpServletResponse response) {
proposalCommonService.exportWorkflowList("delegation", pageForm, approval, tableColumns, response);
}
}
@@ -5,7 +5,6 @@ import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.ExportTableColumns;
import com.budwk.app.base.result.Result;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
@@ -27,7 +26,6 @@ import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import javax.validation.Valid;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@IocBean
@@ -54,7 +52,6 @@ public class ProposalFeedbackEvaluationController {
public void h5Index() {
}
@At
@SaCheckPermission("proposal.feedbackEvaluation")
@ApiOperation("分页列表")
@@ -118,15 +115,6 @@ public class ProposalFeedbackEvaluationController {
return Result.success(pagination);
}
@At
@Ok("void")
@SaCheckPermission("proposal.feedbackEvaluation")
@ApiOperation("导出列表")
public void exportList(@Valid @Param("pageForm") ProposalSearchParam pageForm, boolean approval,
@Param("tableColumns") ExportTableColumns[] tableColumns, HttpServletResponse response) {
proposalCommonService.exportWorkflowList("feedbackEvaluation", pageForm, approval, tableColumns, response);
}
@At
@SaCheckPermission("proposal.feedbackEvaluation")
@ApiOperation("获取立案信息")
@@ -52,6 +52,11 @@ public class ProposalPreAuditController {
public void index() {
}
@At("/h5")
@Ok("beetl:/platform/zhghh5/democratic/proposal/transact/preAudit/index.html")
@SaCheckPermission("proposal.preAudit")
public void h5Index() {
}
@At
@SaCheckPermission("proposal.preAudit")
@@ -74,7 +74,6 @@ public class ProposalSchoolLeaderApprovalController {
public void h5Index() {
}
@At
@SaCheckPermission("proposal.schoolLeaderApproval")
@ApiOperation("分页列表")
@@ -134,6 +134,9 @@ public class ProposalSecondedController {
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
LEFT JOIN wf_process_task dt ON dt.processInstanceId = ins.id
AND dt.taskName = 'delegation' AND dt.taskState = 20
AND (dt.variable ->> '$.submitType') IN ('1', '2')
LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id AND rt.taskState = 10
LEFT JOIN proposal_info info ON info.id = ins.businessNo
LEFT JOIN proposal_second ps ON ps.proposalId = info.id AND ps.seconderId = @seconderId
@@ -157,6 +160,8 @@ public class ProposalSecondedController {
} else {
// 未附议页签只展示当前登录附议人尚未处理的记录
cnd.and("ps.isAgree", "is", null);
// 团长选择同意或不同意后均截止附议退回提案人不视为最终审核结果
cnd.and("dt.id", "is", null);
// 待附议列表只展示两类任务
// 1. invite / second 节点任务
// 2. delegation / committee 节点且任务状态必须是已废弃(99)
@@ -216,6 +221,9 @@ public class ProposalSecondedController {
if (Strings.isBlank(proposalId) || Strings.isBlank(taskActorUserId) || taskId == null || submitType == null) {
return Result.error("参数错误");
}
if (proposalSecondedService.isDelegationReviewed(proposalId)) {
return Result.error("团长已完成审核,当前提案不能继续附议");
}
// 先定位 wf_process_task确保本次请求能准确命中需要完成的附议任务再更新两张业务表
ProcessTask processTask = this.getSecondTask(taskId, taskActorUserId);
@@ -294,6 +302,9 @@ public class ProposalSecondedController {
if (args.getLong("processTaskId") == null || Strings.isBlank(proposalId) || Strings.isBlank(taskActorUserId) || submitType == null) {
return Result.error("参数错误");
}
if (proposalSecondedService.isDelegationReviewed(proposalId)) {
return Result.error("团长已完成审核,当前提案不能继续附议");
}
flowCommonService.executeTask(args);
proposalSecondedService.updateSecondRecord(proposalId, taskActorUserId, opinion, submitType);
return Result.success();
@@ -5,7 +5,6 @@ import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.ExportTableColumns;
import com.budwk.app.base.result.Result;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
@@ -38,7 +37,6 @@ import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import javax.validation.Valid;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.List;
@@ -172,15 +170,6 @@ public class ProposalUnderTakeReplyController {
return Result.success(pagination);
}
@At
@Ok("void")
@SaCheckPermission("proposal.unitReply")
@ApiOperation("导出列表")
public void exportList(@Valid @Param("pageForm") ProposalSearchParam pageForm, boolean approval,
@Param("tableColumns") ExportTableColumns[] tableColumns, HttpServletResponse response) {
proposalCommonService.exportWorkflowList("unitReply", pageForm, approval, tableColumns, response);
}
/**
* 承办单位答复节点撤回需要基于整组当前节点判断
* 只有当前流程正在办理的节点这条列表记录所属节点完全一致时才允许撤回
@@ -73,6 +73,10 @@ public class ProposalCaseFilingInterceptor implements FlowInterceptor {
Dict dict = JSONUtil.toBean(processTask.getVariable(), Dict.class);
dict.set(FlowConst.TASK_FORM_DATA_PREFIX + "helpunitreply", helpUnitReply);
processTask.setVariable(JSONUtil.toJsonStr(dict));
dao.update(processTask);
/*
* 当前任务对象是在任务完成前加载的其中 finishTimeoperator 仍为空
* 这里只更新拦截器实际修改的流程变量避免整行更新覆盖 WF 已写入的办理时间和办理人
*/
dao.update(processTask, "variable");
}
}
@@ -12,6 +12,9 @@ import org.nutz.dao.interceptor.annotation.PrevInsert;
@Accessors(chain = true)
@Table("proposal_reply_unit")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@TableIndexes({
@Index(name = "INDEX_PROPOSAL_REPLY_UNIT_PROPOSAL_UNIT", fields = {"proposalId", "unitId"}, unique = false)
})
@Comment("提案信息")
public class ProposalReplyUnit extends BaseModel {
@@ -19,6 +19,8 @@ public class ProposalQueryComprehensiveParam extends PageForm {
private String name;
@ApiModelProperty(name = "提案编号")
private String code;
@ApiModelProperty(name = "立案编号")
private String caseFilingCode;
@ApiModelProperty(name = "教代会ID")
private String sessionId;
@ApiModelProperty(name = "search session id")
@@ -61,15 +63,22 @@ public class ProposalQueryComprehensiveParam extends PageForm {
@ApiModelProperty(name = "通用关键字")
private String commonKeyword;
@ApiModelProperty(name = "提案名称或编号搜索字段,仅支持name、code、caseFilingCode")
private String proposalSearchField;
@ApiModelProperty(name = "提案名称或编号搜索内容")
private String proposalKeyword;
@ApiModelProperty(name = "搜索关键字")
private String createUserKeyword;
/**
* 构建通用查询参数
* 将综合查询参数追加到查询条件中无返回值处理结果直接写入 cnd
*
* @param cnd cnd
* @param searchParam searchParam
* @param cnd 查询条件容器用于接收届次名称/编号提案人组织及立案条件
* @param searchParam 页面查询参数proposalSearchField namecode caseFilingCode
* proposalKeyword 传对应字段的模糊搜索内容
*/
public static void buildSearch(Cnd cnd, ProposalQueryComprehensiveParam searchParam) {
if (cnd == null) {
@@ -84,6 +93,19 @@ public class ProposalQueryComprehensiveParam extends PageForm {
// Isolated single-session filter for pages that should not reuse sessionId semantics.
cnd.andEX("info.sessionId", "=", searchParam.getSearchSessionId());
// 名称/编号组合搜索仅允许三个固定字段前端提交值不能直接作为SQL字段使用
if (StrUtil.isNotBlank(searchParam.getProposalKeyword())) {
String proposalSearchColumn = switch (StrUtil.blankToDefault(searchParam.getProposalSearchField(), "name")) {
case "name" -> "info.name";
case "code" -> "info.code";
case "caseFilingCode" -> "info.caseFilingCode";
default -> null;
};
if (proposalSearchColumn != null) {
cnd.where().andLike(proposalSearchColumn, searchParam.getProposalKeyword());
}
}
//提案名称
if (StrUtil.isNotBlank(searchParam.getName())) {
cnd.where().andLike("info.name", searchParam.getName());
@@ -92,6 +114,10 @@ public class ProposalQueryComprehensiveParam extends PageForm {
if (StrUtil.isNotBlank(searchParam.getCode())) {
cnd.where().andLike("info.code", searchParam.getCode());
}
// 立案编号由立案环节生成支持按已生成编号进行模糊检索
if (StrUtil.isNotBlank(searchParam.getCaseFilingCode())) {
cnd.where().andLike("info.caseFilingCode", searchParam.getCaseFilingCode());
}
//代表团ID
cnd.andEX("info.delegationId", "=", searchParam.getDelegationId());
if (StrUtil.isNotBlank(searchParam.getCreateUserName())) {
@@ -24,17 +24,34 @@ public class ProposalSearchParam extends PageForm {
private String createUserLoginName;
private String createUserKeyword;
private String caseFilingResult;
/** 名称/编号组合搜索字段,仅支持 name、code、caseFilingCode。 */
private String proposalSearchField;
/** 名称/编号组合搜索内容。 */
private String proposalKeyword;
/**
* 构建通用查询参数
* 将提案列表参数追加到查询条件中无返回值处理结果直接写入 cnd
*
* @param cnd cnd
* @param searchParam searchParam
* @param cnd 查询条件容器用于接收名称/编号届次提案人及立案结果条件
* @param searchParam 页面查询参数proposalSearchField namecode caseFilingCode
* proposalKeyword 传对应字段的模糊搜索内容
*/
public static void buildSearch(Cnd cnd, ProposalSearchParam searchParam) {
if (cnd == null) {
cnd = Cnd.NEW();
}
// 搜索字段通过固定白名单转换禁止把前端字段名直接用于SQL条件
if (StrUtil.isNotBlank(searchParam.getProposalKeyword())) {
String proposalSearchColumn = switch (StrUtil.blankToDefault(searchParam.getProposalSearchField(), "name")) {
case "name" -> "info.name";
case "code" -> "info.code";
case "caseFilingCode" -> "info.caseFilingCode";
default -> null;
};
if (proposalSearchColumn != null) {
cnd.where().andLike(proposalSearchColumn, searchParam.getProposalKeyword());
}
}
if (StrUtil.isNotBlank(searchParam.getName())) {
cnd.where().andLike("info.name", searchParam.getName());
}
@@ -6,6 +6,15 @@ import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo;
public interface ProposalCommitteeFilingService extends BaseService<ProposalInfo> {
/**
* 提案委员会单案立案审核
* 正常提交确认立案或工作建议时先校验所选承办单位是否均已配置负责人再执行 WF 流程任务
*
* @param args 页面提交的流程参数包括 processTaskIdsubmitTypetf_caseFilingResult
* tf_masterUnitIdstf_slaveUnitIds 等字段方法无返回值校验失败时抛出业务异常
*/
void executeFlowTask(Dict args);
/**
* 提案委员会并案审核
*
@@ -5,6 +5,14 @@ import com.budwk.app.zhgh.democratic.proposal.models.ProposalSecond;
public interface ProposalSecondedService extends BaseService<ProposalSecond> {
/**
* 判断团长是否已经对当前提案作出同意或不同意的审核结果
*
* @param proposalId 提案ID用于定位当前提案对应的最新工作流实例
* @return true 表示团长已同意或不同意不能继续附议false 表示仍可按原流程附议
*/
boolean isDelegationReviewed(String proposalId);
/**
* 仅更新附议表 proposal_second 中当前附议人的附议结果
* 参数说明
@@ -174,6 +174,25 @@ public interface ProposalCommonService extends BaseService<ProposalInfo> {
*/
void updateStateCode(String id, Integer stateCode);
/**
* 分页查询提案综合列表
*
* @param pageForm 综合查询页面提交的筛选排序和分页参数origins 指定通用关键字的检索范围
* commonKeyword 为通用关键字其余字段对应页面各独立筛选项
* @return Pagination 分页结果list 中每项包含提案流程状态当前节点及承办单位汇总字段
*/
Pagination queryComprehensive(ProposalQueryComprehensiveParam pageForm);
/**
* 按综合查询页面当前筛选条件和可见字段导出 Excel
*
* @param pageForm 综合查询页面提交的筛选和排序参数导出全部匹配记录不受当前分页限制
* @param tableColumns 列设置中当前可见的字段数组顺序即 Excel 列顺序
* @param response HTTP 响应方法直接输出 XSSF 格式的 Excel 文件
*/
void exportComprehensiveAsExcel(ProposalQueryComprehensiveParam pageForm,
ExportTableColumns[] tableColumns, HttpServletResponse response);
/**
* 查询提案征集进度列表
* 查询结果与页面既有筛选排序语义保持一致内部使用聚合关联避免逐行执行统计子查询
@@ -183,6 +202,85 @@ public interface ProposalCommonService extends BaseService<ProposalInfo> {
*/
Pagination queryCollectProgress(ProposalQueryComprehensiveParam pageForm);
/**
* 按征集进度页面当前筛选条件和可见字段导出 Excel
*
* @param pageForm 征集进度页面筛选及排序参数导出全部匹配记录
* @param tableColumns 列设置中当前可见字段数组顺序即 Excel 列顺序
* @param response HTTP 响应方法直接输出 XSSF Excel
*/
void exportCollectProgressAsExcel(ProposalQueryComprehensiveParam pageForm,
ExportTableColumns[] tableColumns, HttpServletResponse response);
/**
* 查询指定届次的代表团提案统计
*
* @param pageForm 排序参数
* @param sessionId 教代会届次ID
* @return 代表团统计行包含动态立案结果数量和立案率计算字段
*/
List<NutMap> queryDelegationStatistics(PageForm pageForm, String sessionId);
/**
* 导出代表团提案统计
*
* @param pageForm 当前排序参数
* @param sessionId 教代会届次ID
* @param tableColumns 当前可见统计字段
* @param response HTTP 响应方法直接输出 XSSF Excel
*/
void exportDelegationStatisticsAsExcel(PageForm pageForm, String sessionId,
ExportTableColumns[] tableColumns, HttpServletResponse response);
/**
* 查询承办单位办理统计
*
* @param pageForm 排序参数
* @param sessionId 教代会届次ID
* @param undertakeUnitId 承办单位ID为空时统计全部单位
* @param caseFilingResult 立案结果编码为空时不限制
* @return NutMaptableData 为统计行slaveNeedReply 表示协办单位是否需要答复
*/
NutMap queryUnitReplyStatistics(PageForm pageForm, String sessionId,
String undertakeUnitId, String caseFilingResult);
/**
* 导出承办单位办理统计
*
* @param pageForm 当前排序参数
* @param sessionId 教代会届次ID
* @param undertakeUnitId 承办单位ID
* @param caseFilingResult 立案结果编码
* @param tableColumns 当前可见统计字段
* @param response HTTP 响应方法直接输出 XSSF Excel
*/
void exportUnitReplyStatisticsAsExcel(PageForm pageForm, String sessionId, String undertakeUnitId,
String caseFilingResult, ExportTableColumns[] tableColumns,
HttpServletResponse response);
/**
* 查询承办单位满意度统计
*
* @param pageForm 排序参数
* @param sessionId 教代会届次ID
* @param undertakeUnitId 承办单位ID为空时统计全部单位
* @return 满意度统计行动态满意度字段使用字典编码作为键
*/
List<NutMap> queryUnderTakeSatisfactionStatistics(PageForm pageForm, String sessionId,
String undertakeUnitId);
/**
* 导出承办单位满意度统计
*
* @param pageForm 当前排序参数
* @param sessionId 教代会届次ID
* @param undertakeUnitId 承办单位ID
* @param tableColumns 当前可见统计字段
* @param response HTTP 响应方法直接输出 XSSF Excel
*/
void exportUnderTakeSatisfactionAsExcel(PageForm pageForm, String sessionId, String undertakeUnitId,
ExportTableColumns[] tableColumns, HttpServletResponse response);
/**
* 按提案去重统计指定届次各流程节点的待办和已办数量
*
@@ -82,6 +82,18 @@ import java.util.stream.Collectors;
@IocBean(args = {"refer:dao"})
public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> implements ProposalCommonService {
/** 确定立案提案的 Word 模板编码。 */
private static final String PROPOSAL_CONFIRM_FILING_TEMPLATE_CODE = "proposal_confirm_filing";
/** 工作建议提案的 Word 模板编码。 */
private static final String PROPOSAL_SUGGESTION_TEMPLATE_CODE = "proposal_suggestion";
/** 不予立案提案的 Word 模板编码。 */
private static final String PROPOSAL_NOT_TEMPLATE_CODE = "proposal_not";
/** 历史提案立案结果为空或无法识别时使用的原 Word 模板编码。 */
private static final String PROPOSAL_DEFAULT_TEMPLATE_CODE = "proposal";
/** PC端提案列表页面的排序字段白名单,页面代码由对应Controller固定传入。 */
private static final Map<String, Map<String, String>> PROPOSAL_LIST_ORDER_COLUMNS = createProposalListOrderColumns();
@@ -91,6 +103,7 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
/** 征集进度页面允许排序的字段,避免排序参数直接拼接到SQL。 */
private static final Map<String, String> COLLECT_PROGRESS_ORDER_COLUMNS = Map.ofEntries(
Map.entry("code", "info.code"),
Map.entry("caseFilingCode", "info.caseFilingCode"),
Map.entry("name", "info.name"),
Map.entry("createUserName", "info.createUserName"),
Map.entry("delegationName", "delegationName"),
@@ -100,6 +113,68 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
Map.entry("instanceState", "instanceState")
);
/** 征集进度页面允许自定义导出的字段及固定表头。 */
private static final Map<String, String> COLLECT_PROGRESS_EXPORT_COLUMNS = Map.ofEntries(
Map.entry("code", "提案编号"),
Map.entry("caseFilingCode", "立案编号"),
Map.entry("name", "提案名称"),
Map.entry("createUserName", "提案人"),
Map.entry("delegationName", "代表团"),
Map.entry("inviteCount", "邀请附议人数"),
Map.entry("finishCount", "已附议人数"),
Map.entry("curTaskName", "当前节点"),
Map.entry("instanceState", "流程状态")
);
/** 代表团统计固定字段的安全排序映射,动态立案结果字段由后端字典追加。 */
private static final Map<String, String> DELEGATION_SUMMARY_ORDER_COLUMNS = Map.of(
"dbtName", "dbt.name",
"TOTAL", "TOTAL",
"caseRate", "caseRateOrder"
);
/** 承办单位办理统计允许排序的字段白名单。 */
private static final Set<String> UNIT_REPLY_ORDER_COLUMNS = Set.of(
"unitName", "sum", "masterSum", "masterReplySum", "masterNoReplySum",
"slaveSum", "slaveReplySum", "slaveNoReplySum"
);
/** 满意度统计固定字段的排序白名单,动态满意度字段由后端字典追加。 */
private static final Set<String> SATISFACTION_ORDER_COLUMNS = Set.of(
"unitName", "sum", "masterSum", "slaveSum"
);
/** 综合查询页面允许排序的字段,避免前端排序参数直接进入SQL。 */
private static final Map<String, String> COMPREHENSIVE_ORDER_COLUMNS = Map.ofEntries(
Map.entry("code", "info.code"),
Map.entry("caseFilingCode", "info.caseFilingCode"),
Map.entry("name", "info.name"),
Map.entry("createUserName", "info.createUserName"),
Map.entry("typeName", "typeName"),
Map.entry("delegationName", "delegationName"),
Map.entry("caseFilingResult", "info.caseFilingResult"),
Map.entry("caseFilingType", "info.caseFilingType"),
Map.entry("merge", "merge"),
Map.entry("undertakeUnits", "undertakeUnitsOrder"),
Map.entry("curTaskName", "curTaskName"),
Map.entry("instanceState", "instanceState")
);
/** 综合查询页面允许自定义导出的字段及固定表头,防止请求伪造页面外字段。 */
private static final Map<String, String> COMPREHENSIVE_EXPORT_COLUMNS = Map.ofEntries(
Map.entry("code", "提案编号"),
Map.entry("caseFilingCode", "立案编号"),
Map.entry("name", "提案名称"),
Map.entry("createUserName", "提案人"),
Map.entry("typeName", "提案类别"),
Map.entry("delegationName", "代表团"),
Map.entry("caseFilingResult", "立案结果"),
Map.entry("merge", "是否并案"),
Map.entry("undertakeUnits", "承办单位"),
Map.entry("curTaskName", "当前节点"),
Map.entry("instanceState", "流程状态")
);
//找出富文本里面上传的图片
static String IMG_SRC_REGEX = "<img\\s+[^>]*src\\s*=\\s*([\"'])(/platform/sys/file/download\\?id=.*?)\\1[^>]*>";
@@ -122,6 +197,227 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
super(dao);
}
/**
* 分页查询提案综合列表
* 普通查询不关联历史任务表仅在 origins 包含 answer commonKeyword 非空时
* 才关联答复任务变量避免每条提案与全部历史任务产生大量中间结果
*
* @param pageForm 综合查询页面的筛选排序和分页参数
* @return 包含提案基本信息流程状态和承办单位汇总字段的分页结果
*/
@Override
public Pagination queryComprehensive(ProposalQueryComprehensiveParam pageForm) {
boolean searchAnswer = ArrayUtil.contains(pageForm.getOrigins(), "answer")
&& StrUtil.isNotBlank(pageForm.getCommonKeyword());
String answerTaskJoin = searchAnswer
? "LEFT JOIN wf_process_task replyTask ON replyTask.processInstanceId = ins.id"
: "";
Sql sql = Sqls.create("""
SELECT
info.*,
IF(mer.proposalId IS NOT NULL, 1, 0) AS merge,
type.name AS typeName,
tcs.fullName AS sessionName,
tcd.`name` AS delegationName,
ins.id AS instanceId,
ins.businessNo,
ins.state AS instanceState,
ins.variable AS instanceVariable,
ins.processDefineId AS instanceProcessDefineId,
t.displayName AS curTaskName,
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 1 THEN pru.unitName END) AS masterUnitName,
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 0 THEN pru.unitName END) AS slaveUnitNames,
GROUP_CONCAT(DISTINCT pru.unitName ORDER BY pru.isMaster DESC, pru.unitName) AS undertakeUnitsOrder
FROM proposal_info info
LEFT JOIN proposal_type type ON type.id = info.typeId
LEFT JOIN proposal_merge mer ON mer.proposalId = info.id
LEFT JOIN teacher_congress_session tcs ON tcs.id = info.sessionId
LEFT JOIN teacher_congress_delegation tcd ON tcd.id = info.delegationId
LEFT JOIN teacher_congress_delegate tcde
ON tcde.loginName = info.createUserLoginName AND tcde.sessionId = info.sessionId
LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10
LEFT JOIN proposal_reply_unit pru ON pru.proposalId = info.id
%s
$condition
""".formatted(answerTaskJoin));
Cnd dataCnd = buildComprehensiveCondition(pageForm, searchAnswer);
dataCnd.groupBy("info.id");
applySafePageOrder(dataCnd, pageForm, COMPREHENSIVE_ORDER_COLUMNS);
sql.setCondition(dataCnd);
// 总数查询只保留筛选需要的表不重复执行列表字段聚合和排序
String countDelegateJoin = StrUtil.isNotBlank(pageForm.getUnionId()) || StrUtil.isNotBlank(pageForm.getUnitId())
? "LEFT JOIN teacher_congress_delegate tcde ON tcde.loginName = info.createUserLoginName AND tcde.sessionId = info.sessionId"
: "";
String countInstanceJoin = searchAnswer
? "LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id"
: "";
String countReplyUnitJoin = StrUtil.isNotBlank(pageForm.getUnderTakeUnitId())
? "LEFT JOIN proposal_reply_unit pru ON pru.proposalId = info.id"
: "";
Sql countSql = Sqls.create("""
SELECT COUNT(DISTINCT info.id)
FROM proposal_info info
%s
%s
%s
%s
$condition
""".formatted(countDelegateJoin, countInstanceJoin, countReplyUnitJoin, answerTaskJoin));
countSql.setCondition(buildComprehensiveCondition(pageForm, searchAnswer));
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql, countSql);
}
/**
* 按综合查询页面当前筛选条件和列设置导出 Excel
* 查询条件与分页列表共用 buildComprehensiveCondition确保届次组织及万能搜索口径一致
* tableColumns 只决定导出字段和顺序不参与 SQL 字段拼接
*
* @param pageForm 综合查询页面筛选及排序参数导出不受分页参数限制
* @param tableColumns 列设置中当前可见字段数组顺序即 Excel 列顺序
* @param response HTTP 响应返回 XSSF 格式的 Excel 文件
*/
@Override
public void exportComprehensiveAsExcel(ProposalQueryComprehensiveParam pageForm,
ExportTableColumns[] tableColumns, HttpServletResponse response) {
List<ExportTableColumns> exportColumns = getComprehensiveExportColumns(tableColumns);
if (exportColumns.isEmpty()) {
throw new BaseException("请至少选择一个导出字段");
}
boolean searchAnswer = ArrayUtil.contains(pageForm.getOrigins(), "answer")
&& StrUtil.isNotBlank(pageForm.getCommonKeyword());
String answerTaskJoin = searchAnswer
? "LEFT JOIN wf_process_task replyTask ON replyTask.processInstanceId = ins.id"
: "";
Sql sql = Sqls.create("""
SELECT
info.*,
IF(mer.proposalId IS NOT NULL, 1, 0) AS merge,
type.name AS typeName,
tcs.fullName AS sessionName,
tcd.`name` AS delegationName,
ins.state AS instanceState,
t.displayName AS curTaskName,
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 1 THEN pru.unitName END) AS masterUnitName,
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 0 THEN pru.unitName END) AS slaveUnitNames,
GROUP_CONCAT(DISTINCT pru.unitName ORDER BY pru.isMaster DESC, pru.unitName) AS undertakeUnitsOrder
FROM proposal_info info
LEFT JOIN proposal_type type ON type.id = info.typeId
LEFT JOIN proposal_merge mer ON mer.proposalId = info.id
LEFT JOIN teacher_congress_session tcs ON tcs.id = info.sessionId
LEFT JOIN teacher_congress_delegation tcd ON tcd.id = info.delegationId
LEFT JOIN teacher_congress_delegate tcde
ON tcde.loginName = info.createUserLoginName AND tcde.sessionId = info.sessionId
LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10
LEFT JOIN proposal_reply_unit pru ON pru.proposalId = info.id
%s
$condition
""".formatted(answerTaskJoin));
Cnd cnd = buildComprehensiveCondition(pageForm, searchAnswer);
cnd.groupBy("info.id");
applySafePageOrder(cnd, pageForm, COMPREHENSIVE_ORDER_COLUMNS);
sql.setCondition(cnd);
List<NutMap> rows = listMap(sql);
if (rows.isEmpty()) {
throw new BaseException("当前筛选条件下没有可导出的提案数据");
}
convertComprehensiveExportValues(rows);
List<ExcelExportEntity> excelColumns = exportColumns.stream()
.map(column -> new ExcelExportEntity(column.getLabel(), column.getProp(), 20))
.collect(Collectors.toList());
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
exportParams.setTitle("提案综合查询");
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, excelColumns, rows);
CommonDownloadUtil.download("提案综合查询.xlsx", workbook, response);
}
/**
* 按页面字段白名单过滤导出列并使用后端固定表头避免伪造字段或重复字段进入 Excel
*
* @param tableColumns 页面提交的当前可见列
* @return 校验后的导出列保持页面列设置顺序
*/
private List<ExportTableColumns> getComprehensiveExportColumns(ExportTableColumns[] tableColumns) {
if (ArrayUtil.isEmpty(tableColumns)) {
return List.of();
}
List<ExportTableColumns> exportColumns = new ArrayList<>();
Set<String> selectedProps = new HashSet<>();
for (ExportTableColumns column : tableColumns) {
if (column == null || !COMPREHENSIVE_EXPORT_COLUMNS.containsKey(column.getProp())
|| !selectedProps.add(column.getProp())) {
continue;
}
ExportTableColumns safeColumn = new ExportTableColumns();
safeColumn.setProp(column.getProp());
safeColumn.setLabel(COMPREHENSIVE_EXPORT_COLUMNS.get(column.getProp()));
exportColumns.add(safeColumn);
}
return exportColumns;
}
/** 将字典、枚举及组合字段转换为与综合查询列表一致的展示文本。 */
private void convertComprehensiveExportValues(List<NutMap> rows) {
Map<String, String> caseFilingResultMap = sysDictService.getSubListByCode("PROPOSAL_CASE_FILING_RESULT")
.stream().collect(Collectors.toMap(Sys_dict::getCode, Sys_dict::getName));
Map<Integer, String> processStateMap = Arrays.stream(ProcessInstanceStateEnum.values())
.collect(Collectors.toMap(ProcessInstanceStateEnum::getCode, ProcessInstanceStateEnum::getMessage));
for (NutMap row : rows) {
// 承办单位必须在立案结果转换为中文前拼接否则无法判断立案与建议两种展示口径
row.put("undertakeUnits", formatUndertakeUnits(row));
row.put("merge", row.getInt("merge", 0) == 1 ? "" : "");
String caseFilingResult = row.getString("caseFilingResult");
row.put("caseFilingResult", caseFilingResultMap.getOrDefault(caseFilingResult, caseFilingResult));
Integer instanceState = row.getInt("instanceState");
row.put("instanceState", processStateMap.getOrDefault(instanceState, row.getString("instanceState")));
}
}
/**
* 构建综合列表和总数查询共用的筛选条件保证分页总数与列表数据范围一致
* 通用关键字中的多个词沿用原页面的 OR 关系未选择检索范围或关键字为空时不追加条件
*
* @param pageForm 综合查询页面筛选参数
* @param searchAnswer 是否需要检索工作流任务变量中的答复内容
* @return 不包含分组和排序的查询条件
*/
private Cnd buildComprehensiveCondition(ProposalQueryComprehensiveParam pageForm, boolean searchAnswer) {
Cnd cnd = Cnd.NEW();
if (ArrayUtil.isNotEmpty(pageForm.getOrigins()) && StrUtil.isNotBlank(pageForm.getCommonKeyword())) {
SqlExpressionGroup keywordCondition = new SqlExpressionGroup();
for (String keyword : pageForm.getCommonKeyword().split(" ")) {
if (ArrayUtil.contains(pageForm.getOrigins(), "name")) {
keywordCondition.orLike("info.name", keyword);
}
if (ArrayUtil.contains(pageForm.getOrigins(), "brief")) {
keywordCondition.orLike("info.brief", keyword);
}
if (ArrayUtil.contains(pageForm.getOrigins(), "createUserName")) {
keywordCondition.orLike("info.createUserName", keyword);
keywordCondition.orLike("info.createUserLoginName", keyword);
}
// 立案编号由立案环节生成选中该搜索来源时按编号进行模糊匹配
if (ArrayUtil.contains(pageForm.getOrigins(), "caseFilingCode")) {
keywordCondition.orLike("info.caseFilingCode", keyword);
}
if (searchAnswer) {
keywordCondition.orLike("JSON_EXTRACT(replyTask.variable,'$.tf_opinion')", keyword);
}
}
if (!keywordCondition.isEmpty()) {
cnd.and(keywordCondition);
}
}
ProposalQueryComprehensiveParam.buildSearch(cnd, pageForm);
return cnd;
}
/**
* 分页查询提案征集进度
* 附议人数和已附议人数使用分组结果关联避免原查询对每一条提案重复执行统计子查询
@@ -132,11 +428,48 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
*/
@Override
public Pagination queryCollectProgress(ProposalQueryComprehensiveParam pageForm) {
Sql sql = buildCollectProgressListSql(pageForm);
Sql countSql = Sqls.create("""
SELECT COUNT(DISTINCT info.id)
FROM proposal_info info
LEFT JOIN teacher_congress_delegate tcde ON tcde.loginName = info.createUserLoginName
LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10
$condition
""");
countSql.setCondition(buildCollectProgressCondition(pageForm));
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql, countSql);
}
/**
* 按征集进度页面当前筛选条件和可见字段导出全部匹配数据
*
* @param pageForm 页面筛选及排序参数
* @param tableColumns 当前可见字段顺序即 Excel 列顺序
* @param response HTTP 响应直接输出 XSSF Excel
*/
@Override
public void exportCollectProgressAsExcel(ProposalQueryComprehensiveParam pageForm,
ExportTableColumns[] tableColumns, HttpServletResponse response) {
List<NutMap> rows = listMap(buildCollectProgressListSql(pageForm));
Map<Integer, String> processStateMap = Arrays.stream(ProcessInstanceStateEnum.values())
.collect(Collectors.toMap(ProcessInstanceStateEnum::getCode, ProcessInstanceStateEnum::getMessage));
for (NutMap row : rows) {
Integer instanceState = row.getInt("instanceState");
row.put("instanceState", processStateMap.getOrDefault(instanceState, row.getString("instanceState")));
}
exportRowsAsExcel("提案征集进度", rows, tableColumns, COLLECT_PROGRESS_EXPORT_COLUMNS, response);
}
/** 构建征集进度分页和导出共用的数据查询,保证两处筛选、分组及排序口径一致。 */
private Sql buildCollectProgressListSql(ProposalQueryComprehensiveParam pageForm) {
Sql sql = Sqls.create("""
SELECT
info.id,
info.name,
info.code,
info.caseFilingCode,
info.createUserName,
type.NAME AS typeName,
tcs.fullName AS sessionName,
@@ -170,17 +503,7 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
dataCnd.groupBy("info.id");
applySafePageOrder(dataCnd, pageForm, COLLECT_PROGRESS_ORDER_COLUMNS);
sql.setCondition(dataCnd);
Sql countSql = Sqls.create("""
SELECT COUNT(DISTINCT info.id)
FROM proposal_info info
LEFT JOIN teacher_congress_delegate tcde ON tcde.loginName = info.createUserLoginName
LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10
$condition
""");
countSql.setCondition(buildCollectProgressCondition(pageForm));
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql, countSql);
return sql;
}
/**
@@ -244,6 +567,301 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
return cnd;
}
/**
* 查询指定届次的代表团提案统计动态立案结果字段仅使用已校验的后端字典编码
*
* @param pageForm 排序参数
* @param sessionId 教代会届次ID为空时返回空列表避免跨届次统计
* @return 代表团统计行包含各立案结果数量和立案率排序值
*/
@Override
public List<NutMap> queryDelegationStatistics(PageForm pageForm, String sessionId) {
if (StrUtil.isBlank(sessionId)) {
return List.of();
}
List<Sys_dict> dictList = sysDictService.getSubListByCode("PROPOSAL_CASE_FILING_RESULT");
Sql sql = Sqls.create("""
SELECT
dbt.id,
dbt.`name` AS dbtName,
(SELECT COUNT(1) FROM proposal_info info WHERE info.delegationId = dbt.id AND info.sessionId = dbt.sessionId) AS TOTAL,
(SELECT COUNT(1) FROM proposal_info info WHERE info.delegationId = dbt.id AND info.sessionId = dbt.sessionId AND info.isSubmit = 1) AS SUBMIT_COUNT
$resultSql,
(SELECT COUNT(1) FROM proposal_info info WHERE info.delegationId = dbt.id AND info.sessionId = dbt.sessionId AND info.caseFilingResult = @confirmFiling)
/ NULLIF((SELECT COUNT(1) FROM proposal_info info WHERE info.delegationId = dbt.id AND info.sessionId = dbt.sessionId), 0) AS caseRateOrder
FROM teacher_congress_delegation dbt
$condition
""");
Map<String, String> orderColumns = new HashMap<>(DELEGATION_SUMMARY_ORDER_COLUMNS);
List<String> resultSqlParts = new ArrayList<>();
for (Sys_dict dict : dictList) {
String resultCode = dict.getCode();
// 动态 SQL 别名和字面量只接受字母数字下划线避免字典异常值进入 SQL 结构
if (StrUtil.isBlank(resultCode) || !resultCode.matches("[A-Za-z0-9_]+")) {
continue;
}
resultSqlParts.add(", (SELECT COUNT(1) FROM proposal_info info WHERE info.delegationId = dbt.id AND info.sessionId = dbt.sessionId AND info.caseFilingResult = '"
+ resultCode + "') AS `" + resultCode + "`");
orderColumns.put(resultCode, "`" + resultCode + "`");
}
sql.setVar("resultSql", String.join("", resultSqlParts));
sql.setParam("confirmFiling", "CONFIRM_FILING");
Cnd cnd = Cnd.where("dbt.sessionId", "=", sessionId);
if (!applySafePageOrder(cnd, pageForm, orderColumns)) {
cnd.asc("dbt.`code`");
}
sql.setCondition(cnd);
return listMap(sql);
}
/**
* 导出代表团统计导出字段由页面当前可见列决定字段名称以服务端字典为准
*
* @param pageForm 排序参数
* @param sessionId 教代会届次ID
* @param tableColumns 当前可见字段
* @param response HTTP 响应直接输出 XSSF Excel
*/
@Override
public void exportDelegationStatisticsAsExcel(PageForm pageForm, String sessionId,
ExportTableColumns[] tableColumns, HttpServletResponse response) {
List<NutMap> rows = queryDelegationStatistics(pageForm, sessionId);
for (NutMap row : rows) {
int total = row.getInt("TOTAL", 0);
int confirmFiling = row.getInt("CONFIRM_FILING", 0);
row.put("caseRate", total == 0 ? "" : String.format(Locale.ROOT, "%.2f%%", confirmFiling * 100.0 / total));
}
Map<String, String> allowedColumns = new LinkedHashMap<>();
allowedColumns.put("dbtName", "代表团名称");
allowedColumns.put("TOTAL", "提案总数");
for (Sys_dict dict : sysDictService.getSubListByCode("PROPOSAL_CASE_FILING_RESULT")) {
if (StrUtil.isNotBlank(dict.getCode()) && dict.getCode().matches("[A-Za-z0-9_]+")) {
allowedColumns.put(dict.getCode(), dict.getName());
}
}
allowedColumns.put("caseRate", "立案率");
exportRowsAsExcel("代表团提案统计", rows, tableColumns, allowedColumns, response);
}
/**
* 查询承办单位办理统计返回统计行及协办单位是否需要答复的配置
*
* @param pageForm 排序参数
* @param sessionId 教代会届次ID
* @param undertakeUnitId 承办单位ID为空时统计全部单位
* @param caseFilingResult 立案结果编码为空时不限制
* @return tableData 为统计行slaveNeedReply 为协办答复配置
*/
@Override
public NutMap queryUnitReplyStatistics(PageForm pageForm, String sessionId,
String undertakeUnitId, String caseFilingResult) {
ProposalConfig proposalConfig = dao().fetch(ProposalConfig.class, Cnd.NEW());
boolean slaveNeedReply = proposalConfig != null && Boolean.TRUE.equals(proposalConfig.getSlaveUnitNeedReply());
Cnd proposalCnd = Cnd.where(ProposalInfo::getSessionId, "=", sessionId);
proposalCnd.andEX(ProposalInfo::getCaseFilingResult, "=", caseFilingResult);
List<String> proposalIds = dao().query(ProposalInfo.class, proposalCnd).stream()
.map(ProposalInfo::getId).collect(Collectors.toList());
if (proposalIds.isEmpty()) {
return NutMap.NEW().addv("tableData", List.of()).addv("slaveNeedReply", slaveNeedReply);
}
Sql instanceSql = Sqls.create("SELECT id FROM wf_process_instance WHERE businessNo IN (@proposalIds)");
instanceSql.setParam("proposalIds", proposalIds);
instanceSql.setCallback(Sqls.callback.strList());
dao().execute(instanceSql);
List<String> instanceIds = instanceSql.getList(String.class);
Cnd replyUnitCnd = Cnd.where(ProposalReplyUnit::getProposalId, "in", proposalIds);
replyUnitCnd.andEX(ProposalReplyUnit::getUnitId, "=", undertakeUnitId);
List<ProposalReplyUnit> replyUnits = dao().query(ProposalReplyUnit.class, replyUnitCnd);
List<ProcessTask> replyTasks = instanceIds.isEmpty() ? List.of() : dao().query(ProcessTask.class,
Cnd.where(ProcessTask::getProcessInstanceId, "in", instanceIds)
.and(ProcessTask::getTaskName, "in", List.of("two_unit_reply", "unit_reply", "master_reply", "slave_reply", "opinion_master_reply"))
.and(ProcessTask::getTaskState, "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.DOING.getCode())));
List<NutMap> tableData = new ArrayList<>();
replyUnits.stream().collect(Collectors.groupingBy(ProposalReplyUnit::getUnitCode)).forEach((unitCode, units) -> {
NutMap tableRow = NutMap.NEW().addv("id", unitCode);
units.stream().findAny().ifPresent(item -> {
tableRow.put("unitName", item.getUnitName());
tableRow.put("unitCode", item.getUnitCode());
});
tableRow.put("sum", units.size());
tableRow.put("masterSum", units.stream().filter(unit -> Boolean.TRUE.equals(unit.getIsMaster())).count());
tableRow.put("slaveSum", units.stream().filter(unit -> !Boolean.TRUE.equals(unit.getIsMaster())).count());
long masterReplySum = countUnitReplyTasks(replyTasks, unitCode, true, ProcessTaskStateEnum.FINISHED.getCode());
long masterNoReplySum = countUnitReplyTasks(replyTasks, unitCode, true, ProcessTaskStateEnum.DOING.getCode());
long slaveReplySum = countUnitReplyTasks(replyTasks, unitCode, false, ProcessTaskStateEnum.FINISHED.getCode());
long slaveNoReplySum = countUnitReplyTasks(replyTasks, unitCode, false, ProcessTaskStateEnum.DOING.getCode());
tableRow.put("masterReplySum", masterReplySum);
tableRow.put("masterNoReplySum", masterNoReplySum);
tableRow.put("slaveReplySum", slaveReplySum);
tableRow.put("slaveNoReplySum", slaveNoReplySum);
tableData.add(tableRow);
});
sortStatisticsRows(tableData, pageForm, UNIT_REPLY_ORDER_COLUMNS);
return NutMap.NEW().addv("tableData", tableData).addv("slaveNeedReply", slaveNeedReply);
}
/** 按承办单位、主协办标识和任务状态,对流程实例去重统计答复数量。 */
private long countUnitReplyTasks(List<ProcessTask> replyTasks, String unitCode, boolean master, int taskState) {
return replyTasks.stream().filter(task -> {
NutMap variables = FlowUtil.variableToNutMap(task.getVariable());
return Objects.equals(unitCode, variables.getString("underTakeId", ""))
&& master == Boolean.TRUE.equals(variables.getBoolean("underTakeIsMaster"))
&& Objects.equals(task.getTaskState(), taskState);
}).map(ProcessTask::getProcessInstanceId).distinct().count();
}
/** 按承办单位办理统计页面当前可见字段导出全部统计行。 */
@Override
public void exportUnitReplyStatisticsAsExcel(PageForm pageForm, String sessionId, String undertakeUnitId,
String caseFilingResult, ExportTableColumns[] tableColumns,
HttpServletResponse response) {
NutMap statistics = queryUnitReplyStatistics(pageForm, sessionId, undertakeUnitId, caseFilingResult);
List<NutMap> rows = statistics.getList("tableData", NutMap.class);
Map<String, String> allowedColumns = new LinkedHashMap<>();
allowedColumns.put("unitName", "单位名称");
allowedColumns.put("sum", "提案总数");
allowedColumns.put("masterSum", "主办提案数量");
allowedColumns.put("masterReplySum", "主办已答复");
allowedColumns.put("masterNoReplySum", "主办未答复");
allowedColumns.put("slaveSum", "协办提案数量");
if (statistics.getBoolean("slaveNeedReply", false)) {
allowedColumns.put("slaveReplySum", "协办已答复");
allowedColumns.put("slaveNoReplySum", "协办未答复");
}
exportRowsAsExcel("承办单位办理统计", rows, tableColumns, allowedColumns, response);
}
/**
* 查询承办单位满意度统计动态满意度字段来自 PROPOSAL_FEEDBACK 字典
*
* @param pageForm 排序参数
* @param sessionId 教代会届次ID
* @param undertakeUnitId 承办单位ID为空时统计全部单位
* @return 各承办单位的主协办数量及满意度计数
*/
@Override
public List<NutMap> queryUnderTakeSatisfactionStatistics(PageForm pageForm, String sessionId,
String undertakeUnitId) {
List<Sys_dict> feedbackCodes = sysDictService.getSubListByCode("PROPOSAL_FEEDBACK");
Set<String> orderColumns = new HashSet<>(SATISFACTION_ORDER_COLUMNS);
feedbackCodes.stream().map(Sys_dict::getCode).filter(StrUtil::isNotBlank).forEach(orderColumns::add);
Sql proposalSql = Sqls.create("SELECT id FROM proposal_info WHERE sessionId = @sessionId");
proposalSql.setParam("sessionId", sessionId);
proposalSql.setCallback(Sqls.callback.strList());
dao().execute(proposalSql);
List<String> proposalIds = proposalSql.getList(String.class);
if (proposalIds.isEmpty()) {
return List.of();
}
Cnd replyUnitCnd = Cnd.where(ProposalReplyUnit::getProposalId, "in", proposalIds);
replyUnitCnd.andEX(ProposalReplyUnit::getUnitId, "=", undertakeUnitId);
List<ProposalReplyUnit> replyUnits = dao().query(ProposalReplyUnit.class, replyUnitCnd);
List<NutMap> tableData = new ArrayList<>();
replyUnits.stream().collect(Collectors.groupingBy(ProposalReplyUnit::getUnitCode)).forEach((unitCode, units) -> {
NutMap tableRow = NutMap.NEW().addv("id", unitCode);
units.stream().findAny().ifPresent(item -> {
tableRow.put("unitName", item.getUnitName());
tableRow.put("unitCode", item.getUnitCode());
});
tableRow.put("sum", units.size());
tableRow.put("masterSum", units.stream().filter(unit -> Boolean.TRUE.equals(unit.getIsMaster())).count());
tableRow.put("slaveSum", units.stream().filter(unit -> !Boolean.TRUE.equals(unit.getIsMaster())).count());
List<String> unitProposalIds = units.stream().map(ProposalReplyUnit::getProposalId).toList();
List<Long> unitInstanceIds = dao().query(ProcessInstance.class,
Cnd.where(ProcessInstance::getBusinessNo, "in", unitProposalIds)).stream()
.map(ProcessInstance::getId).toList();
List<ProcessTask> feedbackTasks = unitInstanceIds.isEmpty() ? List.of() : dao().query(ProcessTask.class,
Cnd.where(ProcessTask::getProcessInstanceId, "in", unitInstanceIds)
.and(ProcessTask::getTaskName, "=", "feedback")
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.FINISHED.getCode()));
for (Sys_dict feedbackCode : feedbackCodes) {
if (StrUtil.isBlank(feedbackCode.getCode())) {
continue;
}
long count = feedbackTasks.stream().filter(task -> Objects.equals(feedbackCode.getCode(),
FlowUtil.variableToNutMap(task.getVariable()).getString("tf_feedback", "")))
.map(ProcessTask::getProcessInstanceId).distinct().count();
tableRow.put(feedbackCode.getCode(), count);
}
tableData.add(tableRow);
});
sortStatisticsRows(tableData, pageForm, orderColumns);
return tableData;
}
/** 按满意度统计页面当前可见字段导出全部统计行。 */
@Override
public void exportUnderTakeSatisfactionAsExcel(PageForm pageForm, String sessionId, String undertakeUnitId,
ExportTableColumns[] tableColumns, HttpServletResponse response) {
List<NutMap> rows = queryUnderTakeSatisfactionStatistics(pageForm, sessionId, undertakeUnitId);
Map<String, String> allowedColumns = new LinkedHashMap<>();
allowedColumns.put("unitName", "单位名称");
allowedColumns.put("sum", "提案总数");
allowedColumns.put("masterSum", "主办提案数");
allowedColumns.put("slaveSum", "协办提案数");
for (Sys_dict feedbackCode : sysDictService.getSubListByCode("PROPOSAL_FEEDBACK")) {
if (StrUtil.isNotBlank(feedbackCode.getCode())) {
allowedColumns.put(feedbackCode.getCode(), feedbackCode.getName());
}
}
exportRowsAsExcel("承办单位满意度统计", rows, tableColumns, allowedColumns, response);
}
/**
* 按页面字段白名单校验列定义并输出 Excel所有统计导出统一使用 XSSF 格式
*
* @param title 文件名及工作表标题
* @param rows 待导出的全部匹配数据
* @param tableColumns 页面当前可见字段及顺序
* @param allowedColumns 后端允许字段及规范表头
* @param response HTTP 响应
*/
private void exportRowsAsExcel(String title, List<NutMap> rows, ExportTableColumns[] tableColumns,
Map<String, String> allowedColumns, HttpServletResponse response) {
List<ExportTableColumns> exportColumns = getSafeExportColumns(tableColumns, allowedColumns);
if (exportColumns.isEmpty()) {
throw new BaseException("请至少选择一个导出字段");
}
if (rows == null || rows.isEmpty()) {
throw new BaseException("当前筛选条件下没有可导出的数据");
}
List<ExcelExportEntity> excelColumns = exportColumns.stream()
.map(column -> new ExcelExportEntity(column.getLabel(), column.getProp(), 20))
.collect(Collectors.toList());
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
exportParams.setTitle(title);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, excelColumns, rows);
CommonDownloadUtil.download(title + ".xlsx", workbook, response);
}
/** 按后端白名单过滤重复或伪造字段,并保留页面列设置顺序。 */
private List<ExportTableColumns> getSafeExportColumns(ExportTableColumns[] tableColumns,
Map<String, String> allowedColumns) {
if (ArrayUtil.isEmpty(tableColumns)) {
return List.of();
}
List<ExportTableColumns> exportColumns = new ArrayList<>();
Set<String> selectedProps = new HashSet<>();
for (ExportTableColumns column : tableColumns) {
if (column == null || !allowedColumns.containsKey(column.getProp()) || !selectedProps.add(column.getProp())) {
continue;
}
ExportTableColumns safeColumn = new ExportTableColumns();
safeColumn.setProp(column.getProp());
safeColumn.setLabel(allowedColumns.get(column.getProp()));
exportColumns.add(safeColumn);
}
return exportColumns;
}
/**
* 使用业务页面提供的字段白名单构建排序条件非法字段或非法排序方向将被忽略
*
@@ -306,7 +924,7 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
pages.put("commissioner", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "CONFIRM_FILING_COUNT", "CONFIRM_FILING_COUNT", "SUGGESTION_COUNT", "SUGGESTION_COUNT", "NOT_COUNT", "NOT_COUNT", "taskName", "taskName", "instanceState", "ins.state"));
pages.put("feedbackEvaluation", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "merge", "merge", "curTaskName", "curTaskName", "instanceState", "ins.state"));
pages.put("delegation", standardOrderColumns());
pages.put("control", standardOrderColumns());
pages.put("control", mergeOrderColumns(standardOrderColumns(), orderColumns("caseFilingCode", "info.caseFilingCode")));
pages.put("schoolLeaderApproval", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "undertakeUnits", "masterUnitName", "merge", "merge", "curTaskName", "curTaskName", "auditUser", "auditUser", "instanceState", "ins.state"));
pages.put("committeeFilingUnit", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "sessionName", "tcs.fullName", "caseFilingResult", "info.caseFilingResult", "delegationName", "tcd.name", "curTaskName", "curTaskName", "instanceState", "ins.state"));
pages.put("allFinished", orderColumns("code", "info.code", "name", "info.name", "createUserName", "info.createUserName", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "instanceState", "ins.state"));
@@ -318,7 +936,7 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
pages.put("configUnit", orderColumns("name", "t1.name", "code", "t1.code", "unitLeader", "u2.username"));
pages.put("configType", orderColumns("name", "name", "code", "code"));
pages.put("seniorBasic", seniorOrderColumns());
pages.put("expediting", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "delegationName", "tcd.name", "caseFilingResult", "info.caseFilingResult", "curTaskName", "curTaskName", "underTakeName", "underTakeName", "taskName", "t.displayName", "actorName", "ta.actorName", "instanceState", "ins.state"));
pages.put("expediting", orderColumns("code", "info.code", "caseFilingCode", "info.caseFilingCode", "name", "info.name", "typeName", "type.name", "delegationName", "tcd.name", "caseFilingResult", "info.caseFilingResult", "curTaskName", "curTaskName", "underTakeName", "underTakeName", "taskName", "t.displayName", "actorName", "ta.actorName", "instanceState", "ins.state"));
pages.put("seniorDelete", seniorOrderColumns());
pages.put("seniorFeedback", mergeOrderColumns(seniorOrderColumns(), orderColumns("tf_feedback", "tf_feedback")));
pages.put("seniorType", seniorOrderColumns());
@@ -332,7 +950,7 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
/** 创建高级管理列表共用的字段映射。 */
private static Map<String, String> seniorOrderColumns() {
return orderColumns("code", "info.code", "name", "info.name", "createUserName", "info.createUserName", "typeName", "type.name", "delegationName", "tcd.name", "caseFilingResult", "info.caseFilingResult", "isConsolidation", "merge", "curTaskName", "curTaskName", "instanceState", "ins.state");
return orderColumns("code", "info.code", "caseFilingCode", "info.caseFilingCode", "name", "info.name", "createUserName", "info.createUserName", "typeName", "type.name", "delegationName", "tcd.name", "caseFilingResult", "info.caseFilingResult", "isConsolidation", "merge", "curTaskName", "curTaskName", "instanceState", "ins.state");
}
/**
@@ -732,6 +1350,7 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
SELECT
info.name,
info.code,
info.caseFilingResult,
info.brief,
info.measures,
info.createUserName,
@@ -862,13 +1481,29 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
docData.put("secondedList", secondInfos);
docData.put("delegationAuditList", delegationAuditList);
String templateCode = getProposalTemplateCode(docData.getString("caseFilingResult"));
try {
XWPFTemplate.compile(sysOfficeTemplateUtil.getTemplate("proposal"), config).render(docData).writeAndClose(byteArrayOutputStream);
XWPFTemplate.compile(sysOfficeTemplateUtil.getTemplate(templateCode), config).render(docData).writeAndClose(byteArrayOutputStream);
} catch (IOException e) {
log.error("提案导出失败{},提案id:{}", e.getMessage(), id);
}
}
/**
* 根据立案结果选择数据库办公模板编码
*
* @param caseFilingResult 立案结果字典编码CONFIRM_FILINGSUGGESTION NOT
* @return 对应的 Word 模板编码历史空值或未知编码返回 proposal
*/
private String getProposalTemplateCode(String caseFilingResult) {
return switch (StrUtil.nullToEmpty(caseFilingResult)) {
case "CONFIRM_FILING" -> PROPOSAL_CONFIRM_FILING_TEMPLATE_CODE;
case "SUGGESTION" -> PROPOSAL_SUGGESTION_TEMPLATE_CODE;
case "NOT" -> PROPOSAL_NOT_TEMPLATE_CODE;
default -> PROPOSAL_DEFAULT_TEMPLATE_CODE;
};
}
/**
* 将现行工作流节点映射到历史提案表模板使用的区块名称并按主协办单位拆分办理答复
* 模板仍使用旧节点名称时避免已完成的意见和答复因键名不一致而无法渲染
@@ -879,28 +1514,52 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
*/
private void putProposalExportTaskAliases(String proposalId, Map<String, List<ProcessTaskVO>> taskGroups,
NutMap docData) {
putTaskAlias(docData, "提案委主任审核", taskGroups.get("校工会预审核"));
/* putTaskAlias(docData, "提案委主任审核", taskGroups.get("校工会预审核"));
putTaskAlias(docData, "提案委主任意见", taskGroups.get("提案委员会立案"));
putTaskAlias(docData, "提案委委员审议", taskGroups.get("委员会确认承办单位"));
putTaskAlias(docData, "提案委委员审议", taskGroups.get("委员会确认承办单位"));*/
putProposalExportFeedbackName(taskGroups.get("反馈评价"));
List<ProcessTaskVO> replyTasks = taskGroups.get("承办单位答复");
if (Lang.isEmpty(replyTasks)) {
return;
}
Sql masterUnitSql = Sqls.create("SELECT unitName FROM proposal_reply_unit WHERE proposalId = @proposalId AND isMaster = 1")
Sql replyUnitSql = Sqls.create("SELECT unitId, unitName, isMaster FROM proposal_reply_unit WHERE proposalId = @proposalId")
.setParam("proposalId", proposalId);
List<NutMap> masterUnitRows = listMap(masterUnitSql);
Set<String> masterUnitNames = masterUnitRows.stream()
.map(row -> row.getString("unitName"))
List<NutMap> replyUnitRows = listMap(replyUnitSql);
Set<String> masterUnitIds = replyUnitRows.stream()
.filter(row -> row.getBoolean("isMaster", false))
.map(row -> row.getString("unitId"))
.filter(StrUtil::isNotBlank)
.collect(Collectors.toSet());
Map<String, String> replyUnitNameMap = replyUnitRows.stream()
.filter(row -> StrUtil.isNotBlank(row.getString("unitId")))
.collect(Collectors.toMap(row -> row.getString("unitId"), row -> row.getString("unitName", ""),
(left, right) -> left));
List<ProcessTaskVO> masterReplyTasks = new ArrayList<>();
List<ProcessTaskVO> slaveReplyTasks = new ArrayList<>();
for (ProcessTaskVO replyTask : replyTasks) {
Dict taskExt = replyTask.getExt();
Dict taskFormData = replyTask.getTaskFormData();
String unitName = taskFormData == null ? null : taskFormData.getStr("unitName");
if (masterUnitNames.contains(unitName)) {
String underTakeId = taskExt == null ? null : taskExt.getStr("underTakeId");
if (StrUtil.isBlank(underTakeId) && taskFormData != null) {
underTakeId = taskFormData.getStr("unitId");
}
/*
* 承办单位名称保存在任务根变量 underTakeName 而模板读取 taskFormData.underTakeName
* 在本次导出对象中补充 tf_ 前缀字段 ProcessTaskVO 转换后即可保持现有模板不变
*/
if (taskExt != null) {
String underTakeName = taskExt.getStr("underTakeName");
if (StrUtil.isBlank(underTakeName)) {
underTakeName = replyUnitNameMap.getOrDefault(underTakeId, "");
}
taskExt.put("tf_underTakeName", underTakeName);
}
// 主协办关系按单位ID匹配避免把任务中的单位ID误当成单位名称后全部归入协办答复
if (masterUnitIds.contains(underTakeId)) {
masterReplyTasks.add(replyTask);
} else {
slaveReplyTasks.add(replyTask);
@@ -910,6 +1569,28 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
putTaskAlias(docData, "协办单位答复", slaveReplyTasks);
}
/**
* 将反馈评价字典编码转换为模板使用的中文名称仅补充本次导出的任务变量
*
* @param feedbackTasks 已完成的反馈评价任务为空时无需处理
*/
private void putProposalExportFeedbackName(List<ProcessTaskVO> feedbackTasks) {
if (Lang.isEmpty(feedbackTasks)) {
return;
}
Map<String, String> feedbackNameMap = sysDictService.getSubListByCode("PROPOSAL_FEEDBACK").stream()
.filter(item -> StrUtil.isNotBlank(item.getCode()))
.collect(Collectors.toMap(Sys_dict::getCode, Sys_dict::getName, (left, right) -> left));
for (ProcessTaskVO feedbackTask : feedbackTasks) {
Dict taskExt = feedbackTask.getExt();
if (taskExt == null) {
continue;
}
String feedbackCode = taskExt.getStr("tf_feedback");
taskExt.put("tf_feedback_name", feedbackNameMap.getOrDefault(feedbackCode, feedbackCode));
}
}
/**
* 仅在存在已完成任务时写入模板区块空任务不触发 Word 条件标签
*
@@ -980,7 +1661,7 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
);
if (feedbackTask != null) {
info.put(FlowUtil.variableToDict(feedbackTask.getVariable()).getStr("tf_feedback"), "");
info.put("tf_feedback", FlowUtil.variableToDict(feedbackTask.getVariable()).getStr("tf_feedback"));
info.put("tf_feedback_name", FlowUtil.variableToDict(feedbackTask.getVariable()).getStr("tf_feedback"));
info.put("tf_opinion", FlowUtil.variableToDict(feedbackTask.getVariable()).getStr("tf_opinion"));
info.put("tf_phone", FlowUtil.variableToDict(feedbackTask.getVariable()).getStr("tf_phone"));
info.put("tf_address", FlowUtil.variableToDict(feedbackTask.getVariable()).getStr("tf_address"));
@@ -2,14 +2,23 @@ package com.budwk.app.zhgh.democratic.proposal.service.impl;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.service.FlowCommonService;
import com.budwk.app.flow.service.ProcessTaskService;
import com.budwk.app.sys.models.Sys_role;
import com.budwk.app.sys.models.Sys_user_role;
import com.budwk.app.sys.services.SysRoleService;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalConsolidation;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalMerge;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalUndertake;
import com.budwk.app.zhgh.democratic.proposal.service.ProposalCommitteeFilingService;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import org.nutz.aop.interceptor.ioc.TransAop;
@@ -20,7 +29,14 @@ import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.random.R;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@IocBean(args = {"refer:dao"})
public class ProposalCommitteeFilingServiceImpl extends BaseServiceImpl<ProposalInfo> implements ProposalCommitteeFilingService {
@@ -31,14 +47,26 @@ public class ProposalCommitteeFilingServiceImpl extends BaseServiceImpl<Proposal
private FlowCommonService flowCommonService;
@Inject
private ProcessTaskService processTaskService;
@Inject
private SysRoleService sysRoleService;
public ProposalCommitteeFilingServiceImpl(Dao dao) {
super(dao);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void executeFlowTask(Dict args) {
validateSelectedUnitLeaders(args);
flowCommonService.executeTask(args);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void merge(Dict args) {
// 并案必须在创建并案关系和执行任一流程任务前完成负责人校验保证失败时整组数据不发生变化
validateSelectedUnitLeaders(args);
List<Integer> taskIds = (List<Integer>) args.get("processTaskIds");
List<String> proposalIds = (List<String>) args.get("proposalIds");
@@ -65,6 +93,101 @@ public class ProposalCommitteeFilingServiceImpl extends BaseServiceImpl<Proposal
}
}
/**
* 校验正常提交所选承办单位的负责人配置
* 确认立案检查全部主办协办单位工作建议只检查办理单位不予立案和退回操作不检查
* 负责人沿用 WF 承办单位分派口径用户必须绑定提案承办单位领导角色及对应 underTakeId
*
* @param args 页面流程参数submitType=1 时根据 tf_caseFilingResulttf_masterUnitIdstf_slaveUnitIds 检查
*/
private void validateSelectedUnitLeaders(Dict args) {
Integer submitType = args.getInt(FlowConst.SUBMIT_TYPE);
if (!ProcessSubmitTypeEnum.AGREE.getCode().equals(submitType)) {
return;
}
String caseFilingResult = args.getStr(FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingResult");
if (!List.of("CONFIRM_FILING", "SUGGESTION").contains(caseFilingResult)) {
return;
}
LinkedHashSet<String> selectedUnitIds = new LinkedHashSet<>(
getUnitIdList(args, FlowConst.TASK_FORM_DATA_PREFIX + "masterUnitIds"));
if ("CONFIRM_FILING".equals(caseFilingResult)) {
selectedUnitIds.addAll(getUnitIdList(args, FlowConst.TASK_FORM_DATA_PREFIX + "slaveUnitIds"));
}
if (selectedUnitIds.isEmpty()) {
return;
}
Sys_role unitLeaderRole = sysRoleService.getByCode(RoleConstant.PROPOSAL_UNIT_LEADER);
if (unitLeaderRole == null) {
throw new BaseException("提案承办单位领导角色不存在");
}
Map<String, ProposalUndertake> unitMap = dao().query(ProposalUndertake.class,
Cnd.where(ProposalUndertake::getId, "in", selectedUnitIds))
.stream()
.collect(Collectors.toMap(ProposalUndertake::getId, unit -> unit));
if (unitMap.size() != selectedUnitIds.size()) {
throw new BaseException("选择的承办单位不存在,请重新选择");
}
Set<String> configuredUnitIds = dao().query(Sys_user_role.class,
Cnd.where(Sys_user_role::getRoleId, "=", unitLeaderRole.getId())
.and(Sys_user_role::getUnderTakeId, "in", selectedUnitIds))
.stream()
.map(Sys_user_role::getUnderTakeId)
.filter(StrUtil::isNotBlank)
.collect(Collectors.toSet());
List<String> missingUnitNames = selectedUnitIds.stream()
.filter(unitId -> !configuredUnitIds.contains(unitId))
.map(unitMap::get)
.map(ProposalUndertake::getName)
.toList();
if (!missingUnitNames.isEmpty()) {
throw new BaseException(String.join("", missingUnitNames) + "没有设置负责人!");
}
}
/**
* 将页面单位字段统一转换为去空去重后的单位ID列表兼容数组JSON数组字符串和逗号分隔字符串
*
* @param args 页面流程参数
* @param key 需要读取的单位字段名
* @return 规范化后的单位ID列表
*/
private List<String> getUnitIdList(Dict args, String key) {
Object value = args.get(key);
if (ObjectUtil.isEmpty(value)) {
return List.of();
}
List<String> unitIds = new ArrayList<>();
if (value instanceof Collection<?> collection) {
collection.forEach(item -> {
if (ObjectUtil.isNotEmpty(item)) {
unitIds.add(String.valueOf(item));
}
});
} else if (value instanceof String stringValue) {
if (StrUtil.isBlank(stringValue)) {
return List.of();
}
if (JSONUtil.isTypeJSONArray(stringValue)) {
unitIds.addAll(JSONUtil.parseArray(stringValue).toList(String.class));
} else {
Arrays.stream(stringValue.split(","))
.map(String::trim)
.filter(StrUtil::isNotBlank)
.forEach(unitIds::add);
}
} else {
unitIds.add(String.valueOf(value));
}
return new ArrayList<>(new LinkedHashSet<>(unitIds));
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void revokeTask(Long taskId, String proposalId) {
@@ -16,6 +16,7 @@ import com.budwk.app.bpm.models.BpmProcessTask;
import com.budwk.app.bpm.service.BpmService;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.service.FlowCommonService;
import com.budwk.app.flow.service.ProcessTaskService;
import com.budwk.app.sys.models.Sys_user_role;
@@ -34,6 +35,8 @@ 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.Static;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
@@ -87,7 +90,7 @@ public class ProposalCommitteeFilingUnitServiceImpl extends BaseServiceImpl<Prop
if (ObjectUtil.isEmpty(proposalIds)) {
proposalIds = List.of(proposalId);
}
validateAndNormalizeCaseFilingCode(args, proposalIds);
generateAndNormalizeCaseFilingCode(args, proposalIds);
/*
* 先同步 proposal_info 和承办单位再执行 WF 流转使查看页面及下一节点读取到的都是本次最新数据
@@ -111,40 +114,114 @@ public class ProposalCommitteeFilingUnitServiceImpl extends BaseServiceImpl<Prop
}
/**
* 校验并规范化立案编号确定立案时必须传入不超过 20 个字符且不与历史立案编号重复的编号
* 当前提案及并案提案不参与重复校验保证流程回退后可按原编号重新提交
* 其他立案结果不保存编号避免切换选项后把历史输入带入业务表和流程变量
* 按立案结果和服务器当前年份生成立案编号并覆盖页面可能传入的编号
* 正常提交时生成编号退回等非同意操作不占用流水号并案提案共用同一个编号
*
* @param args 流程提交参数其中 tf_caseFilingResult 为立案结果tf_caseFilingCode 为手工填写的立案编号
* @param excludedProposalIds 当前办理的提案及并案提案主键不参与历史重复判断
* @param args 流程提交参数tf_caseFilingResult 分别支持 CONFIRM_FILINGSUGGESTIONNOT
* @param proposalIds 当前办理的提案主键集合并案时包含同组全部提案
*/
private void validateAndNormalizeCaseFilingCode(Dict args, List<String> excludedProposalIds) {
private void generateAndNormalizeCaseFilingCode(Dict args, List<String> proposalIds) {
String caseFilingResultKey = FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingResult";
String caseFilingCodeKey = FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingCode";
String caseFilingTypeKey = FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingType";
if (!"CONFIRM_FILING".equals(args.getStr(caseFilingResultKey))) {
args.put(caseFilingCodeKey, "");
String caseFilingResult = args.getStr(caseFilingResultKey);
if (!"CONFIRM_FILING".equals(caseFilingResult)) {
// 立案类型仅适用于确定立案切换为其他结果时同步清理历史值
args.put(caseFilingTypeKey, "");
}
Integer submitType = args.getInt(FlowConst.SUBMIT_TYPE);
if (submitType == null) {
submitType = ProcessSubmitTypeEnum.AGREE.getCode();
args.put(FlowConst.SUBMIT_TYPE, submitType);
}
if (!ProcessSubmitTypeEnum.AGREE.getCode().equals(submitType)) {
args.put(caseFilingCodeKey, "");
return;
}
String caseFilingCode = StrUtil.trim(args.getStr(caseFilingCodeKey));
if (StrUtil.isBlank(caseFilingCode)) {
throw new BaseException("立案编号不能为空");
String codePrefix = resolveCaseFilingCodePrefix(caseFilingResult);
int currentYear = java.time.LocalDate.now().getYear();
String codeHead = currentYear + codePrefix;
/*
* proposal_config 当前是单行配置表锁定该行可让所有应用实例串行生成流水号
* 行锁一直持有到本次 WF 提交事务结束避免两个审核请求同时读到相同最大值
*/
Sql lockSql = Sqls.create("SELECT id FROM proposal_config ORDER BY id LIMIT 1 FOR UPDATE");
lockSql.setCallback(Sqls.callback.map());
dao().execute(lockSql);
Map<?, ?> configRow = lockSql.getObject(Map.class);
if (ObjectUtil.isEmpty(configRow)) {
throw new BaseException("提案配置不存在,无法生成立案编号");
}
if (caseFilingCode.length() > 20) {
throw new BaseException("立案编号不能超过20个字符");
}
Cnd duplicateCondition = Cnd.where("caseFilingCode", "=", caseFilingCode);
if (!ObjectUtil.isEmpty(excludedProposalIds)) {
duplicateCondition.and("id", "not in", excludedProposalIds);
}
if (dao().count(ProposalInfo.class, duplicateCondition) > 0) {
throw new BaseException("立案编号已存在,请重新填写");
String reusableCode = findReusableCaseFilingCode(proposalIds, caseFilingResult, codeHead);
if (StrUtil.isNotBlank(reusableCode)) {
args.put(caseFilingCodeKey, reusableCode);
return;
}
Sql maxSequenceSql = Sqls.create("""
SELECT COALESCE(MAX(CAST(SUBSTRING(caseFilingCode, 6) AS UNSIGNED)), 0)
FROM proposal_info
WHERE caseFilingCode REGEXP @codePattern
""");
maxSequenceSql.setParam("codePattern", "^" + codeHead + "[0-9]{3,}$");
maxSequenceSql.setCallback(Sqls.callback.integer());
dao().execute(maxSequenceSql);
int nextSequence = maxSequenceSql.getInt() + 1;
String caseFilingCode = codeHead + String.format("%03d", nextSequence);
args.put(caseFilingCodeKey, caseFilingCode);
}
/**
* 将立案结果转换成编号前缀未知结果直接阻止流程提交避免生成无业务含义的编号
*
* @param caseFilingResult 立案结果编码
* @return 确定立案返回 A工作建议返回 B不予立案返回 C
*/
private String resolveCaseFilingCodePrefix(String caseFilingResult) {
return switch (StrUtil.nullToEmpty(caseFilingResult)) {
case "CONFIRM_FILING" -> "A";
case "SUGGESTION" -> "B";
case "NOT" -> "C";
default -> throw new BaseException("立案结果不正确,无法生成立案编号");
};
}
/**
* 流程撤回或重新提交时复用同一批提案已有的本年同类型编号避免流水号无意义增长
* 只有全部提案的结果和编号完全一致且符合新编号格式时才允许复用
*
* @param proposalIds 当前单案或并案提案主键集合
* @param caseFilingResult 本次选择的立案结果
* @param codeHead 年份和编号前缀例如 2026A
* @return 可复用的编号不存在时返回空字符串
*/
private String findReusableCaseFilingCode(List<String> proposalIds, String caseFilingResult, String codeHead) {
List<ProposalInfo> proposalInfos = dao().query(ProposalInfo.class,
Cnd.where(ProposalInfo::getId, "in", proposalIds));
if (proposalInfos.size() != new LinkedHashSet<>(proposalIds).size()) {
return "";
}
boolean allProposalsMatch = proposalInfos.stream().allMatch(proposalInfo ->
Objects.equals(caseFilingResult, proposalInfo.getCaseFilingResult())
&& StrUtil.isNotBlank(proposalInfo.getCaseFilingCode()));
if (!allProposalsMatch) {
return "";
}
LinkedHashSet<String> existingCodes = proposalInfos.stream()
.map(ProposalInfo::getCaseFilingCode)
.map(StrUtil::trim)
.collect(Collectors.toCollection(LinkedHashSet::new));
if (existingCodes.size() != 1) {
return "";
}
String existingCode = existingCodes.iterator().next();
return existingCode.matches(codeHead + "\\d{3,}") ? existingCode : "";
}
/**
* old-flow 页面提交后需要把立案编号立案结果以及主协办单位同步落业务表
* 若这次改成不予立案等无需承办单位的结果也要先清掉旧的承办单位记录避免脏数据残留
@@ -29,6 +29,7 @@ import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
@@ -74,7 +75,8 @@ import java.util.zip.ZipOutputStream;
public class ProposalExportServiceImpl extends BaseServiceImpl<ProposalInfo> implements ProposalExportService {
private static final String WORK_SUGGESTION_SECTION_MARKER = "__WORK_SUGGESTION_SECTION__";
private static final int UNDERTAKE_UNIT_EXPORT_COLUMN_COUNT = 8;
private static final int UNDERTAKE_UNIT_EXPORT_COLUMN_COUNT = 6;
private static final int UNDERTAKE_UNIT_SECOND_TITLE_ROW_INDEX = 1;
@Inject
private ProposalCommonService proposalCommonService;
@@ -411,14 +413,14 @@ public class ProposalExportServiceImpl extends BaseServiceImpl<ProposalInfo> imp
LinkedHashSet<String> masterUnitNames = getUnitNameSet(proposalRow, "masterUnitNames");
LinkedHashSet<String> slaveUnitNames = getUnitNameSet(proposalRow, "slaveUnitNames");
NutMap excelRow = NutMap.NEW()
.addv("code", proposalRow.getString("code"))
.addv("caseFilingCode", proposalRow.getString("caseFilingCode"))
// 承办单位表统一展示为立案编号未生成立案编号的工作建议回退显示提案编号
.addv("caseFilingCode", StrUtil.blankToDefault(proposalRow.getString("caseFilingCode"),
proposalRow.getString("code")))
.addv("name", proposalRow.getString("name"))
.addv("createUserName", proposalRow.getString("createUserName"))
.addv("masterUnitNames", "")
.addv("slaveUnitNames", "")
.addv("workSuggestionUnitNames", "")
.addv("remark", "");
.addv("workSuggestionUnitNames", "");
if ("CONFIRM_FILING".equals(proposalRow.getString("caseFilingResult"))) {
excelRow.put("masterUnitNames", String.join("", masterUnitNames));
excelRow.put("slaveUnitNames", String.join("", slaveUnitNames));
@@ -432,14 +434,12 @@ public class ProposalExportServiceImpl extends BaseServiceImpl<ProposalInfo> imp
}
if (!suggestionRows.isEmpty()) {
filingRows.add(NutMap.NEW()
.addv("code", WORK_SUGGESTION_SECTION_MARKER)
.addv("caseFilingCode", "")
.addv("caseFilingCode", WORK_SUGGESTION_SECTION_MARKER)
.addv("name", "")
.addv("createUserName", "")
.addv("masterUnitNames", "")
.addv("slaveUnitNames", "")
.addv("workSuggestionUnitNames", "")
.addv("remark", ""));
.addv("workSuggestionUnitNames", ""));
filingRows.addAll(suggestionRows);
}
return filingRows;
@@ -450,14 +450,12 @@ public class ProposalExportServiceImpl extends BaseServiceImpl<ProposalInfo> imp
*/
private Workbook buildUndertakeUnitWorkbook(String title, String unitName, List<NutMap> excelRows) {
List<ExcelExportEntity> exportEntities = new ArrayList<>();
exportEntities.add(new ExcelExportEntity("案编号", "code", 18));
exportEntities.add(new ExcelExportEntity("立案编号", "caseFilingCode", 18));
exportEntities.add(new ExcelExportEntity("提案名称", "name", 45));
exportEntities.add(new ExcelExportEntity("提案人", "createUserName", 15));
exportEntities.add(new ExcelExportEntity("办单位", "masterUnitNames", 28));
exportEntities.add(new ExcelExportEntity("协办单位", "slaveUnitNames", 28));
exportEntities.add(new ExcelExportEntity("工作建议送达单位", "workSuggestionUnitNames", 32));
exportEntities.add(new ExcelExportEntity("备注", "remark", 20));
exportEntities.add(new ExcelExportEntity("案编号", "caseFilingCode", 11));
exportEntities.add(new ExcelExportEntity("提案名称", "name", 43));
exportEntities.add(new ExcelExportEntity("提案", "createUserName", 13));
exportEntities.add(new ExcelExportEntity("主办单位", "masterUnitNames", 19));
exportEntities.add(new ExcelExportEntity("办单位", "slaveUnitNames", 25));
exportEntities.add(new ExcelExportEntity("工作建议送达单位", "workSuggestionUnitNames", 17));
for (ExcelExportEntity exportEntity : exportEntities) {
exportEntity.setWrap(true);
}
@@ -468,14 +466,59 @@ public class ProposalExportServiceImpl extends BaseServiceImpl<ProposalInfo> imp
exportParams.setSecondTitle("" + unitName + "");
exportParams.setSheetName("提案承办单位表");
exportParams.setHeaderHeight(30);
exportParams.setHeight((short) 24);
exportParams.setHeight((short) 13);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, exportEntities, excelRows);
mergeWorkSuggestionSection(workbook);
applyUndertakeUnitWorkbookStyle(workbook);
return workbook;
}
/**
* 查找工作建议标记行将八个单元格合并并设置为居中的分组标题
* 为承办单位表的全部有效单元格设置细实线边框并保证第二行承办单位名称加粗且水平垂直居中
* 相同原始样式复用同一个克隆样式避免按单元格重复创建样式导致工作簿样式数量过多
*/
private void applyUndertakeUnitWorkbookStyle(Workbook workbook) {
Sheet sheet = workbook.getSheetAt(0);
Map<String, CellStyle> borderedStyles = new HashMap<>();
Font unitNameFont = workbook.createFont();
unitNameFont.setFontName("宋体");
unitNameFont.setFontHeightInPoints((short) 11);
unitNameFont.setBold(true);
for (int rowIndex = 0; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
Row row = sheet.getRow(rowIndex);
if (row == null) {
continue;
}
boolean centerUnitName = rowIndex == UNDERTAKE_UNIT_SECOND_TITLE_ROW_INDEX;
for (int columnIndex = 0; columnIndex < UNDERTAKE_UNIT_EXPORT_COLUMN_COUNT; columnIndex++) {
Cell cell = row.getCell(columnIndex);
if (cell == null) {
cell = row.createCell(columnIndex);
}
CellStyle sourceStyle = cell.getCellStyle();
String styleKey = sourceStyle.getIndex() + ":" + centerUnitName;
CellStyle borderedStyle = borderedStyles.get(styleKey);
if (borderedStyle == null) {
borderedStyle = workbook.createCellStyle();
borderedStyle.cloneStyleFrom(sourceStyle);
borderedStyle.setBorderTop(BorderStyle.THIN);
borderedStyle.setBorderRight(BorderStyle.THIN);
borderedStyle.setBorderBottom(BorderStyle.THIN);
borderedStyle.setBorderLeft(BorderStyle.THIN);
if (centerUnitName) {
borderedStyle.setAlignment(HorizontalAlignment.CENTER);
borderedStyle.setVerticalAlignment(VerticalAlignment.CENTER);
borderedStyle.setFont(unitNameFont);
}
borderedStyles.put(styleKey, borderedStyle);
}
cell.setCellStyle(borderedStyle);
}
}
}
/**
* 查找工作建议标记行将承办单位表的全部单元格合并并设置为居中的分组标题
*/
private void mergeWorkSuggestionSection(Workbook workbook) {
Sheet sheet = workbook.getSheetAt(0);
@@ -577,47 +620,37 @@ public class ProposalExportServiceImpl extends BaseServiceImpl<ProposalInfo> imp
@Override
public void exportProposalStatisticsAsZip(ProposalQueryComprehensiveParam pageForm, HttpServletResponse response) {
Sql sql = Sqls.create("""
SELECT
info.id,
info.code,
info.name
FROM
proposal_info info
LEFT JOIN proposal_type type on type.id = info.typeId
LEFT JOIN teacher_congress_session tcs on tcs.id = info.sessionId
LEFT JOIN teacher_congress_delegation tcd on tcd.id = info.delegationId
LEFT JOIN teacher_congress_delegate tcde on tcde.loginName = info.createUserLoginName
$condition
""");
Cnd cnd = Cnd.NEW();
ProposalQueryComprehensiveParam.buildSearch(cnd, pageForm);
sql.setCondition(cnd);
List<NutMap> list = listMap(sql);
List<NutMap> list = queryProposalBaseList(pageForm);
if (Lang.isEmpty(list)) {
throw new BaseException("当前筛选条件下没有可导出的提案数据");
}
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ZipOutputStream zipOutputStream = new ZipOutputStream(bos);
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ZipOutputStream zipOutputStream = new ZipOutputStream(bos)) {
Map<String, Integer> entryNameCounts = new HashMap<>();
for (NutMap proposalRow : list) {
try (ByteArrayOutputStream docxByteArrayOutputStream = new ByteArrayOutputStream()) {
proposalCommonService.exportProposalAsDocx(proposalRow.getString("id"), docxByteArrayOutputStream);
ZipEntry zipEntry = new ZipEntry(proposalRow.getString("code") + "-" + sanitizeZipFileName(proposalRow.getString("name")) + ".docx");
if (docxByteArrayOutputStream.size() == 0) {
throw new BaseException("提案【{}】Word生成失败", proposalRow.getString("code"));
}
ZipEntry zipEntry = new ZipEntry(createUniqueZipDocxEntryName("", proposalRow, entryNameCounts));
zipOutputStream.putNextEntry(zipEntry);
docxByteArrayOutputStream.writeTo(zipOutputStream);
zipOutputStream.closeEntry();
} catch (IOException e) {
// 处理单个文件生成失败的情况
throw new RuntimeException("zip entry write failed, proposalId=" + proposalRow.getString("id"), e);
throw new BaseException("提案【{}】写入压缩包失败", proposalRow.getString("code"));
}
}
zipOutputStream.finish();
zipOutputStream.close();
CommonDownloadUtil.download("提案汇总压缩包.zip", bos.toByteArray(), response);
} catch (Exception e) {
e.printStackTrace();
log.error("批量导出提案表压缩包失败", e);
if (e instanceof BaseException) {
throw (BaseException) e;
}
throw new BaseException("批量导出提案表压缩包失败:{}", e.getMessage());
}
}
@@ -673,7 +706,8 @@ public class ProposalExportServiceImpl extends BaseServiceImpl<ProposalInfo> imp
LEFT JOIN proposal_type type on type.id = info.typeId
LEFT JOIN teacher_congress_session tcs on tcs.id = info.sessionId
LEFT JOIN teacher_congress_delegation tcd on tcd.id = info.delegationId
LEFT JOIN teacher_congress_delegate tcde on tcde.loginName = info.createUserLoginName
LEFT JOIN teacher_congress_delegate tcde
ON tcde.loginName = info.createUserLoginName AND tcde.sessionId = info.sessionId
LEFT JOIN proposal_reply_unit pru ON pru.proposalId = info.id
$condition
""");
@@ -713,13 +747,14 @@ public class ProposalExportServiceImpl extends BaseServiceImpl<ProposalInfo> imp
/**
* 生成 ZIP 内唯一且合法的 Word 文件名避免提案名称中的非法字符或重名条目导致整个压缩包缺文件
*
* @param prefix 文档类型前缀
* @param prefix 文档类型前缀为空时直接使用提案编号和名称
* @param proposalRow 提案基础信息
* @param entryNameCounts 已生成的基础文件名及其出现次数
* @return 可以直接用于 ZipEntry 的文件名
*/
private String createUniqueZipDocxEntryName(String prefix, NutMap proposalRow, Map<String, Integer> entryNameCounts) {
String baseName = sanitizeZipFileName(prefix + "-" + proposalRow.getString("code", "未编号") + "-" + proposalRow.getString("name", "未命名提案"));
String prefixText = StrUtil.isBlank(prefix) ? "" : prefix + "-";
String baseName = sanitizeZipFileName(prefixText + proposalRow.getString("code", "未编号") + "-" + proposalRow.getString("name", "未命名提案"));
int sameNameCount = entryNameCounts.merge(baseName, 1, Integer::sum);
return baseName + (sameNameCount > 1 ? "-" + sameNameCount : "") + ".docx";
}
@@ -3,11 +3,17 @@ package com.budwk.app.zhgh.democratic.proposal.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.engine.util.FlowUtil;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalSecond;
import com.budwk.app.zhgh.democratic.proposal.service.ProposalSecondedService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
@@ -19,6 +25,31 @@ public class ProposalSecondedServiceImpl extends BaseServiceImpl<ProposalSecond>
}
@Override
public boolean isDelegationReviewed(String proposalId) {
if (Strings.isBlank(proposalId)) {
return false;
}
// 以提案最新一次流程实例为准团长同意或不同意均截止附议退回提案人则允许后续重新邀请附议
ProcessInstance processInstance = this.dao().fetch(ProcessInstance.class,
Cnd.where(ProcessInstance::getBusinessNo, "=", proposalId)
.desc(ProcessInstance::getCreatedAt));
if (processInstance == null) {
return false;
}
return this.dao().query(ProcessTask.class,
Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstance.getId())
.and(ProcessTask::getTaskName, "=", "delegation")
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.FINISHED.getCode()))
.stream()
.anyMatch(task -> {
Integer submitType = FlowUtil.variableToDict(task.getVariable()).getInt("submitType");
return ObjectUtil.equals(submitType, ProcessSubmitTypeEnum.AGREE.getCode())
|| ObjectUtil.equals(submitType, ProcessSubmitTypeEnum.REJECT.getCode());
});
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void updateSecondRecord(String proposalId, String taskActorUserId, String opinion, Integer submitType) {
if (Strings.isBlank(proposalId) || Strings.isBlank(taskActorUserId) || submitType == null) {
return;
@@ -0,0 +1,49 @@
package com.budwk.app.zhgh.democratic.teachercongress.delegate.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.democratic.teachercongress.delegate.param.RepresentativeChangeRecordPageParam;
import com.budwk.app.zhgh.democratic.teachercongress.delegate.service.RepresentativeChangeRecordService;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
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 javax.validation.Valid;
/**
* 教代会代表变动记录页面及查询接口
*/
@IocBean
@At("/platform/jdh/db/changeRecord")
@Ok("json:full")
public class RepresentativeChangeRecordController {
@Inject
private RepresentativeChangeRecordService representativeChangeRecordService;
/**
* 打开代表变动记录页面
*/
@At("")
@Ok("beetl:/platform/zhgh/democratic/teachercongress/delegate/changeRecord/index.html")
@SaCheckPermission("sys.jdh.db.changeRecord")
public void index() {
}
/**
* 分页查询代表变动记录
*
* @param pageForm 分页关键字及组织筛选参数
* @return 代表变动记录分页结果
*/
@At
@SaCheckPermission("sys.jdh.db.changeRecord")
public Result pageData(@Valid @Param("pageForm") RepresentativeChangeRecordPageParam pageForm) {
Pagination<NutMap> pagination = representativeChangeRecordService.pageData(pageForm);
return Result.success(pagination);
}
}
@@ -27,6 +27,7 @@ import com.budwk.app.zhgh.democratic.teachercongress.delegate.service.TeacherCon
import com.budwk.app.zhgh.democratic.teachercongress.delegation.models.Teacher_congress_delegation;
import com.budwk.app.zhgh.democratic.teachercongress.delegation.models.Teacher_congress_delegation_union;
import com.budwk.app.zhgh.democratic.teachercongress.delegation.models.Teacher_congress_delegation_unit;
import com.budwk.app.zhgh.democratic.teachercongress.prepare.service.TeacherCongressSessionService;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
@@ -68,6 +69,8 @@ public class TeacherCongressDelegateManageController {
private SysRoleService sysRoleService;
@Inject
private TeacherCongressDelegateService teacherDelegateService;
@Inject
private TeacherCongressSessionService teacherCongressSessionService;
@At("")
@Ok("beetl:/platform/zhgh/democratic/teachercongress/delegate/manage/index.html")
@@ -123,12 +126,14 @@ public class TeacherCongressDelegateManageController {
.and(Sys_user_role::getTcSessionId, "=", param.getSessionId())
.and(Sys_user_role::getRoleId, "=", param.getRoleId()));
boolean roleEnable = teacherCongressSessionService.isSessionRoleEnabled(param.getSessionId());
List<Sys_user_role> userRoles = userIds.stream().map(userId -> {
Sys_user_role role = new Sys_user_role();
role.setUserId(userId);
role.setTcSessionId(param.getSessionId());
role.setTcDelegationId(param.getDelegationId());
role.setRoleId(param.getRoleId());
role.setEnable(roleEnable);
return role;
}).toList();
dao.insert(userRoles);
@@ -160,6 +165,7 @@ public class TeacherCongressDelegateManageController {
role.setTcSessionId(param.getSessionId());
role.setTcDelegationId(param.getDelegationId());
role.setRoleId(param.getRoleId());
teacherCongressSessionService.prepareSessionRole(role);
dao.insert(role);
sysUserService.clearCache();
@@ -191,34 +197,7 @@ public class TeacherCongressDelegateManageController {
@At
@SaCheckPermission("tc.delegate.manage")
public Result publicUser(@Valid TeacherCongressDelegateManagePublicUserParam param) {
List<Teacher_congress_delegation_unit> units = dao.query(Teacher_congress_delegation_unit.class, Cnd.where("sessionId", "=", param.getSessionId()).and("delegationId", "=", param.getDelegationId()));
List<String> unitIds = units.stream().map(Teacher_congress_delegation_unit::getUnitId).toList();
if (ObjectUtil.isEmpty(unitIds)) {
return Result.error("代表团没有组成单位");
}
Sql sql = Sqls.create("""
SELECT
t1.id,
t1.username AS userName,
t1.loginName AS loginName,
t2.NAME AS unitName
FROM
`sys_user` t1
LEFT JOIN sys_unit t2 ON t2.id = t1.unitId
WHERE
t1.id NOT IN ( SELECT userId FROM teacher_congress_delegate WHERE sessionId = @sessionId AND delegationId = @delegationId )
AND
t1.unitId in (@unitIds)
AND
(t1.loginname like @keyWord or username like @keyWord)
""");
sql.setParam("sessionId", param.getSessionId());
sql.setParam("delegationId", param.getDelegationId());
sql.setParam("unitIds", unitIds);
sql.setParam("keyWord", "%" + param.getKeyWord() + "%");
Pagination pagination = sysUserService.listPageMap(1, 50, sql);
return Result.success(pagination.getList());
return Result.success(teacherDelegateService.listPublicUsers(param));
}
/**
@@ -330,12 +309,14 @@ public class TeacherCongressDelegateManageController {
dao.insert(delegates);
//插入权限
boolean roleEnable = teacherCongressSessionService.isSessionRoleEnabled(sessionId);
List<Sys_user_role> sysUserRoles = delegates.stream().map(delegate -> {
Sys_user_role sysUserRole = new Sys_user_role();
sysUserRole.setUserId(delegate.getUserId());
sysUserRole.setRoleId(delegate.getRoleId());
sysUserRole.setTcSessionId(sessionId);
sysUserRole.setTcDelegationId(delegate.getDelegationId());
sysUserRole.setEnable(roleEnable);
return sysUserRole;
}).toList();
dao.insert(sysUserRoles);
@@ -464,7 +445,12 @@ public class TeacherCongressDelegateManageController {
try {
dao.insert(delegate);
dao.insert("sys_user_role", Chain.make("userId", delegate.getUserId()).add("roleId", delegate.getRoleId()).add("tcSessionId", sessionId).add("tcDelegationId", delegate.getDelegationId()));
// Excel导入也必须继承届次启用状态避免向历史届次导入代表后恢复旧权限
dao.insert("sys_user_role", Chain.make("userId", delegate.getUserId())
.add("roleId", delegate.getRoleId())
.add("tcSessionId", sessionId)
.add("tcDelegationId", delegate.getDelegationId())
.add("enable", teacherCongressSessionService.isSessionRoleEnabled(sessionId)));
sysUserService.clearCache();
} catch (Exception e) {
log.error("导入代表失败:{}", e.getMessage());
@@ -484,39 +470,7 @@ public class TeacherCongressDelegateManageController {
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tc.delegate.manage")
public Result doAllocateDelegation(String sessionId) {
List<Teacher_congress_delegate> delegateList = dao.query(Teacher_congress_delegate.class,
Cnd.where(Teacher_congress_delegate::getSessionId, "=", sessionId)
.and(Teacher_congress_delegate::getDelegationId, "is", null));
for (Teacher_congress_delegate delegate : delegateList) {
Teacher_congress_delegation_unit delegation_unit = dao.fetch(Teacher_congress_delegation_unit.class,
Cnd.where(Teacher_congress_delegation_unit::getUnitId, "=", delegate.getUnitId())
.and(Teacher_congress_delegation_unit::getSessionId, "=", sessionId));
if (ObjectUtil.isNotEmpty(delegation_unit)) {
delegate.setDelegationId(delegation_unit.getDelegationId());
dao.update(delegate);
Sys_user_role userRole = new Sys_user_role();
userRole.setTcDelegationId(delegation_unit.getDelegationId());
userRole.setUserId(delegate.getUserId());
userRole.setTcSessionId(sessionId);
Sys_role sys_role = dao.fetch(Sys_role.class, Cnd.where(Sys_role::getCode, "=", RoleConstant.TEACHER_CONGRESS_DELEGATE_FORMAL.name()));
if (sys_role.getId().equals(delegate.getRoleId())) {
delegate.setRoleId(sys_role.getId());
userRole.setRoleId(sys_role.getId());
}
Sys_role sys_role2 = dao.fetch(Sys_role.class, Cnd.where(Sys_role::getCode, "=", RoleConstant.TEACHER_CONGRESS_DELEGATE_ATTENDANCE.name()));
if (sys_role2.getId().equals(delegate.getRoleId())) {
delegate.setRoleId(sys_role2.getId());
userRole.setRoleId(sys_role2.getId());
}
userRole.setTcDelegationId(delegate.getDelegationId());
dao.insert(userRole);
}
sysUserService.clearCache();
}
return Result.success();
return Result.success(teacherDelegateService.allocateDelegations(sessionId));
}
@@ -1,27 +1,14 @@
package com.budwk.app.zhgh.democratic.teachercongress.delegate.listener;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.event.user.SysUserEvent;
import com.budwk.app.base.event.user.SysUserEventListener;
import com.budwk.app.sys.models.Sys_role;
import com.budwk.app.sys.models.Sys_user_role;
import com.budwk.app.sys.services.SysRoleService;
import com.budwk.app.sys.services.SysUserService;
import com.budwk.app.sys.views.View_user;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalConfig;
import com.budwk.app.zhgh.democratic.teachercongress.delegate.models.Teacher_congress_delegate;
import com.budwk.app.zhgh.democratic.teachercongress.delegation.models.Teacher_congress_delegation_unit;
import com.budwk.app.zhgh.democratic.teachercongress.prepare.models.Teacher_congress_session;
import com.budwk.app.zhgh.democratic.teachercongress.delegate.service.TeacherCongressDelegateService;
import com.budwk.app.zhgh.staffmanage.member.constant.MemberChangeType;
import lombok.extern.slf4j.Slf4j;
import org.nutz.aop.interceptor.async.Async;
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;
/**
* @version 1.0
* @Author zzr
@@ -30,14 +17,11 @@ import java.util.List;
* @注释
*/
@IocBean
@Slf4j
public class TeacherCongressDelegateListener implements SysUserEventListener {
@Inject
private Dao dao;
@Inject
private SysUserService sysUserService;
@Inject
private SysRoleService sysRoleService;
private TeacherCongressDelegateService teacherCongressDelegateService;
@Override
@Async
@@ -46,65 +30,11 @@ public class TeacherCongressDelegateListener implements SysUserEventListener {
return;
}
//查询当前最新届次
Teacher_congress_session session = dao.fetch(Teacher_congress_session.class, Cnd.NEW().desc(Teacher_congress_session::getStartDate));
if (session == null) {
return;
try {
// 保持原有事件范围仅将监听器内部归团逻辑下沉到事务服务
teacherCongressDelegateService.refreshCurrentDelegateOrganization(event.getUserId());
} catch (Exception e) {
log.error("刷新教代会代表组织信息失败,userId={}", event.getUserId(), e);
}
String userId = event.getUserId();
//查询校领导的单位
ProposalConfig config = dao.fetch(ProposalConfig.class, Cnd.NEW());
List<String> schoolLeaderUnitIds = config.getSchoolLeaderUnitIds();
View_user user = dao.fetch(View_user.class, Cnd.where(View_user::getId, "=", userId));
//校领导排除在外
if (StrUtil.isNotBlank(user.getUnitId()) && schoolLeaderUnitIds.contains(user.getUnitId())) {
return;
}
//查询当前代表的信息
Teacher_congress_delegate currentDelegate = dao.fetch(Teacher_congress_delegate.class, Cnd.where(Teacher_congress_delegate::getSessionId, "=", session.getId()));
if(currentDelegate == null){
return;
}
//查询当前分配的单位属于哪个代表团
String unitId = user.getUnitId();
Teacher_congress_delegation_unit unit = dao.fetch(Teacher_congress_delegation_unit.class, Cnd.where(Teacher_congress_delegation_unit::getSessionId, "=", session)
.and(Teacher_congress_delegation_unit::getUnitId, "=", unitId));
String delegationId = unit.getDelegationId();
currentDelegate.setDelegationId(delegationId);
currentDelegate.setUnitId(user.getUnitId());
currentDelegate.setUnitName(user.getUnitName());
currentDelegate.setUnionId(user.getUnionId());
currentDelegate.setUnionName(user.getUnionName());
dao.updateIgnoreNull(currentDelegate);
//角色数据
Sys_role delegateFormalRole = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATE_FORMAL.name());
Sys_role delegateAttendanceRole = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATE_ATTENDANCE.name());
Sys_role delegateSpeciallyInviteRole = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATE_SPECIALLY_INVITE.name());
Sys_role delegationHeadRole = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD.name());
//删除权限
//删除角色数据
dao.clear(Sys_user_role.class, Cnd.where(Sys_user_role::getUserId, "=", userId)
.and(Sys_user_role::getRoleId, "in", List.of(delegateFormalRole.getId(), delegateAttendanceRole.getId(), delegateSpeciallyInviteRole.getId(), delegationHeadRole.getId()))
.and(Sys_user_role::getTcSessionId, "=", session.getId())
);
//再加权限
Sys_user_role sysUserRole = new Sys_user_role();
sysUserRole.setUserId(userId);
sysUserRole.setTcSessionId(session.getId());
sysUserRole.setTcDelegationId(currentDelegate.getDelegationId());
sysUserRole.setRoleId(currentDelegate.getRoleId());
dao.insert(sysUserRole);
sysUserService.clearCache();
sysRoleService.clearCache();
}
}
@@ -0,0 +1,106 @@
package com.budwk.app.zhgh.democratic.teachercongress.delegate.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.ColDefine;
import org.nutz.dao.entity.annotation.ColType;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Name;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.dao.entity.annotation.TableMeta;
import org.nutz.dao.interceptor.annotation.PrevInsert;
/**
* 教代会届次延用时产生的代表异动记录
* 记录代表在上一届与新一届之间单位分工会及代表团的对应关系供后续异动查询使用
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("representative_change_record")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("教代会代表异动记录")
public class RepresentativeChangeRecord extends BaseModel {
@Name
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("用户ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String userId;
@Column
@Comment("原教代会届次")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String oldJdhSession;
@Column
@Comment("新教代会届次")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String newJdhSession;
@Column
@Comment("原单位ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String oldUnitId;
@Column
@Comment("原单位名称")
@ColDefine(type = ColType.VARCHAR, width = 500)
private String oldUnitName;
@Column
@Comment("原工会ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String oldUnionId;
@Column
@Comment("原工会名称")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String oldUnionName;
@Column
@Comment("原代表团ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String oldDbtId;
@Column
@Comment("原代表团名称")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String oldDbtName;
@Column
@Comment("新单位ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String newUnitId;
@Column
@Comment("新单位名称")
@ColDefine(type = ColType.VARCHAR, width = 500)
private String newUnitName;
@Column
@Comment("新工会ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String newUnionId;
@Column
@Comment("新工会名称")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String newUnionName;
@Column
@Comment("新代表团ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String newDbtId;
@Column
@Comment("新代表团名称")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String newDbtName;
}
@@ -12,6 +12,9 @@ import org.nutz.dao.interceptor.annotation.PrevInsert;
@EqualsAndHashCode(callSuper = true)
@Table("teacher_congress_delegate")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@TableIndexes({
@Index(name = "INDEX_TEACHER_CONGRESS_DELEGATE_LOGIN_SESSION", fields = {"loginName", "sessionId"}, unique = false)
})
@Comment("教代会代表")
public class Teacher_congress_delegate extends BaseModel {
@@ -0,0 +1,22 @@
package com.budwk.app.zhgh.democratic.teachercongress.delegate.param;
import com.budwk.app.base.param.PageForm;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 代表变动记录分页查询参数
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RepresentativeChangeRecordPageParam extends PageForm {
/** 新教代会届次ID。 */
private String sessionId;
/** 代表变动后的所属单位ID。 */
private String unitId;
/** 代表变动后的所属分工会ID。 */
private String unionId;
}
@@ -0,0 +1,21 @@
package com.budwk.app.zhgh.democratic.teachercongress.delegate.service;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.democratic.teachercongress.delegate.models.RepresentativeChangeRecord;
import com.budwk.app.zhgh.democratic.teachercongress.delegate.param.RepresentativeChangeRecordPageParam;
import org.nutz.lang.util.NutMap;
/**
* 教代会代表变动记录服务
*/
public interface RepresentativeChangeRecordService extends BaseService<RepresentativeChangeRecord> {
/**
* 分页查询代表变动记录并关联人员及新旧届次名称
*
* @param pageForm 分页关键字及组织筛选条件
* @return 代表变动记录分页数据
*/
Pagination<NutMap> pageData(RepresentativeChangeRecordPageParam pageForm);
}
@@ -4,6 +4,8 @@ import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.democratic.teachercongress.delegate.models.Teacher_congress_delegate;
import com.budwk.app.zhgh.democratic.teachercongress.delegate.param.TeacherCongressDelegateManagePageParam;
import com.budwk.app.zhgh.democratic.teachercongress.delegate.param.TeacherCongressDelegateManagePublicUserParam;
import org.nutz.lang.util.NutMap;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@@ -20,4 +22,27 @@ public interface TeacherCongressDelegateService extends BaseService<Teacher_cong
void exportXlsx(TeacherCongressDelegateManagePageParam pageForm, HttpServletResponse response);
void exportDocx(TeacherCongressDelegateManagePageParam pageForm, HttpServletResponse response);
/**
* 查询当前代表团组成范围内尚未成为本届代表的人员
*
* @param param 届次代表团及关键字参数
* @return 候选人员列表
*/
List<NutMap> listPublicUsers(TeacherCongressDelegateManagePublicUserParam param);
/**
* 根据系统配置的单位或分工会组成关系为尚未归团的代表自动分配代表团
*
* @param sessionId 届次ID
* @return 已分配未分配数量及未分配人员名称
*/
NutMap allocateDelegations(String sessionId);
/**
* 人员组织变化后按当前组成模式刷新其当前届次代表及角色归团信息
*
* @param userId 用户ID
*/
void refreshCurrentDelegateOrganization(String userId);
}
@@ -12,13 +12,24 @@ import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.base.utils.SysOfficeTemplateUtil;
import com.budwk.app.sys.models.Sys_role;
import com.budwk.app.sys.models.Sys_user_role;
import com.budwk.app.sys.services.SysRoleService;
import com.budwk.app.sys.services.SysUserService;
import com.budwk.app.sys.views.View_user;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import com.budwk.app.zhgh.democratic.teachercongress.delegate.models.Teacher_congress_delegate;
import com.budwk.app.zhgh.democratic.teachercongress.delegate.param.TeacherCongressDelegateManagePageParam;
import com.budwk.app.zhgh.democratic.teachercongress.delegate.param.TeacherCongressDelegateManagePublicUserParam;
import com.budwk.app.zhgh.democratic.teachercongress.delegate.vo.TeacherCongressDelegateExcelVO;
import com.budwk.app.zhgh.democratic.teachercongress.prepare.models.Teacher_congress_session;
import com.budwk.app.zhgh.democratic.teachercongress.prepare.service.TeacherCongressSessionService;
import com.budwk.app.zhgh.democratic.teachercongress.delegation.constant.TeacherCongressDelegationCompositionMode;
import com.budwk.app.zhgh.democratic.teachercongress.delegation.models.Teacher_congress_delegation_union;
import com.budwk.app.zhgh.democratic.teachercongress.delegation.models.Teacher_congress_delegation_unit;
import com.budwk.app.zhgh.democratic.teachercongress.delegation.service.TeacherCongressDelegationService;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalConfig;
import com.budwk.app.base.exception.BaseException;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
@@ -30,6 +41,8 @@ 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.ioc.aop.Aop;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.lang.util.NutMap;
import javax.servlet.http.HttpServletResponse;
@@ -39,6 +52,8 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
@IocBean(args = {"refer:dao"})
public class TeacherCongressDelegateServiceImpl extends BaseServiceImpl<Teacher_congress_delegate> implements TeacherCongressDelegateService {
@@ -52,6 +67,139 @@ public class TeacherCongressDelegateServiceImpl extends BaseServiceImpl<Teacher_
private SysOfficeTemplateUtil sysOfficeTemplateUtil;
@Inject
private ProposalCommonService proposalCommonService;
@Inject
private SysUserService sysUserService;
@Inject
private TeacherCongressSessionService teacherCongressSessionService;
@Inject
private TeacherCongressDelegationService teacherCongressDelegationService;
/**
* 查询当前组成模式覆盖的候选人员并按整个届次排除已存在代表防止一人重复加入多个代表团
*/
@Override
public List<NutMap> listPublicUsers(TeacherCongressDelegateManagePublicUserParam param) {
TeacherCongressDelegationCompositionMode mode = teacherCongressDelegationService.getCompositionMode();
List<String> organizationIds;
if (mode == TeacherCongressDelegationCompositionMode.UNION) {
organizationIds = dao().query(Teacher_congress_delegation_union.class,
Cnd.where(Teacher_congress_delegation_union::getSessionId, "=", param.getSessionId())
.and(Teacher_congress_delegation_union::getDelegationId, "=", param.getDelegationId()))
.stream().map(Teacher_congress_delegation_union::getUnionId).toList();
} else {
organizationIds = dao().query(Teacher_congress_delegation_unit.class,
Cnd.where(Teacher_congress_delegation_unit::getSessionId, "=", param.getSessionId())
.and(Teacher_congress_delegation_unit::getDelegationId, "=", param.getDelegationId()))
.stream().map(Teacher_congress_delegation_unit::getUnitId).toList();
}
if (organizationIds.isEmpty()) {
throw new BaseException("代表团没有组成" + mode.getLabel());
}
String organizationColumn = mode == TeacherCongressDelegationCompositionMode.UNION ? "u.unionId" : "u.unitId";
Sql sql = Sqls.create("SELECT u.id,u.username AS userName,u.loginname AS loginName,u.unitName,u.unionName " +
"FROM vw_user u WHERE u.id NOT IN (SELECT userId FROM teacher_congress_delegate WHERE sessionId=@sessionId) " +
"AND " + organizationColumn + " IN (@organizationIds) " +
"AND (u.loginname LIKE @keyword OR u.username LIKE @keyword) ORDER BY u.loginname LIMIT 50");
sql.setParam("sessionId", param.getSessionId());
sql.setParam("organizationIds", organizationIds);
sql.setParam("keyword", "%" + param.getKeyWord() + "%");
return listMap(sql);
}
/**
* 为当前届次未归团代表批量解析代表团同时刷新其届次代表角色避免重复生成角色关系
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public NutMap allocateDelegations(String sessionId) {
List<Teacher_congress_delegate> delegates = query(Cnd.where(Teacher_congress_delegate::getSessionId, "=", sessionId)
.and(Teacher_congress_delegate::getDelegationId, "is", null));
List<String> unassignedNames = new ArrayList<>();
int assignedCount = 0;
for (Teacher_congress_delegate delegate : delegates) {
String delegationId = teacherCongressDelegationService.resolveDelegationId(sessionId, delegate.getUnitId(), delegate.getUnionId());
if (StrUtil.isBlank(delegationId)) {
unassignedNames.add(delegate.getUserName() + "" + delegate.getLoginName() + "");
continue;
}
delegate.setDelegationId(delegationId);
updateIgnoreNull(delegate);
replaceDelegateRole(delegate);
assignedCount++;
}
sysUserService.clearCache();
return NutMap.NEW().addv("assignedCount", assignedCount)
.addv("unassignedCount", unassignedNames.size()).addv("unassignedNames", unassignedNames);
}
/**
* 人员组织变更事件只处理当前开启届次代表身份保留代表团负责人角色在跨团时清理
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public void refreshCurrentDelegateOrganization(String userId) {
Teacher_congress_session session = dao().fetch(Teacher_congress_session.class,
Cnd.where(Teacher_congress_session::getEnable, "=", true).desc(Teacher_congress_session::getStartDate));
if (session == null) {
return;
}
View_user user = dao().fetch(View_user.class, Cnd.where(View_user::getId, "=", userId));
if (user == null) {
return;
}
ProposalConfig config = dao().fetch(ProposalConfig.class, Cnd.NEW());
if (config != null && config.getSchoolLeaderUnitIds() != null
&& StrUtil.isNotBlank(user.getUnitId()) && config.getSchoolLeaderUnitIds().contains(user.getUnitId())) {
return;
}
Teacher_congress_delegate delegate = fetch(Cnd.where(Teacher_congress_delegate::getSessionId, "=", session.getId())
.and(Teacher_congress_delegate::getUserId, "=", userId));
if (delegate == null) {
return;
}
String oldDelegationId = delegate.getDelegationId();
String newDelegationId = teacherCongressDelegationService.resolveDelegationId(session.getId(), user.getUnitId(), user.getUnionId());
delegate.setDelegationId(newDelegationId);
delegate.setUnitId(user.getUnitId());
delegate.setUnitName(user.getUnitName());
delegate.setUnionId(user.getUnionId());
delegate.setUnionName(user.getUnionName());
delegate.setMobile(user.getMobile());
update(delegate);
replaceDelegateRole(delegate);
if (!java.util.Objects.equals(oldDelegationId, newDelegationId)) {
clearDelegationManagerRoles(userId, session.getId());
}
sysUserService.clearCache();
sysRoleService.clearCache();
}
/** 替换代表本届次的同一身份角色,确保角色中的代表团ID与代表表一致。 */
private void replaceDelegateRole(Teacher_congress_delegate delegate) {
dao().clear(Sys_user_role.class, Cnd.where(Sys_user_role::getUserId, "=", delegate.getUserId())
.and(Sys_user_role::getRoleId, "=", delegate.getRoleId())
.and(Sys_user_role::getTcSessionId, "=", delegate.getSessionId()));
Sys_user_role role = new Sys_user_role();
role.setUserId(delegate.getUserId());
role.setRoleId(delegate.getRoleId());
role.setTcSessionId(delegate.getSessionId());
role.setTcDelegationId(delegate.getDelegationId());
teacherCongressSessionService.prepareSessionRole(role);
dao().insert(role);
}
/** 跨代表团后清理团长、副团长及联络人角色,避免保留原代表团管理权限。 */
private void clearDelegationManagerRoles(String userId, String sessionId) {
Set<String> roleIds = new HashSet<>();
roleIds.add(sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD).getId());
roleIds.add(sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_VICE_DELEGATION_HEAD).getId());
roleIds.add(sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_CONTACT).getId());
dao().clear(Sys_user_role.class, Cnd.where(Sys_user_role::getUserId, "=", userId)
.and(Sys_user_role::getTcSessionId, "=", sessionId)
.and(Sys_user_role::getRoleId, "in", roleIds));
}
@Override
public Pagination<Teacher_congress_delegate> pageData(TeacherCongressDelegateManagePageParam pageForm) {
@@ -0,0 +1,96 @@
package com.budwk.app.zhgh.democratic.teachercongress.delegate.service.impl;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.democratic.teachercongress.delegate.models.RepresentativeChangeRecord;
import com.budwk.app.zhgh.democratic.teachercongress.delegate.param.RepresentativeChangeRecordPageParam;
import com.budwk.app.zhgh.democratic.teachercongress.delegate.service.RepresentativeChangeRecordService;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import java.util.Map;
/**
* 教代会代表变动记录服务实现
*/
@IocBean(args = {"refer:dao"})
public class RepresentativeChangeRecordServiceImpl extends BaseServiceImpl<RepresentativeChangeRecord>
implements RepresentativeChangeRecordService {
/** 前端排序字段与查询字段映射,避免将未校验字段直接拼入 SQL。 */
private static final Map<String, String> SORT_COLUMNS = Map.of(
"username", "u.username",
"loginname", "u.loginname",
"oldJdhName", "oldSession.fullName",
"newJdhName", "newSession.fullName",
"oldUnitName", "c.oldUnitName",
"newUnitName", "c.newUnitName",
"oldDbtName", "c.oldDbtName",
"newDbtName", "c.newDbtName"
);
public RepresentativeChangeRecordServiceImpl(Dao dao) {
super(dao);
}
/**
* 分页查询代表变动记录并关联当前人员姓名工号及新旧届次名称
*
* @param pageForm 分页关键字及组织筛选条件
* @return 可直接供列表展示的分页 Map 数据
*/
@Override
@SuppressWarnings("unchecked")
public Pagination<NutMap> pageData(RepresentativeChangeRecordPageParam pageForm) {
Sql sql = Sqls.create("""
SELECT
c.*,
u.username,
u.loginname,
oldSession.fullName AS oldJdhName,
newSession.fullName AS newJdhName
FROM representative_change_record c
LEFT JOIN sys_user u ON c.userId = u.id
LEFT JOIN teacher_congress_session oldSession ON oldSession.id = c.oldJdhSession
LEFT JOIN teacher_congress_session newSession ON newSession.id = c.newJdhSession
$condition
""");
sql.setCondition(buildCondition(pageForm));
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
}
/**
* 组装代表变动记录查询条件单位和分工会均按变动后的组织信息筛选保持与原系统口径一致
*
* @param pageForm 页面查询参数
* @return SQL 查询条件及安全排序规则
*/
private Cnd buildCondition(RepresentativeChangeRecordPageParam pageForm) {
Cnd cnd = Cnd.NEW();
cnd.andEX("c.newJdhSession", "=", pageForm.getSessionId());
cnd.andEX("c.newUnionId", "=", pageForm.getUnionId());
cnd.andEX("c.newUnitId", "=", pageForm.getUnitId());
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
String keyword = "%" + pageForm.getSearchKeyword().trim() + "%";
cnd.and(Cnd.exps("u.username", "like", keyword)
.or("u.loginname", "like", keyword));
}
String sortColumn = SORT_COLUMNS.get(pageForm.getPageOrderName());
if (StrUtil.isNotBlank(sortColumn)) {
if ("ascending".equalsIgnoreCase(pageForm.getPageOrderBy())) {
cnd.asc(sortColumn);
} else {
cnd.desc(sortColumn);
}
} else {
cnd.desc("c.createdAt");
}
return cnd;
}
}
@@ -0,0 +1,41 @@
package com.budwk.app.zhgh.democratic.teachercongress.delegation.constant;
/**
* 代表团组成模式
*
* <p>系统参数 {@code dbtzc} {@code unit} 时按单位组成
* {@code union} 时按分工会组成空值或非法值统一兼容为单位模式</p>
*/
public enum TeacherCongressDelegationCompositionMode {
UNIT("unit", "单位"),
UNION("union", "分工会");
private final String code;
private final String label;
TeacherCongressDelegationCompositionMode(String code, String label) {
this.code = code;
this.label = label;
}
public String getCode() {
return code;
}
public String getLabel() {
return label;
}
/**
* 将系统参数转换为组成模式非法配置按历史默认行为使用单位模式
*
* @param value 系统参数值
* @return 有效组成模式
*/
public static TeacherCongressDelegationCompositionMode from(String value) {
if (UNION.code.equalsIgnoreCase(value)) {
return UNION;
}
return UNIT;
}
}
@@ -170,45 +170,18 @@ public class TeacherCongressDelegationController {
@At
@SaCheckPermission("tc.delegation")
public Result partUnitTransferData(@Valid String delegationId, @Valid String sessionId) {
//找出本届次已经设置了代表团的所有单位
List<Teacher_congress_delegation_unit> delegationUnits = dao.query(Teacher_congress_delegation_unit.class, Cnd.where("sessionId", "=", sessionId));
List<String> unitIds = delegationUnits.stream().map(Teacher_congress_delegation_unit::getUnitId).toList();
//找出本代表团的单位
List<String> selectUnitIds = delegationUnits.stream().filter(unit -> unit.getDelegationId().equals(delegationId)).map(Teacher_congress_delegation_unit::getUnitId).toList();
SqlExpressionGroup seg = new SqlExpressionGroup();
seg.orEX("id", "not in", unitIds);
seg.orEX("id", "in", selectUnitIds);
// Cnd cnd = Cnd.where("unitLevel", "=", 2);
Cnd cnd = Cnd.where("unitTypeCode", "=", "1");
if (!seg.isEmpty()) {
cnd.and(seg);
}
cnd.asc("unitcode");
List<Sys_unit> units = dao.query(Sys_unit.class, cnd);
NutMap transferData = NutMap.NEW().addv("selectUnitIds", selectUnitIds).addv("allUnits", units);
return Result.success(transferData);
NutMap data = teacherCongressDelegationService.getCompositionTransferData(sessionId, delegationId);
// 保留旧接口字段兼容未同步更新的历史页面调用
data.put("selectUnitIds", data.get("selectedIds"));
data.put("allUnits", data.get("allItems"));
return Result.success(data);
}
@At
@SaCheckPermission("tc.delegation")
@Aop(TransAop.READ_COMMITTED)
public Result partUnitSet(@Valid String delegationId, @Valid String sessionId, @Param("unitIds") String[] unitIds) {
dao.clear(Teacher_congress_delegation_unit.class, Cnd.where("delegationId", "=", delegationId).and("sessionId", "=", sessionId));
List<Teacher_congress_delegation_unit> insertData = Arrays.stream(unitIds).map(unitId -> {
Teacher_congress_delegation_unit delegationUnit = new Teacher_congress_delegation_unit();
delegationUnit.setUnitId(unitId);
delegationUnit.setDelegationId(delegationId);
delegationUnit.setSessionId(sessionId);
return delegationUnit;
}).toList();
dao.insert(insertData);
teacherCongressDelegationService.saveComposition(sessionId, delegationId, unitIds);
return Result.success();
}
@@ -222,24 +195,35 @@ public class TeacherCongressDelegationController {
@At
@SaCheckPermission("tc.delegation")
public Result partUnitPageData(@Valid PageForm pageForm, @Valid String sessionId, @Valid String delegationId) {
Sql sql = Sqls.create("""
SELECT
t2.id,
t2.`name`
FROM
teacher_congress_delegation_unit t1
LEFT JOIN sys_unit t2 ON t2.id = t1.unitId
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("t1.delegationId", "=", delegationId);
cnd.and("t1.sessionId", "=", sessionId);
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
cnd.where().andLike("t2.name", pageForm.getSearchKeyword());
}
sql.setCondition(cnd);
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
return Result.success(teacherCongressDelegationService.pageComposition(pageForm, sessionId, delegationId));
}
/**
* 获取当前配置模式下的代表团组成选择数据
*/
@At
@SaCheckPermission("tc.delegation")
public Result compositionTransferData(@Valid String delegationId, @Valid String sessionId) {
return Result.success(teacherCongressDelegationService.getCompositionTransferData(sessionId, delegationId));
}
/**
* 保存当前配置模式下的代表团组成关系
*/
@At
@SaCheckPermission("tc.delegation")
public Result compositionSet(@Valid String delegationId, @Valid String sessionId, @Param("itemIds") String[] itemIds) {
teacherCongressDelegationService.saveComposition(sessionId, delegationId, itemIds);
return Result.success();
}
/**
* 分页查询当前配置模式下的代表团组成关系
*/
@At
@SaCheckPermission("tc.delegation")
public Result compositionPageData(@Valid PageForm pageForm, @Valid String sessionId, @Valid String delegationId) {
return Result.success(teacherCongressDelegationService.pageComposition(pageForm, sessionId, delegationId));
}
/**

Some files were not shown because too many files have changed in this diff Show More