From a566a26fec9f7dd0ced88bc893e208ca4bb11b47 Mon Sep 17 00:00:00 2001 From: zhaichao Date: Sun, 20 Sep 2026 14:26:52 +0800 Subject: [PATCH] =?UTF-8?q?=E9=80=89=E4=BF=AE=E8=AF=BE=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/ElectiveCourseServiceImpl.java | 272 +++++++++++++++++- .../service/impl/TaskBookFillServiceImpl.java | 14 +- frontend/src/utils/request.js | 2 +- .../taskPlan/TaskBookFillDialog.vue | 35 ++- 4 files changed, 312 insertions(+), 11 deletions(-) diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ElectiveCourseServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ElectiveCourseServiceImpl.java index 21057006..1d4b8fcb 100644 --- a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ElectiveCourseServiceImpl.java +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ElectiveCourseServiceImpl.java @@ -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 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() + .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() + .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() + .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() + .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 rows = studentTeamTaskMapper.selectList(new LambdaQueryWrapper() + .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); diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TaskBookFillServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TaskBookFillServiceImpl.java index 8fefc923..9ca0d4d5 100644 --- a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TaskBookFillServiceImpl.java +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TaskBookFillServiceImpl.java @@ -170,7 +170,7 @@ public class TaskBookFillServiceImpl implements TaskBookFillService { for (XYDRWB row : rows) { List 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(); } diff --git a/frontend/src/utils/request.js b/frontend/src/utils/request.js index f9fde625..28d88836 100644 --- a/frontend/src/utils/request.js +++ b/frontend/src/utils/request.js @@ -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 diff --git a/frontend/src/views/teachOffice/taskPlan/TaskBookFillDialog.vue b/frontend/src/views/teachOffice/taskPlan/TaskBookFillDialog.vue index 09cc6a96..6e131690 100644 --- a/frontend/src/views/teachOffice/taskPlan/TaskBookFillDialog.vue +++ b/frontend/src/views/teachOffice/taskPlan/TaskBookFillDialog.vue @@ -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}`)