Merge remote-tracking branch 'origin/main'

This commit is contained in:
2026-08-19 16:43:47 +08:00
52 changed files with 591 additions and 128 deletions
@@ -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());
}
@@ -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)
@@ -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,94 @@ public class SysClubExamineServiceImpl extends BaseServiceImpl<SysClubExamineReg
return clubExamineVo;
}
/**
* 将旧版年审活动和计划字段转换为查看页统一使用的V4字段,避免历史数据在页面中显示为空。
*
* @param examineVo 年审详情
*/
private void normalizeActivityData(ClubExamineVo examineVo) {
examineVo.setSummaryFiles(normalizeSummaryFiles(examineVo.getSummaryFiles()));
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文件预览组件所需的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);
@@ -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);
@@ -402,6 +402,10 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
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);
}
@@ -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;
@@ -75,6 +76,7 @@ public class ProposalExportServiceImpl extends BaseServiceImpl<ProposalInfo> imp
private static final String WORK_SUGGESTION_SECTION_MARKER = "__WORK_SUGGESTION_SECTION__";
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;
@@ -448,12 +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("立案编号", "caseFilingCode", 18));
exportEntities.add(new ExcelExportEntity("提案名称", "name", 65));
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("立案编号", "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);
}
@@ -467,9 +469,54 @@ public class ProposalExportServiceImpl extends BaseServiceImpl<ProposalInfo> imp
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);
}
}
}
/**
* 查找工作建议标记行,将承办单位表的全部单元格合并并设置为居中的分组标题。
*/