人才培养方案

This commit is contained in:
2026-09-20 11:59:04 +08:00
parent e4f4720113
commit 9f0c0ac873
9 changed files with 280 additions and 48 deletions
@@ -1,5 +1,6 @@
package com.roomroot.jwgl.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
@@ -33,12 +34,12 @@ public class ZYB {
@TableField("专业方向")
private String zyfx;
/** 学年制 */
@TableField("学年制")
/** 学年制(达梦非空;空串会被当成 NULL,插入时必须有值) */
@TableField(value = "学年制", fill = FieldFill.INSERT, insertStrategy = FieldStrategy.ALWAYS)
private String xnz;
/** 学期数 */
@TableField("学期数")
@TableField(value = "学期数", fill = FieldFill.INSERT, insertStrategy = FieldStrategy.ALWAYS)
private Integer xqs;
/** 专业备注 */
@@ -109,26 +110,26 @@ public class ZYB {
@TableField("简称")
private String jc;
/** 节次类别 */
@TableField("节次类别")
private String jclb;
/** 系统模式 */
@TableField("系统模式")
private String xtms;
/** JSONZD NVARCHAR(4000) */
@TableField("JSONZD")
private String jsonzd;
/** 主干专业 */
@TableField("主干专业")
@TableField(value = "主干专业", fill = FieldFill.INSERT, insertStrategy = FieldStrategy.ALWAYS)
private Boolean zgzy;
/** 规范名称 */
@TableField("规范名称")
/** 规范名称(达梦非空,缺省用专业名称) */
@TableField(value = "规范名称", fill = FieldFill.INSERT, insertStrategy = FieldStrategy.ALWAYS)
private String gfmc;
/** 节次类别 */
@TableField(value = "节次类别", fill = FieldFill.INSERT, insertStrategy = FieldStrategy.ALWAYS)
private String jclb;
/** 系统模式 */
@TableField(value = "系统模式", fill = FieldFill.INSERT, insertStrategy = FieldStrategy.ALWAYS)
private String xtms;
/** 教学大纲编号 */
@TableField("教学大纲编号")
private String jxdgbh;
@@ -0,0 +1,111 @@
package com.roomroot.jwgl.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.roomroot.jwgl.entity.ZYB;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
/**
* 专业表插入时补齐达梦非空列。达梦将空串视为 NULL,缺省值必须是非空文本或数字。
*/
@Component
public class ZybInsertFillHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
Object origin = metaObject.getOriginalObject();
if (!(origin instanceof ZYB)) {
return;
}
ZYB entity = (ZYB) origin;
fillNotNullColumns(entity);
strictInsertFill(metaObject, "xnz", String.class, entity.getXnz());
strictInsertFill(metaObject, "xqs", Integer.class, entity.getXqs());
strictInsertFill(metaObject, "gfmc", String.class, entity.getGfmc());
strictInsertFill(metaObject, "jclb", String.class, entity.getJclb());
strictInsertFill(metaObject, "xtms", String.class, entity.getXtms());
strictInsertFill(metaObject, "zgzy", Boolean.class, entity.getZgzy());
}
@Override
public void updateFill(MetaObject metaObject) {
// 仅插入补齐
}
/** @deprecated 使用 {@link #fillNotNullColumns(ZYB)} */
public static void fillAcademicYears(ZYB entity) {
fillNotNullColumns(entity);
}
public static void fillNotNullColumns(ZYB entity) {
if (entity == null) {
return;
}
String name = blankToNull(entity.getZymc());
String xnz = blankToNull(entity.getXnz());
if (xnz == null) {
if (entity.getXqs() != null && entity.getXqs() > 0) {
xnz = String.valueOf(Math.max(1, (entity.getXqs() + 1) / 2));
} else {
xnz = "4";
}
}
entity.setXnz(xnz);
if (entity.getXqs() == null) {
int years = 4;
try {
years = (int) Math.round(Double.parseDouble(xnz));
} catch (NumberFormatException ignored) {
years = 4;
}
entity.setXqs(Math.max(1, years) * 2);
}
if (blankToNull(entity.getGfmc()) == null) {
entity.setGfmc(name != null ? name : "-");
}
if (blankToNull(entity.getZybsh()) == null && blankToNull(entity.getZydh()) != null) {
entity.setZybsh(entity.getZydh());
}
if (blankToNull(entity.getJc()) == null) {
entity.setJc(name != null ? name : "-");
}
if (blankToNull(entity.getZyfx()) == null) {
entity.setZyfx("-");
}
if (blankToNull(entity.getZybb()) == null) {
entity.setZybb("-");
}
if (blankToNull(entity.getPxlx2()) == null) {
entity.setPxlx2("其他");
}
if (blankToNull(entity.getXylb()) == null) {
entity.setXylb("其他");
}
if (blankToNull(entity.getJclb()) == null) {
entity.setJclb("双节次");
}
if (blankToNull(entity.getXtms()) == null) {
entity.setXtms("军校模式");
}
if (blankToNull(entity.getZdyfl()) == null) {
entity.setZdyfl("-");
}
if (blankToNull(entity.getZygf()) == null) {
entity.setZygf("-");
}
if (entity.getZgzy() == null) {
entity.setZgzy(false);
}
if (entity.getTy() == null) {
entity.setTy(false);
}
}
private static String blankToNull(String value) {
if (value == null) {
return null;
}
String trimmed = value.trim();
return trimmed.isEmpty() ? null : trimmed;
}
}
@@ -63,6 +63,9 @@ public interface TrainingProgramService {
/** 导出培养方案(专业)到 Excel(.xlsx) */
void exportZYBExcel(jakarta.servlet.http.HttpServletResponse response) throws Exception;
/** 下载培养方案目录导入模板(仅表头) */
void exportZYBTemplate(jakarta.servlet.http.HttpServletResponse response) throws Exception;
/**
* 查询未停用的教学管理机构下拉选项。
*/
@@ -104,6 +104,7 @@ public class DisciplineServiceImpl implements DisciplineService {
}
assertMajorCodeUnique(entity.getZydh());
fillMajorDefaults(entity);
com.roomroot.jwgl.handler.ZybInsertFillHandler.fillNotNullColumns(entity);
zybMapper.insert(entity);
}
@@ -284,6 +285,7 @@ public class DisciplineServiceImpl implements DisciplineService {
if (StringUtils.isEmpty(entity.getGfmc())) {
entity.setGfmc(entity.getZymc());
}
com.roomroot.jwgl.handler.ZybInsertFillHandler.fillNotNullColumns(entity);
}
/**
@@ -44,6 +44,10 @@ public class TrainingProgramServiceImpl implements TrainingProgramService {
}
entity.setTy(false);
entity.setQysj(LocalDateTime.now());
com.roomroot.jwgl.handler.ZybInsertFillHandler.fillNotNullColumns(entity);
if (findExistingMajor(entity) != null) {
throw new RuntimeException("专业名称已存在,请勿重复新增");
}
zybMapper.insert(entity);
}
@@ -90,24 +94,84 @@ public class TrainingProgramServiceImpl implements TrainingProgramService {
if (list.isEmpty()) {
throw new RuntimeException("Excel中无有效数据");
}
int inserted = 0;
int updated = 0;
int rowNo = 1;
for (ZYB entity : list) {
if (entity.getZydh() == null || entity.getZydh().isEmpty()) {
entity.setZydh(com.roomroot.jwgl.utils.UuidUtil.getUUID());
rowNo++;
if (entity.getZymc() == null || entity.getZymc().trim().isEmpty()) {
throw new RuntimeException("" + rowNo + "行:专业名称不能为空");
}
if (entity.getTy() == null) {
entity.setZymc(entity.getZymc().trim());
if (entity.getZydh() != null) {
entity.setZydh(entity.getZydh().trim());
if (entity.getZydh().isEmpty()) {
entity.setZydh(null);
}
}
com.roomroot.jwgl.handler.ZybInsertFillHandler.fillNotNullColumns(entity);
ZYB existing = findExistingMajor(entity);
if (existing != null) {
entity.setZydh(existing.getZydh());
if (entity.getZybsh() == null || entity.getZybsh().isEmpty()) {
entity.setZybsh(existing.getZybsh() != null ? existing.getZybsh() : existing.getZydh());
}
entity.setTy(false);
zybMapper.updateById(entity);
updated++;
} else {
if (entity.getZydh() == null || entity.getZydh().isEmpty()) {
entity.setZydh(com.roomroot.jwgl.utils.UuidUtil.getUUID());
}
if (entity.getZybsh() == null || entity.getZybsh().isEmpty()) {
entity.setZybsh(entity.getZydh());
}
entity.setTy(false);
entity.setQysj(LocalDateTime.now());
zybMapper.insert(entity);
inserted++;
}
zybMapper.insert(entity);
}
return list.size();
return inserted + updated;
}
/**
* 按主键或专业名称命中已有记录,避免触发 PK_专业名称表。
*/
private ZYB findExistingMajor(ZYB entity) {
if (entity.getZydh() != null && !entity.getZydh().isEmpty()) {
ZYB byId = zybMapper.selectById(entity.getZydh());
if (byId != null) {
return byId;
}
}
java.util.List<ZYB> sameName = zybMapper.selectList(new LambdaQueryWrapper<ZYB>()
.eq(ZYB::getZymc, entity.getZymc()));
if (sameName == null || sameName.isEmpty()) {
return null;
}
for (ZYB row : sameName) {
boolean fxSame = java.util.Objects.equals(
nullToEmpty(row.getZyfx()), nullToEmpty(entity.getZyfx()));
boolean lxSame = java.util.Objects.equals(
nullToEmpty(row.getPxlx()), nullToEmpty(entity.getPxlx()));
if (fxSame && lxSame) {
return row;
}
}
return sameName.get(0);
}
private static String nullToEmpty(String value) {
return value == null ? "" : value.trim();
}
@Override
public void exportZYBExcel(jakarta.servlet.http.HttpServletResponse response) throws Exception {
java.util.List<ZYB> list = zybMapper.selectList(
new LambdaQueryWrapper<ZYB>().orderByAsc(ZYB::getZydh));
String[] headers = {"专业代号", "专业名称", "专业代码", "专业版本", "培训类型", "培训层次",
"学年制", "学期数", "专业方向", "专业备注", "学员类别", "简称", "培训类型2"};
String[] headers = com.roomroot.jwgl.utils.ExcelParseUtil.TRAINING_PROGRAM_IMPORT_HEADERS;
byte[] bytes = com.roomroot.jwgl.utils.ExcelExportUtil.export("培养方案", headers, list,
ZYB::getZydh, ZYB::getZymc, ZYB::getZydm, ZYB::getZybb, ZYB::getPxlx,
ZYB::getPxcc, ZYB::getXnz, ZYB::getXqs, ZYB::getZyfx, ZYB::getZybz,
@@ -120,6 +184,19 @@ public class TrainingProgramServiceImpl implements TrainingProgramService {
response.getOutputStream().flush();
}
@Override
public void exportZYBTemplate(jakarta.servlet.http.HttpServletResponse response) throws Exception {
String[] headers = com.roomroot.jwgl.utils.ExcelParseUtil.TRAINING_PROGRAM_IMPORT_HEADERS;
byte[] bytes = com.roomroot.jwgl.utils.ExcelExportUtil.export("培养方案", headers,
java.util.Collections.emptyList());
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition",
"attachment; filename=" + java.net.URLEncoder.encode(
"人才培养方案目录模板.xlsx", java.nio.charset.StandardCharsets.UTF_8));
response.getOutputStream().write(bytes);
response.getOutputStream().flush();
}
@Override
public java.util.List<JXGLJG> listTeachingOrganizations() {
java.util.List<JXGLJG> list = jxgljgMapper.selectEnabledOptions();
@@ -450,11 +450,17 @@ public class ExcelParseUtil {
return true;
}
/**
* 人才培养方案(专业表)导入模板列头。下载模板与解析共用,按列名匹配,顺序可调整。
*/
public static final String[] TRAINING_PROGRAM_IMPORT_HEADERS = {
"专业代号", "专业名称", "专业代码", "专业版本", "培训类型", "培训层次",
"学年制", "学期数", "专业方向", "专业备注", "学员类别", "简称", "培训类型2"
};
/**
* 解析培养方案(专业表)导入 Excel 文件(支持 .xls / .xlsx)。
* <p>表头顺序:</p>
* 专业代号, 专业名称, 专业代码, 专业版本, 培训类型, 培训层次, 学年制, 学期数,
* 专业方向, 专业备注, 学员类别, 简称, 培训类型2
* <p>按 {@link #TRAINING_PROGRAM_IMPORT_HEADERS} 列名匹配;未识别表头则拒绝导入。</p>
*
* @return 专业实体列表
* @throws Exception 解析异常
@@ -464,29 +470,53 @@ public class ExcelParseUtil {
Workbook workbook = createWorkbook(inputStream, fileName);
try {
Sheet sheet = workbook.getSheetAt(0);
int lastRowNum = sheet.getLastRowNum();
for (int rowNum = 1; rowNum <= lastRowNum; rowNum++) {
int firstRowNum = sheet.getFirstRowNum();
Row headerRow = sheet.getRow(firstRowNum);
if (headerRow == null) {
throw new IllegalArgumentException("导入文件缺少表头行,请先下载「人才培养方案目录模板」填写");
}
Map<String, Integer> headerIndex = new LinkedHashMap<>();
for (int col = headerRow.getFirstCellNum(); col < headerRow.getLastCellNum(); col++) {
String title = normalizeHeader(getCellStringValue(headerRow.getCell(col)));
if (!title.isEmpty() && !headerIndex.containsKey(title)) {
headerIndex.put(title, col);
}
}
int matched = 0;
for (String header : TRAINING_PROGRAM_IMPORT_HEADERS) {
if (headerIndex.containsKey(header)) {
matched++;
}
}
if (matched == 0) {
throw new IllegalArgumentException(
"未识别到人才培养方案目录模板表头,请下载「人才培养方案目录模板」并按表头填写(须含学年制、学期数等列)");
}
if (!headerIndex.containsKey("学年制") && headerIndex.containsKey("学制")) {
headerIndex.put("学年制", headerIndex.get("学制"));
}
for (int rowNum = firstRowNum + 1; rowNum <= sheet.getLastRowNum(); rowNum++) {
Row row = sheet.getRow(rowNum);
if (row == null) {
continue;
}
ZYB entity = new ZYB();
entity.setZydh(getCellStringValue(row.getCell(0)));
entity.setZymc(getCellStringValue(row.getCell(1)));
entity.setZydm(getCellStringValue(row.getCell(2)));
entity.setZybb(getCellStringValue(row.getCell(3)));
entity.setPxlx(getCellStringValue(row.getCell(4)));
entity.setPxcc(getCellStringValue(row.getCell(5)));
entity.setXnz(getCellStringValue(row.getCell(6)));
entity.setXqs(parseIntCell(row.getCell(7)));
entity.setZyfx(getCellStringValue(row.getCell(8)));
entity.setZybz(getCellStringValue(row.getCell(9)));
entity.setXylb(getCellStringValue(row.getCell(10)));
entity.setJc(getCellStringValue(row.getCell(11)));
entity.setPxlx2(getCellStringValue(row.getCell(12)));
// 至少要有专业代号或名称才视为有效行
if (entity.getZydh() != null && !entity.getZydh().isEmpty()
|| entity.getZymc() != null && !entity.getZymc().isEmpty()) {
entity.setZydh(cellText(row, headerIndex, "专业代号"));
entity.setZymc(cellText(row, headerIndex, "专业名称"));
entity.setZydm(cellText(row, headerIndex, "专业代码"));
entity.setZybb(cellText(row, headerIndex, "专业版本"));
entity.setPxlx(cellText(row, headerIndex, "培训类型"));
entity.setPxcc(cellText(row, headerIndex, "培训层次"));
entity.setXnz(cellText(row, headerIndex, "学年制"));
entity.setXqs(cellInt(row, headerIndex, "学期数"));
entity.setZyfx(cellText(row, headerIndex, "专业方向"));
entity.setZybz(cellText(row, headerIndex, "专业备注"));
entity.setXylb(cellText(row, headerIndex, "学员类别"));
entity.setJc(cellText(row, headerIndex, "简称"));
entity.setPxlx2(cellText(row, headerIndex, "培训类型2"));
if (isNotBlank(entity.getZydh()) || isNotBlank(entity.getZymc())) {
result.add(entity);
}
}