选修课管理

This commit is contained in:
2026-09-20 14:26:52 +08:00
parent 9f0c0ac873
commit a566a26fec
4 changed files with 312 additions and 11 deletions
@@ -14,15 +14,19 @@ import com.roomroot.jwgl.entity.JSB;
import com.roomroot.jwgl.entity.JYB;
import com.roomroot.jwgl.entity.KB;
import com.roomroot.jwgl.entity.XYDB;
import com.roomroot.jwgl.entity.XYDNDXQJBXXB;
import com.roomroot.jwgl.entity.XYDQB;
import com.roomroot.jwgl.entity.XYDRWB;
import com.roomroot.jwgl.entity.ZYB;
import com.roomroot.jwgl.mapper.ClassRoomMapper;
import com.roomroot.jwgl.mapper.ClassSemesterMapper;
import com.roomroot.jwgl.mapper.ElectiveCourseMapper;
import com.roomroot.jwgl.mapper.JYBMapper;
import com.roomroot.jwgl.mapper.KBMapper;
import com.roomroot.jwgl.mapper.StudentTeamTaskMapper;
import com.roomroot.jwgl.mapper.XYDBMapper;
import com.roomroot.jwgl.mapper.XYDQBMapper;
import com.roomroot.jwgl.mapper.ZYBMapper;
import com.roomroot.jwgl.service.ElectiveCourseService;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
@@ -84,6 +88,10 @@ public class ElectiveCourseServiceImpl implements ElectiveCourseService {
private ClassRoomMapper classRoomMapper;
@Resource
private XYDQBMapper xydqbMapper;
@Resource
private ZYBMapper zybMapper;
@Resource
private ClassSemesterMapper classSemesterMapper;
@Override
public PageResult<ElectiveCourseVO> pageList(PageQuery query, ElectiveCourseQuery cond) {
@@ -103,7 +111,7 @@ public class ElectiveCourseServiceImpl implements ElectiveCourseService {
throw new ServiceException("选修课信息不能为空", BAD_REQUEST);
}
KB course = requireCourse(dto.getKbh());
String xydbh = resolveTeamId(dto.getXydbh(), dto.getXxbmc(), dto.getNj(), true);
String xydbh = resolveTeamId(dto.getXydbh(), dto.getXxbmc(), dto.getNj(), true, dto.getRs());
XYDRWB task = new XYDRWB();
task.setBh(UuidUtil.getOriginalUUID());
task.setDelFlag(0);
@@ -123,6 +131,8 @@ public class ElectiveCourseServiceImpl implements ElectiveCourseService {
task.setJysdh(StringUtils.isNotEmpty(dto.getJysdh()) ? dto.getJysdh() : course.getJysdh());
task.setBz(dto.getBz());
task.setXz(FLAG_DRAFT);
task.setXydxqbh(resolveOrCreateSemester(xydbh, task.getNd(), dto.getXqdc(), dto.getKkrq(), dto.getJkrq()));
fillTaskRequiredDefaults(task, course);
studentTeamTaskMapper.insert(task);
upsertPinban(xydbh, STATUS_DRAFT, dto.getBmkssj(), dto.getBmjssj(),
dto.getYqsm(), dto.getKxdlbc(), dto.getBz());
@@ -245,7 +255,7 @@ public class ElectiveCourseServiceImpl implements ElectiveCourseService {
}
jsbh = room.getJsbh();
}
String xydbh = resolveTeamId(null, row.getXxbmc().trim(), row.getKxbcnj(), true);
String xydbh = resolveTeamId(null, row.getXxbmc().trim(), row.getKxbcnj(), true, row.getJhrs());
XYDRWB task = new XYDRWB();
task.setBh(UuidUtil.getOriginalUUID());
task.setDelFlag(0);
@@ -260,6 +270,9 @@ public class ElectiveCourseServiceImpl implements ElectiveCourseService {
task.setKlx(ensureElectiveType(null));
task.setJysdh(course.getJysdh());
task.setXz(statusToFlag(row.getXkzt()));
task.setXydxqbh(resolveOrCreateSemester(xydbh, task.getNd(), null,
parseDateTime(row.getKkrq()), parseDateTime(row.getJkrq())));
fillTaskRequiredDefaults(task, course);
studentTeamTaskMapper.insert(task);
upsertPinban(xydbh, normalizeStatus(row.getXkzt()), parseDateTime(row.getBmkssj()),
parseDateTime(row.getBmjssj()), row.getYqsm(), row.getKxbcnj(), null);
@@ -347,7 +360,7 @@ public class ElectiveCourseServiceImpl implements ElectiveCourseService {
}
}
private String resolveTeamId(String xydbh, String xxbmc, String nj, boolean createIfMissing) {
private String resolveTeamId(String xydbh, String xxbmc, String nj, boolean createIfMissing, Integer rs) {
if (StringUtils.isNotEmpty(xydbh)) {
XYDB exists = xydbMapper.selectById(xydbh);
if (exists == null) {
@@ -371,10 +384,263 @@ public class ElectiveCourseServiceImpl implements ElectiveCourseService {
created.setNj(blankToNull(nj));
created.setXydlx(CLASS_TYPE_ELECTIVE);
created.setXslx(0);
created.setXydrs(rs != null ? rs : 0);
fillTeamRequiredDefaults(created);
xydbMapper.insert(created);
return created.getXydbh();
}
/**
* 补齐学员队表非空列。优先沿用已有班次上的专业/状态/单位等,避免逐列撞约束。
*/
private void fillTeamRequiredDefaults(XYDB created) {
XYDB template = xydbMapper.selectOne(new LambdaQueryWrapper<XYDB>()
.ne(created.getXydbh() != null, XYDB::getXydbh, created.getXydbh())
.last("LIMIT 1"));
if (template != null) {
if (StringUtils.isEmpty(created.getZydh())) {
created.setZydh(template.getZydh());
}
if (StringUtils.isEmpty(created.getNj())) {
created.setNj(template.getNj());
}
if (StringUtils.isEmpty(created.getZyjsbh())) {
created.setZyjsbh(template.getZyjsbh());
}
if (StringUtils.isEmpty(created.getZxzt())) {
created.setZxzt(template.getZxzt());
}
if (StringUtils.isEmpty(created.getXh())) {
created.setXh(template.getXh());
}
if (StringUtils.isEmpty(created.getRwlb())) {
created.setRwlb(template.getRwlb());
}
if (StringUtils.isEmpty(created.getJclb())) {
created.setJclb(template.getJclb());
}
if (StringUtils.isEmpty(created.getXtms())) {
created.setXtms(template.getXtms());
}
if (StringUtils.isEmpty(created.getJsonzd())) {
created.setJsonzd(template.getJsonzd());
}
if (StringUtils.isEmpty(created.getJc())) {
created.setJc(template.getJc());
}
if (StringUtils.isEmpty(created.getSsdw())) {
created.setSsdw(template.getSsdw());
}
if (StringUtils.isEmpty(created.getPxrwbsh())) {
created.setPxrwbsh(template.getPxrwbsh());
}
if (created.getZtpxrw() == null) {
created.setZtpxrw(template.getZtpxrw());
}
if (StringUtils.isEmpty(created.getBz())) {
created.setBz(template.getBz());
}
}
if (created.getXydrs() == null) {
created.setXydrs(0);
}
if (created.getXslx() == null) {
created.setXslx(0);
}
if (created.getZtpxrw() == null) {
created.setZtpxrw(0);
}
if (StringUtils.isEmpty(created.getZydh())) {
created.setZydh(resolveDefaultZydh());
}
if (StringUtils.isEmpty(created.getNj())) {
created.setNj(String.valueOf(LocalDate.now().getYear()));
}
if (StringUtils.isEmpty(created.getZxzt())) {
created.setZxzt("在校");
}
}
private String resolveDefaultZydh() {
XYDB existing = xydbMapper.selectOne(new LambdaQueryWrapper<XYDB>()
.isNotNull(XYDB::getZydh)
.ne(XYDB::getZydh, "")
.last("LIMIT 1"));
if (existing != null && StringUtils.isNotEmpty(existing.getZydh())) {
return existing.getZydh();
}
ZYB major = zybMapper.selectOne(new LambdaQueryWrapper<ZYB>()
.isNotNull(ZYB::getZydh)
.ne(ZYB::getZydh, "")
.last("LIMIT 1"));
if (major != null && StringUtils.isNotEmpty(major.getZydh())) {
return major.getZydh();
}
throw new ServiceException("新建选修班需要专业代号,请先在学科专业中维护专业", BAD_REQUEST);
}
/**
* 任务表「学员队学期编号」非空且外键到班次学期。按班次+年度+学期第次查找,没有则按班次学期口径新建一条。
*/
private String resolveOrCreateSemester(String xydbh, Integer taskNd, Integer xqdc,
LocalDateTime start, LocalDateTime end) {
Integer year = SemesterCodeUtil.toYear(taskNd);
if (year == null) {
year = LocalDate.now().getYear();
}
Integer period = xqdc != null ? xqdc : SemesterCodeUtil.toPeriod(taskNd, SemesterCodeUtil.periodOf(LocalDate.now()));
if (period == null) {
period = 1;
}
XYDNDXQJBXXB existing = classSemesterMapper.selectByBcAndNdAndXqdc(
xydbh, String.valueOf(year), String.valueOf(period));
if (existing == null) {
existing = classSemesterMapper.selectOne(new LambdaQueryWrapper<XYDNDXQJBXXB>()
.eq(XYDNDXQJBXXB::getXydbh, xydbh)
.and(w -> w.isNull(XYDNDXQJBXXB::getDelFlag).or().eq(XYDNDXQJBXXB::getDelFlag, 0))
.last("LIMIT 1"));
}
if (existing != null) {
return existing.getBh();
}
LocalDate kxrq = start != null ? start.toLocalDate() : LocalDate.now();
LocalDate jsrq = end != null ? end.toLocalDate() : kxrq.plusMonths(4);
if (!jsrq.isAfter(kxrq)) {
jsrq = kxrq.plusMonths(4);
}
XYDB team = xydbMapper.selectById(xydbh);
XYDNDXQJBXXB row = new XYDNDXQJBXXB();
row.setBh(UuidUtil.getUUID());
row.setXydbh(xydbh);
row.setNd(year);
row.setXqdc(period);
row.setKxrq(kxrq);
row.setJsrq(jsrq);
row.setXhbs(0);
row.setYsbbh(0);
row.setKfjypk(0);
row.setDelFlag(0);
if (team != null) {
row.setZyjsbh(team.getZyjsbh());
row.setJclb(StringUtils.isNotEmpty(team.getJclb()) ? team.getJclb() : "双节次");
row.setXtms(team.getXtms() == null ? "" : team.getXtms());
row.setJsonzd(team.getJsonzd());
} else {
row.setJclb("双节次");
row.setXtms("");
}
classSemesterMapper.insert(row);
return row.getBh();
}
/**
* 补齐学员队任务表无库默认值的非空列(与教学任务新增同一套口径)。
* 计划课次序号、序号标识、编组、配当同班异课选修编组、配档启用自定义学时、学时、课类型、教研室代号。
*/
private void fillTaskRequiredDefaults(XYDRWB task, KB course) {
int seq = nextTaskSequence(task.getXydbh());
if (task.getJhkcxh() == null) {
task.setJhkcxh(seq);
}
if (task.getXhbs() == null) {
task.setXhbs(task.getJhkcxh());
}
if (task.getKcxh() == null) {
task.setKcxh(task.getJhkcxh());
}
if (task.getBz2() == null) {
task.setBz2(0);
}
if (task.getPdtbykxxbz() == null) {
task.setPdtbykxxbz(1);
}
if (task.getPdqyzdyxs() == null) {
task.setPdqyzdyxs(0);
}
if (task.getPdxh() == null) {
task.setPdxh(0);
}
if (task.getPdqsz() == null) {
task.setPdqsz(0);
}
if (task.getPdaz() == null) {
task.setPdaz(0);
}
if (task.getPdzzksj() == null) {
task.setPdzzksj(0);
}
if (task.getPdbz() == null) {
task.setPdbz(0);
}
if (task.getXs() == null) {
task.setXs(course != null && course.getXs() != null ? course.getXs() : 0);
}
if (task.getXf() == null) {
task.setXf(course != null && course.getXf() != null ? course.getXf() : 0f);
}
if (task.getRs() == null) {
task.setRs(0);
}
if (task.getXqdc() == null) {
task.setXqdc(1);
}
if (task.getXz() == null) {
task.setXz(FLAG_DRAFT);
}
if (StringUtils.isEmpty(task.getKlx())) {
task.setKlx(COURSE_TYPE_ELECTIVE);
}
if (StringUtils.isEmpty(task.getJysdh())) {
String jysdh = course == null ? null : course.getJysdh();
task.setJysdh(StringUtils.isNotEmpty(jysdh) ? jysdh : "-");
}
if (StringUtils.isEmpty(task.getJc()) && course != null) {
task.setJc(StringUtils.isNotEmpty(course.getJc()) ? course.getJc() : course.getKmc());
}
if (task.getKsks() == null) {
task.setKsks(course != null && course.getKsks() != null ? course.getKsks() : 0);
}
if (task.getLlxs() == null) {
task.setLlxs(0);
}
if (task.getSjxs() == null) {
task.setSjxs(0);
}
if (task.getKsksbxs() == null) {
task.setKsksbxs(0);
}
if (task.getBjrxypjf() == null) {
task.setBjrxypjf(0);
}
if (StringUtils.isEmpty(task.getCjfz()) && course != null && StringUtils.isNotEmpty(course.getCjfz())) {
task.setCjfz(course.getCjfz());
}
if (task.getDelFlag() == null) {
task.setDelFlag(0);
}
}
private int nextTaskSequence(String xydbh) {
if (StringUtils.isEmpty(xydbh)) {
return 1;
}
List<XYDRWB> rows = studentTeamTaskMapper.selectList(new LambdaQueryWrapper<XYDRWB>()
.eq(XYDRWB::getXydbh, xydbh)
.eq(XYDRWB::getDelFlag, 0));
int max = 0;
if (rows != null) {
for (XYDRWB row : rows) {
if (row.getJhkcxh() != null && row.getJhkcxh() > max) {
max = row.getJhkcxh();
}
if (row.getXhbs() != null && row.getXhbs() > max) {
max = row.getXhbs();
}
}
}
return max + 1;
}
private KB requireCourse(String kbh) {
if (StringUtils.isEmpty(kbh)) {
throw new ServiceException("课程科目不能为空", BAD_REQUEST);
@@ -170,7 +170,7 @@ public class TaskBookFillServiceImpl implements TaskBookFillService {
for (XYDRWB row : rows) {
List<String> diff = new ArrayList<>();
if (!Objects.equals(row.getKbh(), first.getKbh())) {
diff.add("科目(" + row.getKbh() + " ≠ " + first.getKbh() + ")");
diff.add("科目(" + subjectLabel(row) + " ≠ " + subjectLabel(first) + ")");
}
if (!Objects.equals(row.getXs(), first.getXs())) {
diff.add("学时(" + row.getXs() + " ≠ " + first.getXs() + ")");
@@ -182,12 +182,22 @@ public class TaskBookFillServiceImpl implements TaskBookFillService {
diff.add("成绩分制(" + row.getCjfz() + " ≠ " + first.getCjfz() + ")");
}
if (!diff.isEmpty()) {
throw new ServiceException("任务 " + row.getBh() + "(" + row.getXydbh() + ")与第一行不合班:" + String.join("、", diff),
throw new ServiceException(
"「" + subjectLabel(row) + "」(" + row.getXydbh() + ")与「"
+ subjectLabel(first) + "」不合班:" + String.join("、", diff)
+ "。合班请选同一科目、不同学员队的行",
BAD_REQUEST);
}
}
}
private static String subjectLabel(XYDRWB row) {
if (row.getJc() != null && !row.getJc().isEmpty()) {
return row.getJc();
}
return row.getKbh() == null ? "" : row.getKbh();
}
private static String norm(String s) {
return s == null || s.isEmpty() ? "" : s.trim();
}
+1 -1
View File
@@ -77,7 +77,7 @@ service.interceptors.response.use(res => {
// 未设置状态码则默认成功状态
const code = res.data.code || 200
// 获取错误信息
const msg = errorCode[code] || res.data.msg || errorCode['default']
const msg = errorCode[code] || res.data.msg || res.data.message || errorCode['default']
// 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
return res.data
@@ -392,16 +392,41 @@ export default {
this.$message.success('操作成功')
this.loadRows()
})
.catch(err => {
this.$message.error((err && err.message) || '操作失败')
})
.catch(() => {})
},
/** 手动合班前置校验:科目/学时/课类型/成绩分制必须相同 */
mergeIncompatibleReason(rows) {
const first = rows[0]
const diffs = []
const courses = [...new Set(rows.map(r => r.kcmc || r.kbh || ''))].filter(Boolean)
if (rows.some(r => r.kbh !== first.kbh)) {
diffs.push('科目不同(' + (courses.join('、') || '课号不一致') + ')')
}
if (rows.some(r => r.xs !== first.xs)) {
diffs.push('学时不同')
}
if (rows.some(r => r.klx !== first.klx)) {
diffs.push('课类型不同')
}
const norm = v => (v == null || v === '' ? '' : String(v).trim())
if (rows.some(r => norm(r.cjfz) !== norm(first.cjfz))) {
diffs.push('成绩分制不同')
}
if (!diffs.length) return ''
return '不能手动合班:' + diffs.join(';') + '。请选择同一科目、同一学时、同一课类型、同一成绩分制的行(通常是不同学员队的同一门课)'
},
handleMerge() {
const bhList = this.selection.map(r => r.bh)
if (bhList.length < 2) {
const selected = this.selection
if (selected.length < 2) {
this.$message.warning('请至少选择两行进行合班')
return
}
const reason = this.mergeIncompatibleReason(selected)
if (reason) {
this.$message.warning(reason)
return
}
const bhList = selected.map(r => r.bh)
this.$confirm(`把选中的 ${bhList.length} 行合成一个班?`, '手动合班', { type: 'warning' })
.then(() => this.reload(taskBookMerge(bhList).then(res => {
this.$message.info(`已合入编组 ${res.data}`)