commit
This commit is contained in:
+31
@@ -7,6 +7,7 @@ import com.budwk.app.base.param.ExportTableColumns;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.zhgh.democratic.proposal.param.ProposalQueryComprehensiveParam;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.ProposalExportService;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.ProposalWriteService;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -32,6 +33,8 @@ public class ProposalExportComprehensiveController {
|
||||
private ProposalExportService proposalExportService;
|
||||
@Inject
|
||||
private ProposalCommonService proposalCommonService;
|
||||
@Inject
|
||||
private ProposalWriteService proposalWriteService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/democratic/proposal/export/comprehensive/index.html")
|
||||
@@ -83,6 +86,19 @@ public class ProposalExportComprehensiveController {
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询综合导出页面当前教代会届次配置的提案类别。
|
||||
*
|
||||
* @param sessionId 教代会届次ID,用于读取该届次配置的提案类别
|
||||
* @return VO 列表,每项包含筛选使用的 id 和页面展示使用的 name
|
||||
*/
|
||||
@At
|
||||
@SaCheckPermission("proposal.export.comprehensive")
|
||||
@ApiOperation("查询当前届次提案类别")
|
||||
public Result listSessionProposalTypes(@Valid String sessionId) {
|
||||
return Result.success(proposalWriteService.listSessionProposalTypes(sessionId));
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@@ -92,6 +108,21 @@ public class ProposalExportComprehensiveController {
|
||||
proposalExportService.exportSummaryAsExcel(pageForm, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按办理单位导出提案承办单位表压缩包。
|
||||
*
|
||||
* @param pageForm 综合导出页面查询参数,其中 sessionId 为必传的教代会届次ID
|
||||
* @param response HTTP 响应,返回内容为包含多个 XSSF Excel 的 ZIP 文件
|
||||
*/
|
||||
@At
|
||||
@Ok("void")
|
||||
@ApiOperation("按办理单位导出提案承办单位表压缩包")
|
||||
@SaCheckPermission("proposal.query.comprehensive")
|
||||
public void exportUndertakeUnitTablesAsZip(@Valid @Param("pageForm") ProposalQueryComprehensiveParam pageForm,
|
||||
HttpServletResponse response) {
|
||||
proposalExportService.exportUndertakeUnitTablesAsZip(pageForm, response);
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
|
||||
+16
@@ -76,6 +76,19 @@ public class ProposalWriteController {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前教代会届次可用于撰写提案的类型。
|
||||
*
|
||||
* @param sessionId 教代会届次ID
|
||||
* @return VO 列表,每项包含保存使用的 id 和页面展示使用的 name
|
||||
*/
|
||||
@At
|
||||
@SaCheckPermission("proposal.write")
|
||||
@ApiOperation("查询届次可用提案类型")
|
||||
public Result listSessionProposalTypes(@Valid String sessionId) {
|
||||
return Result.success(proposalWriteService.listSessionProposalTypes(sessionId));
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.write")
|
||||
@@ -84,6 +97,7 @@ public class ProposalWriteController {
|
||||
@SLog(tag = "提案管理系统-我的提案", msg = "保存提案")
|
||||
public Result save(@Param("info") ProposalInfo proposalInfo) {
|
||||
proposalWriteService.checkCurrentUserDelegate(proposalInfo.getSessionId());
|
||||
proposalWriteService.validateProposalType(proposalInfo.getSessionId(), proposalInfo.getTypeId());
|
||||
if (StrUtil.isBlank(proposalInfo.getCode())) {
|
||||
proposalInfo.setCode(proposalWriteService.generateProposalCode(proposalInfo.getSessionId(), proposalInfo.getDelegationId()));
|
||||
}
|
||||
@@ -100,6 +114,7 @@ public class ProposalWriteController {
|
||||
@SLog(tag = "提案管理系统-我的提案", msg = "提交提案")
|
||||
public Result submit(@Param("info") ProposalInfo proposalInfo) {
|
||||
proposalWriteService.checkCurrentUserDelegate(proposalInfo.getSessionId());
|
||||
proposalWriteService.validateProposalType(proposalInfo.getSessionId(), proposalInfo.getTypeId());
|
||||
if (StrUtil.isBlank(proposalInfo.getCode())) {
|
||||
proposalInfo.setCode(proposalWriteService.generateProposalCode(proposalInfo.getSessionId(), proposalInfo.getDelegationId()));
|
||||
}
|
||||
@@ -130,6 +145,7 @@ public class ProposalWriteController {
|
||||
@ApiOperation("重新提交提案")
|
||||
@SLog(tag = "提案管理系统-我的提案", msg = "重新提交提案")
|
||||
public Result submitAgain(@Param("info") ProposalInfo proposalInfo, @Param("taskId") Long taskId) {
|
||||
proposalWriteService.validateProposalType(proposalInfo.getSessionId(), proposalInfo.getTypeId());
|
||||
dao.insertOrUpdate(proposalInfo);
|
||||
|
||||
Dict dict = Dict.create();
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.budwk.app.zhgh.democratic.proposal.controller.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 撰写提案时当前教代会届次允许选择的提案类型。
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@ApiModel("届次可用提案类型")
|
||||
public class ProposalWriteTypeVO {
|
||||
|
||||
@ApiModelProperty("提案类型ID,保存提案时写入 proposal_info.typeId")
|
||||
private Integer id;
|
||||
|
||||
@ApiModelProperty("提案类型名称,来源于当前教代会届次的提案类型配置")
|
||||
private String name;
|
||||
}
|
||||
@@ -25,7 +25,7 @@ public class ProposalType extends BaseModel {
|
||||
|
||||
@Column
|
||||
@Comment("名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 10)
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String name;
|
||||
|
||||
@Column
|
||||
|
||||
+8
@@ -34,6 +34,14 @@ public interface ProposalExportService extends BaseService<ProposalInfo> {
|
||||
*/
|
||||
void exportSummaryAsExcel(ProposalQueryComprehensiveParam pageForm, HttpServletResponse response);
|
||||
|
||||
/**
|
||||
* 按办理单位导出提案承办单位表压缩包。
|
||||
*
|
||||
* @param pageForm 综合导出页面查询参数;sessionId 应传当前教代会届次ID,其他条件沿用页面查询条件
|
||||
* @param response HTTP 响应,方法直接输出 ZIP;ZIP 中每个有提案的单位对应一个 XSSF Excel
|
||||
*/
|
||||
void exportUndertakeUnitTablesAsZip(ProposalQueryComprehensiveParam pageForm, HttpServletResponse response);
|
||||
|
||||
/**
|
||||
* 导出提案立案汇总表excel
|
||||
*
|
||||
|
||||
+17
@@ -2,6 +2,7 @@ package com.budwk.app.zhgh.democratic.proposal.service;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.sys.models.Sys_dict;
|
||||
import com.budwk.app.zhgh.democratic.proposal.controller.vo.ProposalWriteTypeVO;
|
||||
import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo;
|
||||
|
||||
import javax.validation.Valid;
|
||||
@@ -10,6 +11,22 @@ import java.util.Map;
|
||||
|
||||
public interface ProposalWriteService extends BaseService<ProposalInfo> {
|
||||
|
||||
/**
|
||||
* 查询指定教代会届次允许选择的提案类型。
|
||||
*
|
||||
* @param sessionId 教代会届次ID
|
||||
* @return 提案类型 VO 列表,id 用于保存,name 用于页面展示
|
||||
*/
|
||||
List<ProposalWriteTypeVO> listSessionProposalTypes(String sessionId);
|
||||
|
||||
/**
|
||||
* 校验所选提案类型是否属于当前教代会届次。
|
||||
*
|
||||
* @param sessionId 教代会届次ID
|
||||
* @param typeId 提案类型ID
|
||||
*/
|
||||
void validateProposalType(String sessionId, Integer typeId);
|
||||
|
||||
List<Sys_dict> listSource(String sessionId);
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -158,7 +158,7 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
|
||||
pages.put("preAudit", standardOrderColumns());
|
||||
pages.put("committeeFiling", 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("caseCheck", orderColumns("code", "info.code", "name", "info.name", "createUserName", "info.createUserName", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "curTaskName", "curTaskName", "instanceState", "ins.state", "finishTime", "t.finishTime"));
|
||||
pages.put("exportComprehensive", orderColumns("code", "info.code", "name", "info.name", "createUserName", "info.createUserName", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "caseFilingResult", "info.caseFilingResult", "caseFilingType", "info.caseFilingType", "merge", "merge", "brief", "info.brief", "measures", "info.measures", "undertakeUnits", "masterUnitName", "curTaskName", "curTaskName", "instanceState", "ins.state"));
|
||||
pages.put("exportComprehensive", orderColumns("code", "info.code", "caseFilingCode", "info.caseFilingCode", "name", "info.name", "createUserName", "info.createUserName", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "caseFilingResult", "info.caseFilingResult", "caseFilingType", "info.caseFilingType", "merge", "merge", "brief", "info.brief", "measures", "info.measures", "undertakeUnits", "masterUnitName", "curTaskName", "curTaskName", "instanceState", "ins.state"));
|
||||
pages.put("exportSingleCustom", standardOrderColumns());
|
||||
pages.put("configUnit", orderColumns("name", "t1.name", "code", "t1.code", "unitLeader", "u2.username"));
|
||||
pages.put("configType", orderColumns("name", "name", "code", "code"));
|
||||
|
||||
+248
@@ -4,8 +4,10 @@ import cn.afterturn.easypoi.excel.ExcelExportUtil;
|
||||
import cn.afterturn.easypoi.excel.entity.ExportParams;
|
||||
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
|
||||
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.param.ExportTableColumns;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||
@@ -27,7 +29,15 @@ 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.Cell;
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
import org.apache.poi.ss.usermodel.Font;
|
||||
import org.apache.poi.ss.usermodel.HorizontalAlignment;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.VerticalAlignment;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
import org.apache.poi.xwpf.usermodel.BreakType;
|
||||
import org.apache.poi.xwpf.usermodel.IBodyElement;
|
||||
import org.apache.poi.xwpf.usermodel.XWPFDocument;
|
||||
@@ -51,6 +61,8 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -61,6 +73,9 @@ import java.util.zip.ZipOutputStream;
|
||||
@Slf4j
|
||||
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;
|
||||
|
||||
@Inject
|
||||
private ProposalCommonService proposalCommonService;
|
||||
@Inject
|
||||
@@ -246,6 +261,239 @@ public class ProposalExportServiceImpl extends BaseServiceImpl<ProposalInfo> imp
|
||||
CommonDownloadUtil.download(title + ".xlsx", workbook, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按办理单位生成提案承办单位表。查询时忽略页面单个承办单位条件,其他查询条件继续生效;
|
||||
* 同一提案涉及多个单位时,会进入每个相关单位的 Excel,并保留完整主办、协办单位信息。
|
||||
*
|
||||
* @param pageForm 综合导出页面查询参数,sessionId 必须为当前教代会届次ID
|
||||
* @param response HTTP 响应,直接输出包含多个 XSSF Excel 的 ZIP 文件
|
||||
*/
|
||||
@Override
|
||||
public void exportUndertakeUnitTablesAsZip(ProposalQueryComprehensiveParam pageForm, HttpServletResponse response) {
|
||||
if (pageForm == null || StrUtil.isBlank(pageForm.getSessionId())) {
|
||||
throw new BaseException("请选择教代会");
|
||||
}
|
||||
Teacher_congress_session session = dao().fetch(Teacher_congress_session.class, pageForm.getSessionId());
|
||||
if (session == null) {
|
||||
throw new BaseException("教代会届次不存在");
|
||||
}
|
||||
|
||||
List<NutMap> unitRows = queryUndertakeUnitExportRows(pageForm);
|
||||
if (Lang.isEmpty(unitRows)) {
|
||||
throw new BaseException("暂无可导出的承办单位提案数据");
|
||||
}
|
||||
|
||||
Map<String, NutMap> proposalRows = new LinkedHashMap<>();
|
||||
Map<String, String> unitNames = new LinkedHashMap<>();
|
||||
Map<String, LinkedHashSet<String>> unitProposalIds = new LinkedHashMap<>();
|
||||
for (NutMap row : unitRows) {
|
||||
String proposalId = row.getString("id");
|
||||
String unitId = row.getString("unitId");
|
||||
if (StrUtil.isBlank(proposalId) || StrUtil.isBlank(unitId)) {
|
||||
continue;
|
||||
}
|
||||
String unitName = StrUtil.blankToDefault(row.getString("unitName"), "未命名单位");
|
||||
NutMap proposalRow = proposalRows.computeIfAbsent(proposalId, key -> NutMap.NEW()
|
||||
.addv("id", proposalId)
|
||||
.addv("code", row.getString("code"))
|
||||
.addv("caseFilingCode", row.getString("caseFilingCode"))
|
||||
.addv("name", row.getString("name"))
|
||||
.addv("createUserName", row.getString("createUserName"))
|
||||
.addv("caseFilingResult", row.getString("caseFilingResult"))
|
||||
.addv("masterUnitNames", new LinkedHashSet<String>())
|
||||
.addv("slaveUnitNames", new LinkedHashSet<String>()));
|
||||
getUnitNameSet(proposalRow, Boolean.TRUE.equals(row.getBoolean("isMaster"))
|
||||
? "masterUnitNames" : "slaveUnitNames").add(unitName);
|
||||
unitNames.putIfAbsent(unitId, unitName);
|
||||
unitProposalIds.computeIfAbsent(unitId, key -> new LinkedHashSet<>()).add(proposalId);
|
||||
}
|
||||
if (unitProposalIds.isEmpty()) {
|
||||
throw new BaseException("暂无可导出的承办单位提案数据");
|
||||
}
|
||||
|
||||
String title = StrUtil.format("{}第{}教职工代表大会第{}会议提案承办单位表",
|
||||
Globals.AppName, session.getJ(), session.getC());
|
||||
ByteArrayOutputStream zipBytes = new ByteArrayOutputStream();
|
||||
Map<String, Integer> zipEntryNameCounts = new HashMap<>();
|
||||
try (ZipOutputStream zipOutputStream = new ZipOutputStream(zipBytes)) {
|
||||
for (Map.Entry<String, LinkedHashSet<String>> unitEntry : unitProposalIds.entrySet()) {
|
||||
String unitName = unitNames.get(unitEntry.getKey());
|
||||
List<NutMap> excelRows = buildUndertakeUnitExcelRows(unitEntry.getValue(), proposalRows);
|
||||
try (Workbook workbook = buildUndertakeUnitWorkbook(title, unitName, excelRows);
|
||||
ByteArrayOutputStream workbookBytes = new ByteArrayOutputStream()) {
|
||||
workbook.write(workbookBytes);
|
||||
String baseEntryName = "提案承办单位表-" + sanitizeZipFileName(unitName);
|
||||
int sameNameCount = zipEntryNameCounts.merge(baseEntryName, 1, Integer::sum);
|
||||
String entryName = baseEntryName + (sameNameCount > 1 ? "-" + sameNameCount : "") + ".xlsx";
|
||||
zipOutputStream.putNextEntry(new ZipEntry(entryName));
|
||||
workbookBytes.writeTo(zipOutputStream);
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
}
|
||||
zipOutputStream.finish();
|
||||
} catch (IOException e) {
|
||||
log.error("按办理单位导出提案承办单位表失败:{}", e.getMessage(), e);
|
||||
throw new BaseException("导出提案承办单位表失败");
|
||||
}
|
||||
CommonDownloadUtil.download(sanitizeZipFileName(title) + ".zip", zipBytes.toByteArray(), response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前届次已确定立案或作为工作建议的提案及其全部办理单位。
|
||||
* 页面承办单位单选条件会被清空,避免只生成一个单位文件;其他页面条件继续沿用。
|
||||
*/
|
||||
private List<NutMap> queryUndertakeUnitExportRows(ProposalQueryComprehensiveParam pageForm) {
|
||||
ProposalQueryComprehensiveParam exportPageForm = new ProposalQueryComprehensiveParam();
|
||||
BeanUtil.copyProperties(pageForm, exportPageForm);
|
||||
exportPageForm.setUnderTakeUnitId(null);
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT DISTINCT
|
||||
info.id,
|
||||
info.code,
|
||||
info.caseFilingCode,
|
||||
info.name,
|
||||
info.createUserName,
|
||||
info.caseFilingResult,
|
||||
pru.unitId,
|
||||
pru.unitName,
|
||||
pru.isMaster
|
||||
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
|
||||
INNER JOIN proposal_reply_unit pru ON pru.proposalId = info.id
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.where("info.sessionId", "=", pageForm.getSessionId());
|
||||
cnd.and("info.caseFilingResult", "in", List.of("CONFIRM_FILING", "SUGGESTION"));
|
||||
ProposalQueryComprehensiveParam.buildSearch(cnd, exportPageForm);
|
||||
cnd.asc("info.code");
|
||||
cnd.asc("pru.unitName");
|
||||
sql.setCondition(cnd);
|
||||
return listMap(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将某个办理单位涉及的提案整理为 Excel 行。确定立案在前,工作建议通过标记行单独分组。
|
||||
*/
|
||||
private List<NutMap> buildUndertakeUnitExcelRows(LinkedHashSet<String> proposalIds,
|
||||
Map<String, NutMap> proposalRows) {
|
||||
List<NutMap> filingRows = new ArrayList<>();
|
||||
List<NutMap> suggestionRows = new ArrayList<>();
|
||||
for (String proposalId : proposalIds) {
|
||||
NutMap proposalRow = proposalRows.get(proposalId);
|
||||
if (proposalRow == null) {
|
||||
continue;
|
||||
}
|
||||
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("name", proposalRow.getString("name"))
|
||||
.addv("createUserName", proposalRow.getString("createUserName"))
|
||||
.addv("masterUnitNames", "")
|
||||
.addv("slaveUnitNames", "")
|
||||
.addv("workSuggestionUnitNames", "")
|
||||
.addv("remark", "");
|
||||
if ("CONFIRM_FILING".equals(proposalRow.getString("caseFilingResult"))) {
|
||||
excelRow.put("masterUnitNames", String.join(",", masterUnitNames));
|
||||
excelRow.put("slaveUnitNames", String.join(",", slaveUnitNames));
|
||||
filingRows.add(excelRow);
|
||||
} else {
|
||||
LinkedHashSet<String> workSuggestionUnitNames = new LinkedHashSet<>(masterUnitNames);
|
||||
workSuggestionUnitNames.addAll(slaveUnitNames);
|
||||
excelRow.put("workSuggestionUnitNames", String.join(",", workSuggestionUnitNames));
|
||||
suggestionRows.add(excelRow);
|
||||
}
|
||||
}
|
||||
if (!suggestionRows.isEmpty()) {
|
||||
filingRows.add(NutMap.NEW()
|
||||
.addv("code", WORK_SUGGESTION_SECTION_MARKER)
|
||||
.addv("caseFilingCode", "")
|
||||
.addv("name", "")
|
||||
.addv("createUserName", "")
|
||||
.addv("masterUnitNames", "")
|
||||
.addv("slaveUnitNames", "")
|
||||
.addv("workSuggestionUnitNames", "")
|
||||
.addv("remark", ""));
|
||||
filingRows.addAll(suggestionRows);
|
||||
}
|
||||
return filingRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建单个办理单位的 XSSF Excel,并把工作建议标记行合并为分组标题。
|
||||
*/
|
||||
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));
|
||||
for (ExcelExportEntity exportEntity : exportEntities) {
|
||||
exportEntity.setWrap(true);
|
||||
}
|
||||
|
||||
ExportParams exportParams = new ExportParams();
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
exportParams.setTitle(title);
|
||||
exportParams.setSecondTitle("(" + unitName + ")");
|
||||
exportParams.setSheetName("提案承办单位表");
|
||||
exportParams.setHeaderHeight(30);
|
||||
exportParams.setHeight((short) 24);
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, exportEntities, excelRows);
|
||||
mergeWorkSuggestionSection(workbook);
|
||||
return workbook;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找工作建议标记行,将八个单元格合并并设置为居中的分组标题。
|
||||
*/
|
||||
private void mergeWorkSuggestionSection(Workbook workbook) {
|
||||
Sheet sheet = workbook.getSheetAt(0);
|
||||
for (Row row : sheet) {
|
||||
Cell firstCell = row.getCell(0);
|
||||
if (firstCell == null || !WORK_SUGGESTION_SECTION_MARKER.equals(firstCell.toString())) {
|
||||
continue;
|
||||
}
|
||||
CellStyle sectionStyle = workbook.createCellStyle();
|
||||
sectionStyle.cloneStyleFrom(firstCell.getCellStyle());
|
||||
sectionStyle.setAlignment(HorizontalAlignment.CENTER);
|
||||
sectionStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
Font sectionFont = workbook.createFont();
|
||||
sectionFont.setFontName("宋体");
|
||||
sectionFont.setFontHeightInPoints((short) 11);
|
||||
sectionFont.setBold(true);
|
||||
sectionStyle.setFont(sectionFont);
|
||||
for (int columnIndex = 0; columnIndex < UNDERTAKE_UNIT_EXPORT_COLUMN_COUNT; columnIndex++) {
|
||||
Cell cell = row.getCell(columnIndex);
|
||||
if (cell == null) {
|
||||
cell = row.createCell(columnIndex);
|
||||
}
|
||||
cell.setCellValue(columnIndex == 0 ? "作为工作建议的提案" : "");
|
||||
cell.setCellStyle(sectionStyle);
|
||||
}
|
||||
row.setHeightInPoints(24);
|
||||
sheet.addMergedRegion(new CellRangeAddress(row.getRowNum(), row.getRowNum(), 0,
|
||||
UNDERTAKE_UNIT_EXPORT_COLUMN_COUNT - 1));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取提案行中用于去重且保持顺序的办理单位名称集合。 */
|
||||
@SuppressWarnings("unchecked")
|
||||
private LinkedHashSet<String> getUnitNameSet(NutMap proposalRow, String key) {
|
||||
return (LinkedHashSet<String>) proposalRow.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exportProposalRegisterSummaryAsExcel(ProposalQueryComprehensiveParam pageForm, HttpServletResponse response) {
|
||||
Sql sql = exportComprehensiveSql(pageForm);
|
||||
|
||||
+68
@@ -4,6 +4,7 @@ import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
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.sys.models.Sys_dict;
|
||||
import com.budwk.app.sys.models.Sys_user;
|
||||
@@ -11,6 +12,7 @@ import com.budwk.app.sys.services.SysDictService;
|
||||
import com.budwk.app.sys.services.SysUserService;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.democratic.proposal.constants.ProposalState;
|
||||
import com.budwk.app.zhgh.democratic.proposal.controller.vo.ProposalWriteTypeVO;
|
||||
import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo;
|
||||
import com.budwk.app.zhgh.democratic.proposal.models.ProposalType;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.ProposalWriteService;
|
||||
@@ -24,6 +26,7 @@ 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.Strings;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -44,6 +47,70 @@ public class ProposalWriteServiceImpl extends BaseServiceImpl<ProposalInfo> impl
|
||||
super(dao);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProposalWriteTypeVO> listSessionProposalTypes(String sessionId) {
|
||||
List<String> typeNames = getSessionProposalTypeNames(sessionId);
|
||||
if (typeNames.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<ProposalType> proposalTypes = dao().query(ProposalType.class,
|
||||
Cnd.where(ProposalType::getName, "in", typeNames));
|
||||
List<ProposalWriteTypeVO> result = new ArrayList<>();
|
||||
for (String typeName : typeNames) {
|
||||
ProposalType proposalType = proposalTypes.stream()
|
||||
.filter(item -> typeName.equals(item.getName()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (proposalType != null) {
|
||||
result.add(new ProposalWriteTypeVO(proposalType.getId(), proposalType.getName()));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateProposalType(String sessionId, Integer typeId) {
|
||||
if (typeId == null) {
|
||||
throw new BaseException("请选择提案类型");
|
||||
}
|
||||
ProposalType proposalType = dao().fetch(ProposalType.class, typeId);
|
||||
if (proposalType == null || !getSessionProposalTypeNames(sessionId).contains(proposalType.getName())) {
|
||||
throw new BaseException("所选提案类型不属于当前教代会届次");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析指定届次配置的提案类型。标准数据为 JSON 数组,同时兼容历史单值字符串。
|
||||
*
|
||||
* @param sessionId 教代会届次ID
|
||||
* @return 按届次配置顺序排列的提案类型名称;未配置时返回空列表
|
||||
*/
|
||||
private List<String> getSessionProposalTypeNames(String sessionId) {
|
||||
if (StrUtil.isBlank(sessionId)) {
|
||||
throw new BaseException("所属教代会不能为空");
|
||||
}
|
||||
Teacher_congress_session session = dao().fetch(Teacher_congress_session.class, sessionId);
|
||||
if (session == null) {
|
||||
throw new BaseException("所属教代会不存在");
|
||||
}
|
||||
if (StrUtil.isBlank(session.getProposalType())) {
|
||||
return List.of();
|
||||
}
|
||||
String proposalTypeValue = StrUtil.trim(session.getProposalType());
|
||||
if (!proposalTypeValue.startsWith("[")) {
|
||||
return List.of(proposalTypeValue);
|
||||
}
|
||||
try {
|
||||
return Json.fromJsonAsList(String.class, proposalTypeValue).stream()
|
||||
.map(StrUtil::trim)
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.distinct()
|
||||
.toList();
|
||||
} catch (Exception e) {
|
||||
throw new BaseException("教代会届次提案类型配置格式不正确");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Sys_dict> listSource(String sessionId) {
|
||||
Sql sql = Sqls.create("""
|
||||
@@ -224,6 +291,7 @@ public class ProposalWriteServiceImpl extends BaseServiceImpl<ProposalInfo> impl
|
||||
throw new RuntimeException("没有开启的教代会");
|
||||
}
|
||||
Teacher_congress_session teacherCongressSession = teacherCongressSessions.get(0);
|
||||
validateProposalType(teacherCongressSession.getId(), proposalTypeObj.getId());
|
||||
|
||||
// 保存到数据库
|
||||
ProposalInfo proposalInfo = new ProposalInfo();
|
||||
|
||||
+1
-11
@@ -1,7 +1,6 @@
|
||||
package com.budwk.app.zhgh.democratic.teachercongress.prepare.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
@@ -87,16 +86,7 @@ public class TeacherCongressSessionController {
|
||||
@SaCheckPermission("tc.prepare.session")
|
||||
@ApiOperation(value = "修改届次信息")
|
||||
public Result update(@Valid Teacher_congress_session session) {
|
||||
int count = dao.count(Teacher_congress_session.class,
|
||||
Cnd.where(Teacher_congress_session::getJ, "=", session.getJ())
|
||||
.and(Teacher_congress_session::getC, "=", session.getC())
|
||||
.and(Teacher_congress_session::getId, "!=", session.getId())
|
||||
);
|
||||
if (count > 0) {
|
||||
throw new BaseException("请勿重复创建");
|
||||
}
|
||||
session.setFullName("第" + session.getJ() + "第" + session.getC());
|
||||
dao.updateIgnoreNull(session);
|
||||
teacherCongressSessionService.updateSession(session);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -44,10 +44,10 @@ public class Teacher_congress_session extends BaseModel {
|
||||
private String fullName;
|
||||
|
||||
@Column
|
||||
@Comment("提案类型")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
@Comment("提案类型(JSON数组)")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 1000)
|
||||
@NotBlank(message = "提案类型不能为空")
|
||||
@Size(max = 50, message = "提案类型最多50个字")
|
||||
@Size(max = 1000, message = "提案类型数据不能超过1000个字符")
|
||||
private String proposalType;
|
||||
|
||||
@Column
|
||||
|
||||
+7
@@ -20,5 +20,12 @@ public interface TeacherCongressSessionService extends BaseService<Teacher_congr
|
||||
|
||||
void insertSession(Teacher_congress_session session, boolean isExtend);
|
||||
|
||||
/**
|
||||
* 修改届次基础信息并统一校验多选提案类型。
|
||||
*
|
||||
* @param session 届次信息,其中 proposalType 为 JSON 字符串数组
|
||||
*/
|
||||
void updateSession(Teacher_congress_session session);
|
||||
|
||||
void deleteSession(String id);
|
||||
}
|
||||
|
||||
+124
-1
@@ -16,6 +16,7 @@ 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.ProposalType;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.delegate.models.Teacher_congress_delegate;
|
||||
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;
|
||||
@@ -36,10 +37,13 @@ import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.random.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -66,7 +70,17 @@ public class TeacherCongressSessionServiceImpl extends BaseServiceImpl<Teacher_c
|
||||
"WHERE proposalType IS NOT NULL AND TRIM(proposalType) <> '' ORDER BY proposalType");
|
||||
sql.setCallback(Sqls.callback.strList());
|
||||
dao().execute(sql);
|
||||
return sql.getList(String.class).stream()
|
||||
LinkedHashSet<String> proposalTypes = new LinkedHashSet<>();
|
||||
for (String proposalTypeValue : sql.getList(String.class)) {
|
||||
for (String proposalType : parseProposalTypes(proposalTypeValue)) {
|
||||
String normalizedProposalType = StrUtil.trim(proposalType);
|
||||
if (StrUtil.isNotBlank(normalizedProposalType)) {
|
||||
proposalTypes.add(normalizedProposalType);
|
||||
}
|
||||
}
|
||||
}
|
||||
return proposalTypes.stream()
|
||||
.sorted()
|
||||
.map(TeacherCongressSessionProposalTypeVO::new)
|
||||
.toList();
|
||||
}
|
||||
@@ -74,6 +88,7 @@ public class TeacherCongressSessionServiceImpl extends BaseServiceImpl<Teacher_c
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void insertSession(Teacher_congress_session session, boolean isExtend) {
|
||||
normalizeAndValidateProposalTypes(session);
|
||||
int count = count(Cnd.where(Teacher_congress_session::getJ, "=", session.getJ())
|
||||
.and(Teacher_congress_session::getC, "=", session.getC()));
|
||||
if (count > 0) {
|
||||
@@ -257,6 +272,114 @@ public class TeacherCongressSessionServiceImpl extends BaseServiceImpl<Teacher_c
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改届次基础信息。提案类型在保存前统一完成 JSON 解析、去重和长度校验,
|
||||
* 避免新增与编辑使用不同的数据格式。
|
||||
*
|
||||
* @param session 届次信息,其中 proposalType 应传 JSON 字符串数组
|
||||
*/
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void updateSession(Teacher_congress_session session) {
|
||||
normalizeAndValidateProposalTypes(session);
|
||||
int count = count(Cnd.where(Teacher_congress_session::getJ, "=", session.getJ())
|
||||
.and(Teacher_congress_session::getC, "=", session.getC())
|
||||
.and(Teacher_congress_session::getId, "!=", session.getId()));
|
||||
if (count > 0) {
|
||||
throw new BaseException("请勿重复创建");
|
||||
}
|
||||
session.setFullName("第" + session.getJ() + "第" + session.getC());
|
||||
dao().updateIgnoreNull(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将多选提案类型规范化为 JSON 数组字符串。每个类型去除首尾空格、去重且最多 50 个字符,
|
||||
* 整体序列化结果最多 1000 个字符。
|
||||
*
|
||||
* @param session 待新增或修改的届次实体
|
||||
*/
|
||||
private void normalizeAndValidateProposalTypes(Teacher_congress_session session) {
|
||||
List<String> values = parseProposalTypes(session.getProposalType());
|
||||
LinkedHashSet<String> normalizedValues = new LinkedHashSet<>();
|
||||
for (String value : values) {
|
||||
String normalizedValue = StrUtil.trim(value);
|
||||
if (StrUtil.isBlank(normalizedValue)) {
|
||||
continue;
|
||||
}
|
||||
if (normalizedValue.length() > 50) {
|
||||
throw new BaseException("单个提案类型不能超过50个字符");
|
||||
}
|
||||
normalizedValues.add(normalizedValue);
|
||||
}
|
||||
if (normalizedValues.isEmpty()) {
|
||||
throw new BaseException("请至少选择或输入一个提案类型");
|
||||
}
|
||||
|
||||
String proposalTypeJson = Json.toJson(new ArrayList<>(normalizedValues));
|
||||
if (proposalTypeJson.length() > 1000) {
|
||||
throw new BaseException("提案类型数据不能超过1000个字符");
|
||||
}
|
||||
session.setProposalType(proposalTypeJson);
|
||||
syncProposalTypes(normalizedValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将届次中新输入的类型同步到全局提案类型表,以便继续使用 proposal_info.typeId
|
||||
* 以及现有列表、统计和导出的关联查询。已有同名类型直接复用,不删除历史类型。
|
||||
*
|
||||
* @param proposalTypeNames 当前届次规范化、去重后的提案类型名称
|
||||
*/
|
||||
private void syncProposalTypes(LinkedHashSet<String> proposalTypeNames) {
|
||||
ProposalType lastType = dao().fetch(ProposalType.class, Cnd.NEW().desc(ProposalType::getSort));
|
||||
int nextSort = lastType == null || lastType.getSort() == null ? 1 : lastType.getSort() + 1;
|
||||
for (String proposalTypeName : proposalTypeNames) {
|
||||
ProposalType proposalType = dao().fetch(ProposalType.class,
|
||||
Cnd.where(ProposalType::getName, "=", proposalTypeName));
|
||||
if (proposalType != null) {
|
||||
continue;
|
||||
}
|
||||
ProposalType newProposalType = new ProposalType();
|
||||
newProposalType.setCode(generateProposalTypeCode());
|
||||
newProposalType.setName(proposalTypeName);
|
||||
newProposalType.setSort(nextSort++);
|
||||
dao().insert(newProposalType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成不超过 10 个字符的内部提案类型编码。
|
||||
*
|
||||
* @return 以 TC 开头且在 proposal_type 表中未使用的编码
|
||||
*/
|
||||
private String generateProposalTypeCode() {
|
||||
String code;
|
||||
do {
|
||||
code = "TC" + R.UU32().substring(0, 8).toUpperCase();
|
||||
} while (dao().count(ProposalType.class, Cnd.where(ProposalType::getCode, "=", code)) > 0);
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析届次中保存的提案类型。标准数据为 JSON 字符串数组,同时兼容调整前可能存在的单值数据。
|
||||
*
|
||||
* @param proposalTypeValue 数据库字段值或前端提交的 JSON 字符串
|
||||
* @return 提案类型字符串列表;空值返回空列表
|
||||
*/
|
||||
private List<String> parseProposalTypes(String proposalTypeValue) {
|
||||
if (StrUtil.isBlank(proposalTypeValue)) {
|
||||
return List.of();
|
||||
}
|
||||
String value = proposalTypeValue.trim();
|
||||
if (!value.startsWith("[")) {
|
||||
return List.of(value);
|
||||
}
|
||||
try {
|
||||
return Json.fromJsonAsList(String.class, value);
|
||||
} catch (Exception e) {
|
||||
throw new BaseException("提案类型数据格式不正确");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void deleteSession(String id) {
|
||||
|
||||
Reference in New Issue
Block a user