forked from liweijie/education
班历功能添加
This commit is contained in:
+20
@@ -1,9 +1,11 @@
|
|||||||
package com.roomroot.web.controller.jwgl;
|
package com.roomroot.web.controller.jwgl;
|
||||||
|
|
||||||
import com.roomroot.jwgl.dto.calendar.ClassCalendarApplyDTO;
|
import com.roomroot.jwgl.dto.calendar.ClassCalendarApplyDTO;
|
||||||
|
import com.roomroot.jwgl.dto.calendar.ClassCalendarBatchSaveDTO;
|
||||||
import com.roomroot.jwgl.dto.calendar.ClassCalendarEventDTO;
|
import com.roomroot.jwgl.dto.calendar.ClassCalendarEventDTO;
|
||||||
import com.roomroot.jwgl.service.ClassEventCalendarService;
|
import com.roomroot.jwgl.service.ClassEventCalendarService;
|
||||||
import com.roomroot.jwgl.unit.Result;
|
import com.roomroot.jwgl.unit.Result;
|
||||||
|
import com.roomroot.jwgl.vo.calendar.ClassCalendarResyncVO;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
@@ -35,6 +37,24 @@ public class ClassEventCalendarController {
|
|||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存班历时间格(拖选区域一次提交,单事务 upsert;action=clear 恢复默认)
|
||||||
|
*/
|
||||||
|
@PostMapping("/batchSave")
|
||||||
|
public Result<Integer> batchSave(@RequestBody ClassCalendarBatchSaveDTO body) {
|
||||||
|
int saved = classEventCalendarService.batchSave(body);
|
||||||
|
return Result.success("已保存 " + saved + " 个时间格", saved);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新同步校历:confirm=false 返回缺失格预览,confirm=true 实际补齐(只补缺失,不覆盖人工改动)
|
||||||
|
*/
|
||||||
|
@PostMapping("/resync")
|
||||||
|
public Result<ClassCalendarResyncVO> resync(@RequestParam("xydxqbh") String xydxqbh,
|
||||||
|
@RequestParam(value = "confirm", defaultValue = "false") boolean confirm) {
|
||||||
|
return Result.success(classEventCalendarService.resync(xydxqbh, confirm));
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/apply")
|
@PostMapping("/apply")
|
||||||
public Result<Integer> apply(@RequestBody ClassCalendarApplyDTO body) {
|
public Result<Integer> apply(@RequestBody ClassCalendarApplyDTO body) {
|
||||||
return Result.success(classEventCalendarService.apply(
|
return Result.success(classEventCalendarService.apply(
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ import java.util.Map;
|
|||||||
* 学员队任务表控制器
|
* 学员队任务表控制器
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/classCalendar")
|
@RequestMapping({"/classCalendar", "/course-task"})
|
||||||
public class ClassSemesterCalendarController {
|
public class ClassSemesterCalendarController {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
|
|||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
package com.roomroot.jwgl.dto.calendar;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班历批量保存入参。一次拖选产生的所有时间格在单事务内 upsert。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ClassCalendarBatchSaveDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学员队学期编号(班次学期)
|
||||||
|
*/
|
||||||
|
private String xydxqbh;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间格列表。cell.action = "clear" 表示恢复默认(清名称、可排课)。
|
||||||
|
*/
|
||||||
|
private List<ClassCalendarEventDTO> cells;
|
||||||
|
}
|
||||||
+5
@@ -21,4 +21,9 @@ public class ClassCalendarEventDTO {
|
|||||||
private Boolean zdpk;
|
private Boolean zdpk;
|
||||||
private Boolean zk;
|
private Boolean zk;
|
||||||
private String courseClass;
|
private String courseClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存动作:"clear" 表示恢复默认(清名称、可排课);空或其它值按事件内容写入。
|
||||||
|
*/
|
||||||
|
private String action;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package com.roomroot.jwgl.entity;
|
package com.roomroot.jwgl.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 节次时间表
|
* 节次时间表
|
||||||
@@ -22,41 +22,49 @@ public class JCSJB {
|
|||||||
/**
|
/**
|
||||||
* 双节次
|
* 双节次
|
||||||
*/
|
*/
|
||||||
|
@TableField("双节次")
|
||||||
private Boolean sjc;
|
private Boolean sjc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 节次索引
|
* 节次索引
|
||||||
*/
|
*/
|
||||||
|
@TableField("节次索引")
|
||||||
private String jcsy;
|
private String jcsy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开始时间
|
* 开始时间
|
||||||
*/
|
*/
|
||||||
|
@TableField("开始时间")
|
||||||
private String kssj;
|
private String kssj;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 结束时间
|
* 结束时间
|
||||||
*/
|
*/
|
||||||
|
@TableField("结束时间")
|
||||||
private String jssj;
|
private String jssj;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 备注
|
* 备注
|
||||||
*/
|
*/
|
||||||
|
@TableField("备注")
|
||||||
private String bz;
|
private String bz;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 名称
|
* 名称
|
||||||
*/
|
*/
|
||||||
|
@TableField("名称")
|
||||||
private String mc;
|
private String mc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 简称
|
* 简称
|
||||||
*/
|
*/
|
||||||
|
@TableField("简称")
|
||||||
private String jc;
|
private String jc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 时段
|
* 时段
|
||||||
*/
|
*/
|
||||||
|
@TableField("时段")
|
||||||
private String sd;
|
private String sd;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.roomroot.jwgl.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.roomroot.jwgl.entity.JCSJB;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface JCSJBMapper extends BaseMapper<JCSJB> {
|
||||||
|
}
|
||||||
+15
-1
@@ -1,6 +1,8 @@
|
|||||||
package com.roomroot.jwgl.service;
|
package com.roomroot.jwgl.service;
|
||||||
|
|
||||||
|
import com.roomroot.jwgl.dto.calendar.ClassCalendarBatchSaveDTO;
|
||||||
import com.roomroot.jwgl.dto.calendar.ClassCalendarEventDTO;
|
import com.roomroot.jwgl.dto.calendar.ClassCalendarEventDTO;
|
||||||
|
import com.roomroot.jwgl.vo.calendar.ClassCalendarResyncVO;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -15,10 +17,22 @@ public interface ClassEventCalendarService {
|
|||||||
List<ClassCalendarEventDTO> listOrInherit(String xydxqbh);
|
List<ClassCalendarEventDTO> listOrInherit(String xydxqbh);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新一个时间格。班次日期范围外的格子拒绝保存。
|
* 更新一个时间格(upsert:编号不存在时按 学员队学期编号+日期+节次 定位,仍无则插入)。
|
||||||
|
* 班次日期范围外的格子拒绝保存。
|
||||||
*/
|
*/
|
||||||
void update(ClassCalendarEventDTO event);
|
void update(ClassCalendarEventDTO event);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存时间格,单事务 upsert;cell.action="clear" 恢复默认。
|
||||||
|
*/
|
||||||
|
int batchSave(ClassCalendarBatchSaveDTO body);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新同步校历:只补校历有而班历缺失的时间格,不覆盖人工改动。
|
||||||
|
* confirm=false 仅返回差异预览;confirm=true 实际写入。
|
||||||
|
*/
|
||||||
|
ClassCalendarResyncVO resync(String xydxqbh, boolean confirm);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 把选定或整个班历覆盖到其它班次的对应时间格。不改课程任务。
|
* 把选定或整个班历覆盖到其它班次的对应时间格。不改课程任务。
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.roomroot.jwgl.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节次字典。校历 / 班历 / 场地历的节次段统一由「节次时间表」驱动,
|
||||||
|
* 表内无数据时回退到默认六段(上午三节 + 下午一节 + 晚上两节)。
|
||||||
|
*/
|
||||||
|
public interface ClassPeriodService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 双节次时间格标签(如 1-2、3-4),按起始节次排序。
|
||||||
|
*/
|
||||||
|
List<String> slotLabels();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上午时段的双节次标签,用于「正课」默认判定。
|
||||||
|
*/
|
||||||
|
Set<String> mainSlotLabels();
|
||||||
|
}
|
||||||
+157
-7
@@ -2,6 +2,7 @@ package com.roomroot.jwgl.service.impl;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.roomroot.common.exception.ServiceException;
|
import com.roomroot.common.exception.ServiceException;
|
||||||
|
import com.roomroot.jwgl.dto.calendar.ClassCalendarBatchSaveDTO;
|
||||||
import com.roomroot.jwgl.dto.calendar.ClassCalendarEventDTO;
|
import com.roomroot.jwgl.dto.calendar.ClassCalendarEventDTO;
|
||||||
import com.roomroot.jwgl.entity.JQB;
|
import com.roomroot.jwgl.entity.JQB;
|
||||||
import com.roomroot.jwgl.entity.XQXLB;
|
import com.roomroot.jwgl.entity.XQXLB;
|
||||||
@@ -10,9 +11,11 @@ import com.roomroot.jwgl.mapper.ClassSemesterMapper;
|
|||||||
import com.roomroot.jwgl.mapper.JQBMapper;
|
import com.roomroot.jwgl.mapper.JQBMapper;
|
||||||
import com.roomroot.jwgl.mapper.SemesterCalendarMapper;
|
import com.roomroot.jwgl.mapper.SemesterCalendarMapper;
|
||||||
import com.roomroot.jwgl.service.ClassEventCalendarService;
|
import com.roomroot.jwgl.service.ClassEventCalendarService;
|
||||||
|
import com.roomroot.jwgl.service.ClassPeriodService;
|
||||||
import com.roomroot.jwgl.service.TeachingTaskWriteGuard;
|
import com.roomroot.jwgl.service.TeachingTaskWriteGuard;
|
||||||
import com.roomroot.jwgl.utils.SemesterCodeUtil;
|
import com.roomroot.jwgl.utils.SemesterCodeUtil;
|
||||||
import com.roomroot.jwgl.utils.UuidUtil;
|
import com.roomroot.jwgl.utils.UuidUtil;
|
||||||
|
import com.roomroot.jwgl.vo.calendar.ClassCalendarResyncVO;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
@@ -21,7 +24,6 @@ import jakarta.annotation.Resource;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -38,11 +40,14 @@ public class ClassEventCalendarServiceImpl implements ClassEventCalendarService
|
|||||||
|
|
||||||
private static final DateTimeFormatter DAY = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
private static final DateTimeFormatter DAY = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||||
private static final DateTimeFormatter COMPACT = DateTimeFormatter.ofPattern("yyyyMMdd");
|
private static final DateTimeFormatter COMPACT = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||||
private static final List<String> PERIODS = Arrays.asList("1-2", "3-4", "5-6", "7-8", "9-10", "11-12");
|
private static final String ACTION_CLEAR = "clear";
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private JQBMapper jqbMapper;
|
private JQBMapper jqbMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ClassPeriodService classPeriodService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ClassSemesterMapper classSemesterMapper;
|
private ClassSemesterMapper classSemesterMapper;
|
||||||
|
|
||||||
@@ -69,18 +74,158 @@ public class ClassEventCalendarServiceImpl implements ClassEventCalendarService
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void update(ClassCalendarEventDTO event) {
|
public void update(ClassCalendarEventDTO event) {
|
||||||
if (event == null || event.getBh() == null || event.getBh().isEmpty()) {
|
if (event == null) {
|
||||||
throw new ServiceException("班历编号不能为空", BAD_REQUEST);
|
throw new ServiceException("班历时间格不能为空", BAD_REQUEST);
|
||||||
}
|
}
|
||||||
JQB existing = jqbMapper.selectById(event.getBh());
|
JQB existing = findCell(event);
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
|
// upsert:编号查不到时按「学员队学期编号+日期+节次」再定位,仍无则插入新格,
|
||||||
|
// 使班次日期范围扩大(或校历新增的节次段)后出现的格子也能写入。
|
||||||
|
if (event.getXydxqbh() == null || event.getXydxqbh().isEmpty()) {
|
||||||
throw new ServiceException("班历时间格不存在", BAD_REQUEST);
|
throw new ServiceException("班历时间格不存在", BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
teachingTaskWriteGuard.assertCourseTaskWritable(event.getXydxqbh());
|
||||||
|
XYDNDXQJBXXB semester = requireSemester(event.getXydxqbh());
|
||||||
|
LocalDate day = parseDay(event.getJqsj());
|
||||||
|
assertInRange(semester, day);
|
||||||
|
if (event.getCourseClass() == null || event.getCourseClass().isEmpty()) {
|
||||||
|
throw new ServiceException("班历节次不能为空", BAD_REQUEST);
|
||||||
|
}
|
||||||
|
applyCell(semester, null, day, event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
teachingTaskWriteGuard.assertCourseTaskWritable(existing.getXydxqbh());
|
teachingTaskWriteGuard.assertCourseTaskWritable(existing.getXydxqbh());
|
||||||
XYDNDXQJBXXB semester = requireSemester(existing.getXydxqbh());
|
XYDNDXQJBXXB semester = requireSemester(existing.getXydxqbh());
|
||||||
LocalDate day = parseDay(event.getJqsj() == null ? formatDay(existing.getJqsj()) : event.getJqsj());
|
LocalDate day = parseDay(event.getJqsj() == null ? formatDay(existing.getJqsj()) : event.getJqsj());
|
||||||
assertInRange(semester, day);
|
assertInRange(semester, day);
|
||||||
|
applyCell(semester, existing, day, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存:一次拖选的所有时间格在单事务内 upsert,action=clear 恢复默认。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public int batchSave(ClassCalendarBatchSaveDTO body) {
|
||||||
|
if (body == null || body.getXydxqbh() == null || body.getXydxqbh().isEmpty()) {
|
||||||
|
throw new ServiceException("请选择班次学期", BAD_REQUEST);
|
||||||
|
}
|
||||||
|
List<ClassCalendarEventDTO> cells = body.getCells();
|
||||||
|
if (CollectionUtils.isEmpty(cells)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
teachingTaskWriteGuard.assertCourseTaskWritable(body.getXydxqbh());
|
||||||
|
XYDNDXQJBXXB semester = requireSemester(body.getXydxqbh());
|
||||||
|
// 一次性拉出该班次学期全部已有格,按 (日期|节次) 建索引避免逐格查询
|
||||||
|
Map<String, JQB> existingByKey = new LinkedHashMap<>();
|
||||||
|
for (JQB row : listRows(body.getXydxqbh())) {
|
||||||
|
existingByKey.putIfAbsent(cellKey(row.getJqsj(), row.getCourseClass()), row);
|
||||||
|
}
|
||||||
|
int count = 0;
|
||||||
|
for (ClassCalendarEventDTO cell : cells) {
|
||||||
|
if (cell == null || cell.getCourseClass() == null || cell.getCourseClass().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
LocalDate day = parseDay(cell.getJqsj());
|
||||||
|
assertInRange(semester, day);
|
||||||
|
JQB existing = existingByKey.get(cellKey(Integer.valueOf(day.format(COMPACT)), cell.getCourseClass()));
|
||||||
|
applyCell(semester, existing, day, cell);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新同步校历(口径A:只补缺失格,不覆盖人工改动)。
|
||||||
|
* confirm=false 返回差异预览;confirm=true 实际写入。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public ClassCalendarResyncVO resync(String xydxqbh, boolean confirm) {
|
||||||
|
XYDNDXQJBXXB semester = requireSemester(xydxqbh);
|
||||||
|
LocalDate start = semester.getKxrq();
|
||||||
|
LocalDate end = semester.getJsrq();
|
||||||
|
if (start == null || end == null) {
|
||||||
|
throw new ServiceException("班次学期缺少开学或结束日期,不能同步校历", BAD_REQUEST);
|
||||||
|
}
|
||||||
|
Set<String> existingKeys = new HashSet<>();
|
||||||
|
for (JQB row : listRows(xydxqbh)) {
|
||||||
|
existingKeys.add(cellKey(row.getJqsj(), row.getCourseClass()));
|
||||||
|
}
|
||||||
|
List<ClassCalendarEventDTO> missing = new ArrayList<>();
|
||||||
|
for (XQXLB school : mergeByDayAndPeriod(loadSchoolRows(semester, start, end), start, end)) {
|
||||||
|
LocalDate day = parseDay(school.getJqsj());
|
||||||
|
if (!existingKeys.contains(cellKey(Integer.valueOf(day.format(COMPACT)), school.getCourseClass()))) {
|
||||||
|
ClassCalendarEventDTO dto = new ClassCalendarEventDTO();
|
||||||
|
dto.setJqsj(day.format(DAY));
|
||||||
|
dto.setCourseClass(school.getCourseClass());
|
||||||
|
dto.setJqmc(school.getJqmc());
|
||||||
|
dto.setJc(Boolean.TRUE.equals(school.getJc()));
|
||||||
|
dto.setKpk(Boolean.TRUE.equals(school.getKpk()));
|
||||||
|
dto.setBzxs(Boolean.TRUE.equals(school.getBzxs()));
|
||||||
|
dto.setZdpk(Boolean.TRUE.equals(school.getZdpk()));
|
||||||
|
dto.setZk(Boolean.TRUE.equals(school.getZk()));
|
||||||
|
dto.setBz(school.getBz());
|
||||||
|
missing.add(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClassCalendarResyncVO vo = new ClassCalendarResyncVO();
|
||||||
|
vo.setItems(missing);
|
||||||
|
vo.setAddCount(missing.size());
|
||||||
|
if (confirm && !missing.isEmpty()) {
|
||||||
|
teachingTaskWriteGuard.assertCourseTaskWritable(xydxqbh);
|
||||||
|
for (ClassCalendarEventDTO dto : missing) {
|
||||||
|
insertCell(semester, parseDay(dto.getJqsj()), dto.getCourseClass(), dto.getJqmc(),
|
||||||
|
flag(dto.getJc()), flag(dto.getKpk()), flag(dto.getBzxs()),
|
||||||
|
flag(dto.getZdpk()), flag(dto.getZk()), dto.getBz());
|
||||||
|
}
|
||||||
|
vo.setExecuted(true);
|
||||||
|
}
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按 编号 / (学员队学期编号+日期+节次) 定位已有格。
|
||||||
|
*/
|
||||||
|
private JQB findCell(ClassCalendarEventDTO event) {
|
||||||
|
if (event.getBh() != null && !event.getBh().isEmpty()) {
|
||||||
|
JQB row = jqbMapper.selectById(event.getBh());
|
||||||
|
if (row != null) {
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (event.getXydxqbh() == null || event.getXydxqbh().isEmpty()
|
||||||
|
|| event.getJqsj() == null || event.getCourseClass() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
List<JQB> matched = jqbMapper.selectList(new LambdaQueryWrapper<JQB>()
|
||||||
|
.eq(JQB::getXydxqbh, event.getXydxqbh())
|
||||||
|
.eq(JQB::getJqsj, Integer.valueOf(parseDay(event.getJqsj()).format(COMPACT)))
|
||||||
|
.eq(JQB::getCourseClass, event.getCourseClass()));
|
||||||
|
return matched.isEmpty() ? null : matched.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把一格事件写入 existing(null 时新建)。action=clear 恢复默认:清名称、可排课。
|
||||||
|
*/
|
||||||
|
private void applyCell(XYDNDXQJBXXB semester, JQB existing, LocalDate day, ClassCalendarEventDTO event) {
|
||||||
|
if (ACTION_CLEAR.equals(event.getAction())) {
|
||||||
|
event.setJqmc("");
|
||||||
|
event.setBz(null);
|
||||||
|
event.setJc(Boolean.FALSE);
|
||||||
|
event.setKpk(Boolean.TRUE);
|
||||||
|
event.setBzxs(Boolean.FALSE);
|
||||||
|
event.setZdpk(Boolean.FALSE);
|
||||||
|
event.setZk(Boolean.FALSE);
|
||||||
|
}
|
||||||
|
if (existing == null) {
|
||||||
|
insertCell(semester, day, event.getCourseClass(), event.getJqmc(),
|
||||||
|
flag(event.getJc()), flag(event.getKpk()), flag(event.getBzxs()),
|
||||||
|
flag(event.getZdpk()), flag(event.getZk()), event.getBz());
|
||||||
|
return;
|
||||||
|
}
|
||||||
existing.setJqmc(event.getJqmc() == null ? "" : event.getJqmc());
|
existing.setJqmc(event.getJqmc() == null ? "" : event.getJqmc());
|
||||||
existing.setBz(event.getBz());
|
existing.setBz(event.getBz());
|
||||||
existing.setJc(flag(event.getJc()));
|
existing.setJc(flag(event.getJc()));
|
||||||
@@ -95,6 +240,10 @@ public class ClassEventCalendarServiceImpl implements ClassEventCalendarService
|
|||||||
jqbMapper.updateById(existing);
|
jqbMapper.updateById(existing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String cellKey(Integer jqsj, String courseClass) {
|
||||||
|
return jqsj + "|" + courseClass;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public int apply(String sourceXydxqbh, List<String> targetBhList, List<String> eventBhList) {
|
public int apply(String sourceXydxqbh, List<String> targetBhList, List<String> eventBhList) {
|
||||||
@@ -219,10 +368,11 @@ public class ClassEventCalendarServiceImpl implements ClassEventCalendarService
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void generateEmptyGrid(XYDNDXQJBXXB semester, LocalDate start, LocalDate end) {
|
private void generateEmptyGrid(XYDNDXQJBXXB semester, LocalDate start, LocalDate end) {
|
||||||
|
Set<String> mainSlots = classPeriodService.mainSlotLabels();
|
||||||
for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) {
|
for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) {
|
||||||
boolean weekdayMain = day.getDayOfWeek().getValue() <= 5;
|
boolean weekdayMain = day.getDayOfWeek().getValue() <= 5;
|
||||||
for (String period : PERIODS) {
|
for (String period : classPeriodService.slotLabels()) {
|
||||||
boolean mainSlot = weekdayMain && ("1-2".equals(period) || "3-4".equals(period) || "5-6".equals(period));
|
boolean mainSlot = weekdayMain && mainSlots.contains(period);
|
||||||
insertCell(semester, day, period, "", 0, mainSlot ? 1 : 0, 0, mainSlot ? 1 : 0, mainSlot ? 1 : 0, null);
|
insertCell(semester, day, period, "", 0, mainSlot ? 1 : 0, 0, mainSlot ? 1 : 0, mainSlot ? 1 : 0, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
package com.roomroot.jwgl.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.roomroot.jwgl.entity.JCSJB;
|
||||||
|
import com.roomroot.jwgl.mapper.JCSJBMapper;
|
||||||
|
import com.roomroot.jwgl.service.ClassPeriodService;
|
||||||
|
import com.roomroot.jwgl.utils.PeriodUtil;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ClassPeriodServiceImpl implements ClassPeriodService {
|
||||||
|
|
||||||
|
private static final List<String> DEFAULT_SLOTS =
|
||||||
|
Arrays.asList("1-2", "3-4", "5-6", "7-8", "9-10", "11-12");
|
||||||
|
private static final Set<String> DEFAULT_MAIN_SLOTS =
|
||||||
|
new LinkedHashSet<>(Arrays.asList("1-2", "3-4", "5-6"));
|
||||||
|
private static final String MAIN_PERIOD_OF_DAY = "上午";
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private JCSJBMapper jcsjbMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> slotLabels() {
|
||||||
|
List<JCSJB> slots = loadSlots();
|
||||||
|
if (slots.isEmpty()) {
|
||||||
|
return DEFAULT_SLOTS;
|
||||||
|
}
|
||||||
|
return slots.stream().map(JCSJB::getJcsy)
|
||||||
|
.filter(s -> s != null && !s.isEmpty()).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> mainSlotLabels() {
|
||||||
|
List<JCSJB> slots = loadSlots();
|
||||||
|
if (slots.isEmpty()) {
|
||||||
|
return DEFAULT_MAIN_SLOTS;
|
||||||
|
}
|
||||||
|
Set<String> main = slots.stream()
|
||||||
|
.filter(s -> MAIN_PERIOD_OF_DAY.equals(s.getSd()))
|
||||||
|
.map(JCSJB::getJcsy)
|
||||||
|
.filter(s -> s != null && !s.isEmpty())
|
||||||
|
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||||
|
return main.isEmpty() ? DEFAULT_MAIN_SLOTS : main;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<JCSJB> loadSlots() {
|
||||||
|
List<JCSJB> rows = jcsjbMapper.selectList(new LambdaQueryWrapper<JCSJB>()
|
||||||
|
.eq(JCSJB::getSjc, true));
|
||||||
|
rows.sort(Comparator.comparingInt(r -> PeriodUtil.firstPeriod(r.getJcsy())));
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-24
@@ -37,6 +37,7 @@ import com.roomroot.jwgl.mapper.SemesterCalendarMapper;
|
|||||||
import com.roomroot.jwgl.service.SchedulingWindowService;
|
import com.roomroot.jwgl.service.SchedulingWindowService;
|
||||||
import com.roomroot.jwgl.utils.JwglRoleHelper;
|
import com.roomroot.jwgl.utils.JwglRoleHelper;
|
||||||
import com.roomroot.jwgl.utils.SemesterCodeUtil;
|
import com.roomroot.jwgl.utils.SemesterCodeUtil;
|
||||||
|
import com.roomroot.jwgl.utils.PeriodUtil;
|
||||||
import com.roomroot.jwgl.utils.UuidUtil;
|
import com.roomroot.jwgl.utils.UuidUtil;
|
||||||
import com.roomroot.jwgl.vo.scheduling.SchedulingViewVO;
|
import com.roomroot.jwgl.vo.scheduling.SchedulingViewVO;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@@ -187,7 +188,7 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
|||||||
.eq(XQXLB::getNd, ctx.ndCode));
|
.eq(XQXLB::getNd, ctx.ndCode));
|
||||||
for (XQXLB row : schoolRows) {
|
for (XQXLB row : schoolRows) {
|
||||||
if (row.getJqsj() == null) continue;
|
if (row.getJqsj() == null) continue;
|
||||||
for (Integer jc : parsePeriods(row.getCourseClass())) {
|
for (Integer jc : PeriodUtil.parsePeriods(row.getCourseClass())) {
|
||||||
String key = row.getJqsj() + "#" + jc;
|
String key = row.getJqsj() + "#" + jc;
|
||||||
if (Boolean.FALSE.equals(row.getKpk())) schoolHard.add(key);
|
if (Boolean.FALSE.equals(row.getKpk())) schoolHard.add(key);
|
||||||
else if (Boolean.FALSE.equals(row.getZk())) schoolSoft.add(key);
|
else if (Boolean.FALSE.equals(row.getZk())) schoolSoft.add(key);
|
||||||
@@ -200,7 +201,7 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
|||||||
for (JQB row : classRows) {
|
for (JQB row : classRows) {
|
||||||
if (row.getJqsj() == null) continue;
|
if (row.getJqsj() == null) continue;
|
||||||
String date = intDate(row.getJqsj());
|
String date = intDate(row.getJqsj());
|
||||||
for (Integer jc : parsePeriods(row.getCourseClass())) {
|
for (Integer jc : PeriodUtil.parsePeriods(row.getCourseClass())) {
|
||||||
String key = date + "#" + jc;
|
String key = date + "#" + jc;
|
||||||
if (row.getKpk() != null && row.getKpk() == 0) classHard.add(key);
|
if (row.getKpk() != null && row.getKpk() == 0) classHard.add(key);
|
||||||
else if (row.getZk() != null && row.getZk() == 0) classSoft.add(key);
|
else if (row.getZk() != null && row.getZk() == 0) classSoft.add(key);
|
||||||
@@ -694,7 +695,7 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
|||||||
for (XQXLB row : semesterCalendarMapper.selectList(new LambdaQueryWrapper<XQXLB>()
|
for (XQXLB row : semesterCalendarMapper.selectList(new LambdaQueryWrapper<XQXLB>()
|
||||||
.eq(XQXLB::getNd, ctx.ndCode).eq(XQXLB::getKpk, false))) {
|
.eq(XQXLB::getNd, ctx.ndCode).eq(XQXLB::getKpk, false))) {
|
||||||
if (row.getJqsj() == null) continue;
|
if (row.getJqsj() == null) continue;
|
||||||
for (Integer jc : parsePeriods(row.getCourseClass())) keys.add(row.getJqsj() + "#" + jc);
|
for (Integer jc : PeriodUtil.parsePeriods(row.getCourseClass())) keys.add(row.getJqsj() + "#" + jc);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (JQB row : jqbMapper.selectList(new LambdaQueryWrapper<JQB>()
|
for (JQB row : jqbMapper.selectList(new LambdaQueryWrapper<JQB>()
|
||||||
@@ -702,7 +703,7 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
|||||||
.eq(JQB::getKpk, 0))) {
|
.eq(JQB::getKpk, 0))) {
|
||||||
if (row.getJqsj() == null) continue;
|
if (row.getJqsj() == null) continue;
|
||||||
String date = intDate(row.getJqsj());
|
String date = intDate(row.getJqsj());
|
||||||
for (Integer jc : parsePeriods(row.getCourseClass())) keys.add(date + "#" + jc);
|
for (Integer jc : PeriodUtil.parsePeriods(row.getCourseClass())) keys.add(date + "#" + jc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return keys;
|
return keys;
|
||||||
@@ -781,26 +782,6 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
|||||||
return s.length() == 8 ? s.substring(0, 4) + "-" + s.substring(4, 6) + "-" + s.substring(6, 8) : s;
|
return s.length() == 8 ? s.substring(0, 4) + "-" + s.substring(4, 6) + "-" + s.substring(6, 8) : s;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Integer> parsePeriods(String label) {
|
|
||||||
List<Integer> out = new ArrayList<>();
|
|
||||||
if (label == null) return out;
|
|
||||||
for (String token : label.split("[,,]")) {
|
|
||||||
String t = token.trim();
|
|
||||||
if (t.contains("-")) {
|
|
||||||
String[] parts = t.split("-");
|
|
||||||
try {
|
|
||||||
int a = Integer.parseInt(parts[0].trim());
|
|
||||||
int b = Integer.parseInt(parts[1].trim());
|
|
||||||
for (int i = Math.min(a, b); i <= Math.max(a, b); i++) out.add(i);
|
|
||||||
} catch (Exception ignored) { }
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
out.add(Integer.parseInt(t));
|
|
||||||
} catch (Exception ignored) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String safe(String s) {
|
private static String safe(String s) {
|
||||||
return s == null || s.isEmpty() ? "-" : s;
|
return s == null || s.isEmpty() ? "-" : s;
|
||||||
|
|||||||
+7
-7
@@ -33,15 +33,12 @@ public class SemesterCalendarServiceImpl implements SemesterCalendarService {
|
|||||||
|
|
||||||
private static final DateTimeFormatter DAY = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
private static final DateTimeFormatter DAY = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||||
|
|
||||||
/**
|
|
||||||
* 正课时段:与班历空网格 {@code generateEmptyGrid} 的规则保持一致
|
|
||||||
* (周一至周五的 1-2 / 3-4 / 5-6 节)。
|
|
||||||
*/
|
|
||||||
private static final List<String> MAIN_PERIODS = Arrays.asList("1-2", "3-4", "5-6");
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private SemesterCalendarMapper xqxlbMapper;
|
private SemesterCalendarMapper xqxlbMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private com.roomroot.jwgl.service.ClassPeriodService classPeriodService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private XQMapper xqMapper;
|
private XQMapper xqMapper;
|
||||||
|
|
||||||
@@ -135,8 +132,11 @@ public class SemesterCalendarServiceImpl implements SemesterCalendarService {
|
|||||||
/**
|
/**
|
||||||
* 正课判定:周一至周五的 1-2 / 3-4 / 5-6 节。
|
* 正课判定:周一至周五的 1-2 / 3-4 / 5-6 节。
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* 正课判定:周一至周五且节次段属于「节次时间表」的上午时段。
|
||||||
|
*/
|
||||||
private boolean isMainCourse(LocalDate date, String courseClass) {
|
private boolean isMainCourse(LocalDate date, String courseClass) {
|
||||||
return date.getDayOfWeek().getValue() <= 5 && MAIN_PERIODS.contains(courseClass);
|
return date.getDayOfWeek().getValue() <= 5 && classPeriodService.mainSlotLabels().contains(courseClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+21
-25
@@ -9,8 +9,10 @@ import com.roomroot.jwgl.entity.SSKCB;
|
|||||||
import com.roomroot.jwgl.entity.SSKCXYD;
|
import com.roomroot.jwgl.entity.SSKCXYD;
|
||||||
import com.roomroot.jwgl.entity.TimetableConflictResult;
|
import com.roomroot.jwgl.entity.TimetableConflictResult;
|
||||||
import com.roomroot.jwgl.entity.XYDB;
|
import com.roomroot.jwgl.entity.XYDB;
|
||||||
|
import com.roomroot.jwgl.entity.XYDNDXQJBXXB;
|
||||||
import com.roomroot.jwgl.entity.XQXLB;
|
import com.roomroot.jwgl.entity.XQXLB;
|
||||||
import com.roomroot.jwgl.mapper.ClassRoomCalendarMapper;
|
import com.roomroot.jwgl.mapper.ClassRoomCalendarMapper;
|
||||||
|
import com.roomroot.jwgl.mapper.ClassSemesterMapper;
|
||||||
import com.roomroot.jwgl.mapper.JQBMapper;
|
import com.roomroot.jwgl.mapper.JQBMapper;
|
||||||
import com.roomroot.jwgl.mapper.KBMapper;
|
import com.roomroot.jwgl.mapper.KBMapper;
|
||||||
import com.roomroot.jwgl.mapper.SSKCBFZJYMapper;
|
import com.roomroot.jwgl.mapper.SSKCBFZJYMapper;
|
||||||
@@ -23,6 +25,8 @@ import com.roomroot.jwgl.mapper.SemesterCalendarMapper;
|
|||||||
import com.roomroot.jwgl.mapper.TimetableConflictResultMapper;
|
import com.roomroot.jwgl.mapper.TimetableConflictResultMapper;
|
||||||
import com.roomroot.jwgl.mapper.XYDBMapper;
|
import com.roomroot.jwgl.mapper.XYDBMapper;
|
||||||
import com.roomroot.jwgl.service.TimetableConflictService;
|
import com.roomroot.jwgl.service.TimetableConflictService;
|
||||||
|
import com.roomroot.jwgl.utils.PeriodUtil;
|
||||||
|
import com.roomroot.jwgl.utils.SemesterCodeUtil;
|
||||||
import com.roomroot.jwgl.unit.PageResult;
|
import com.roomroot.jwgl.unit.PageResult;
|
||||||
import com.roomroot.jwgl.unit.conflict.TimetableConflictDimension;
|
import com.roomroot.jwgl.unit.conflict.TimetableConflictDimension;
|
||||||
import com.roomroot.jwgl.vo.timetableconflict.TimetableConflictCardVO;
|
import com.roomroot.jwgl.vo.timetableconflict.TimetableConflictCardVO;
|
||||||
@@ -86,6 +90,9 @@ public class TimetableConflictServiceImpl implements TimetableConflictService {
|
|||||||
@Resource
|
@Resource
|
||||||
private JQBMapper jqbMapper;
|
private JQBMapper jqbMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ClassSemesterMapper classSemesterMapper;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ClassRoomCalendarMapper classRoomCalendarMapper;
|
private ClassRoomCalendarMapper classRoomCalendarMapper;
|
||||||
|
|
||||||
@@ -516,7 +523,7 @@ public class TimetableConflictServiceImpl implements TimetableConflictService {
|
|||||||
.eq(XQXLB::getNd, nd)
|
.eq(XQXLB::getNd, nd)
|
||||||
.eq(XQXLB::getKpk, false));
|
.eq(XQXLB::getKpk, false));
|
||||||
for (XQXLB grid : schoolUnavailable) {
|
for (XQXLB grid : schoolUnavailable) {
|
||||||
for (Integer jc : parsePeriods(grid.getCourseClass())) {
|
for (Integer jc : PeriodUtil.parsePeriods(grid.getCourseClass())) {
|
||||||
List<SSKCB> hits = byGrid.get(grid.getJqsj() + "#" + jc);
|
List<SSKCB> hits = byGrid.get(grid.getJqsj() + "#" + jc);
|
||||||
if (hits != null && !hits.isEmpty()) {
|
if (hits != null && !hits.isEmpty()) {
|
||||||
result.add(buildEventDetail("SCHOOL|" + grid.getJqsj() + "#" + jc, "校历不可排课格",
|
result.add(buildEventDetail("SCHOOL|" + grid.getJqsj() + "#" + jc, "校历不可排课格",
|
||||||
@@ -527,8 +534,18 @@ public class TimetableConflictServiceImpl implements TimetableConflictService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---- 班历:对某学员队可排课=0 的格 ----
|
// ---- 班历:对某学员队可排课=0 的格 ----
|
||||||
List<JQB> classUnavailable = jqbMapper.selectList(new LambdaQueryWrapper<JQB>()
|
// 按班次学期(学员队学期编号)限定:同一年度可能存在多个学期,
|
||||||
.eq(JQB::getNd, nd)
|
// 仅按 年度 过滤会把其它学期的不可排格混进来。
|
||||||
|
List<XYDNDXQJBXXB> semesterRows = classSemesterMapper.selectList(new LambdaQueryWrapper<XYDNDXQJBXXB>()
|
||||||
|
.eq(XYDNDXQJBXXB::getDelFlag, 0));
|
||||||
|
Set<String> semesterBhs = semesterRows.stream()
|
||||||
|
.filter(s -> Objects.equals(nd, SemesterCodeUtil.resolve(s.getNd(), s.getXqdc()))
|
||||||
|
|| Objects.equals(nd, s.getNd()))
|
||||||
|
.map(XYDNDXQJBXXB::getBh)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
List<JQB> classUnavailable = semesterBhs.isEmpty() ? Collections.emptyList()
|
||||||
|
: jqbMapper.selectList(new LambdaQueryWrapper<JQB>()
|
||||||
|
.in(JQB::getXydxqbh, semesterBhs)
|
||||||
.eq(JQB::getKpk, 0));
|
.eq(JQB::getKpk, 0));
|
||||||
if (!classUnavailable.isEmpty()) {
|
if (!classUnavailable.isEmpty()) {
|
||||||
Map<String, SSKCB> lessonByBh = lessons.stream()
|
Map<String, SSKCB> lessonByBh = lessons.stream()
|
||||||
@@ -544,7 +561,7 @@ public class TimetableConflictServiceImpl implements TimetableConflictService {
|
|||||||
for (JQB grid : classUnavailable) {
|
for (JQB grid : classUnavailable) {
|
||||||
if (grid.getJqsj() == null || grid.getCourseClass() == null) continue;
|
if (grid.getJqsj() == null || grid.getCourseClass() == null) continue;
|
||||||
String dateStr = intDateToString(grid.getJqsj());
|
String dateStr = intDateToString(grid.getJqsj());
|
||||||
for (Integer jc : parsePeriods(grid.getCourseClass())) {
|
for (Integer jc : PeriodUtil.parsePeriods(grid.getCourseClass())) {
|
||||||
for (Map.Entry<String, List<String>> e : teamsOfLesson.entrySet()) {
|
for (Map.Entry<String, List<String>> e : teamsOfLesson.entrySet()) {
|
||||||
if (!e.getValue().contains(grid.getXydbh())) continue;
|
if (!e.getValue().contains(grid.getXydbh())) continue;
|
||||||
SSKCB lesson = lessonByBh.get(e.getKey());
|
SSKCB lesson = lessonByBh.get(e.getKey());
|
||||||
@@ -646,27 +663,6 @@ public class TimetableConflictServiceImpl implements TimetableConflictService {
|
|||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 解析历表「节次」标签为节次号集合:"1-2"→[1,2],"3"→[3],无法解析返回空 */
|
|
||||||
private List<Integer> parsePeriods(String label) {
|
|
||||||
List<Integer> out = new ArrayList<>();
|
|
||||||
if (label == null) return out;
|
|
||||||
for (String token : label.split("[,,]")) {
|
|
||||||
String t = token.trim();
|
|
||||||
if (t.contains("-")) {
|
|
||||||
String[] parts = t.split("-");
|
|
||||||
try {
|
|
||||||
int a = Integer.parseInt(parts[0].trim());
|
|
||||||
int b = Integer.parseInt(parts[1].trim());
|
|
||||||
for (int i = Math.min(a, b); i <= Math.max(a, b); i++) out.add(i);
|
|
||||||
} catch (Exception ignored) { }
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
out.add(Integer.parseInt(t));
|
|
||||||
} catch (Exception ignored) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String intDateToString(Integer d) {
|
private static String intDateToString(Integer d) {
|
||||||
if (d == null) return null;
|
if (d == null) return null;
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ public final class OperationLogUtil {
|
|||||||
"教学场地历管理",
|
"教学场地历管理",
|
||||||
"教室输出课程表管理",
|
"教室输出课程表管理",
|
||||||
"学期校历管理",
|
"学期校历管理",
|
||||||
|
"班历管理",
|
||||||
"教材管理",
|
"教材管理",
|
||||||
"教材库存管理",
|
"教材库存管理",
|
||||||
"其他"));
|
"其他"));
|
||||||
@@ -1178,6 +1179,9 @@ public final class OperationLogUtil {
|
|||||||
if (normalizedUrl.contains("/xqxlb/")) {
|
if (normalizedUrl.contains("/xqxlb/")) {
|
||||||
return "学期校历管理";
|
return "学期校历管理";
|
||||||
}
|
}
|
||||||
|
if (normalizedUrl.contains("/class-calendar/")) {
|
||||||
|
return "班历管理";
|
||||||
|
}
|
||||||
if (normalizedUrl.contains("/teachingmaterial/")) {
|
if (normalizedUrl.contains("/teachingmaterial/")) {
|
||||||
return "教材管理";
|
return "教材管理";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.roomroot.jwgl.utils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 历表「节次」标签解析。校历 / 班历 / 场地历的节次段(如 "1-2"、"3"、"1,3-4")
|
||||||
|
* 统一在这里展开为单节次号,供排课窗、冲突检测、历表读写共用。
|
||||||
|
*/
|
||||||
|
public final class PeriodUtil {
|
||||||
|
|
||||||
|
private PeriodUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析节次标签为节次号集合:"1-2"→[1,2],"3"→[3],"1,3-4"→[1,3,4];无法解析返回空。
|
||||||
|
*/
|
||||||
|
public static List<Integer> parsePeriods(String label) {
|
||||||
|
List<Integer> out = new ArrayList<>();
|
||||||
|
if (label == null) {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
for (String token : label.split("[,,]")) {
|
||||||
|
String t = token.trim();
|
||||||
|
if (t.contains("-")) {
|
||||||
|
String[] parts = t.split("-");
|
||||||
|
try {
|
||||||
|
int a = Integer.parseInt(parts[0].trim());
|
||||||
|
int b = Integer.parseInt(parts[1].trim());
|
||||||
|
for (int i = Math.min(a, b); i <= Math.max(a, b); i++) {
|
||||||
|
out.add(i);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
out.add(Integer.parseInt(t));
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节次段的起始节次号(用于排序),无法解析返回 Integer.MAX_VALUE。
|
||||||
|
*/
|
||||||
|
public static int firstPeriod(String label) {
|
||||||
|
List<Integer> parsed = parsePeriods(label);
|
||||||
|
return parsed.isEmpty() ? Integer.MAX_VALUE : parsed.get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package com.roomroot.jwgl.vo.calendar;
|
||||||
|
|
||||||
|
import com.roomroot.jwgl.dto.calendar.ClassCalendarEventDTO;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班历重新同步校历结果。confirm=false 时为差异预览,confirm=true 时为实际补齐结果。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ClassCalendarResyncVO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校历中有、班历缺失的时间格数量
|
||||||
|
*/
|
||||||
|
private int addCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 待补齐的时间格明细(按校历口径)
|
||||||
|
*/
|
||||||
|
private List<ClassCalendarEventDTO> items;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已实际写入(false = 仅预览)
|
||||||
|
*/
|
||||||
|
private boolean executed;
|
||||||
|
}
|
||||||
@@ -16,6 +16,24 @@ export function updateClassCalendarEvent(data) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量保存班历时间格(拖选区域一次提交;cell.action='clear' 恢复默认)
|
||||||
|
export function batchSaveClassCalendarEvent(xydxqbh, cells) {
|
||||||
|
return request({
|
||||||
|
url: '/class-calendar/batchSave',
|
||||||
|
method: 'post',
|
||||||
|
data: { xydxqbh, cells }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新同步校历:confirm=false 返回缺失格预览,confirm=true 实际补齐
|
||||||
|
export function resyncClassCalendar(xydxqbh, confirm) {
|
||||||
|
return request({
|
||||||
|
url: '/class-calendar/resync',
|
||||||
|
method: 'post',
|
||||||
|
params: { xydxqbh, confirm }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function applyClassCalendarEvent(data) {
|
export function applyClassCalendarEvent(data) {
|
||||||
return request({
|
return request({
|
||||||
url: '/class-calendar/apply',
|
url: '/class-calendar/apply',
|
||||||
|
|||||||
@@ -140,7 +140,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { getSemester } from '@/api/teachBusiness/semester'
|
import { getSemester } from '@/api/teachBusiness/semester'
|
||||||
import { listXqxlb, addXqxlb, updateXqxlb } from '@/api/teachBusiness/xqxlb'
|
import { listXqxlb, addXqxlb, updateXqxlb } from '@/api/teachBusiness/xqxlb'
|
||||||
import { listClassCalendarEvent, updateClassCalendarEvent } from '@/api/teachOffice/classCalendarEvent'
|
import { listClassCalendarEvent, updateClassCalendarEvent, batchSaveClassCalendarEvent } from '@/api/teachOffice/classCalendarEvent'
|
||||||
import {
|
import {
|
||||||
listVenueCalendarRange,
|
listVenueCalendarRange,
|
||||||
batchSaveVenueCalendar,
|
batchSaveVenueCalendar,
|
||||||
@@ -1027,6 +1027,22 @@ export default {
|
|||||||
/* ---------- 后端持久化(xqxlb 接口) ---------- */
|
/* ---------- 后端持久化(xqxlb 接口) ---------- */
|
||||||
// 批量保存选中格事件,返回各格子保存成功/失败的个数
|
// 批量保存选中格事件,返回各格子保存成功/失败的个数
|
||||||
persistEvents(keys) {
|
persistEvents(keys) {
|
||||||
|
// 班历走批量接口:一次拖选只发一个请求,整批单事务提交
|
||||||
|
if (this.mode === 'class') {
|
||||||
|
const cells = keys
|
||||||
|
.filter(key => this.events[key])
|
||||||
|
.map(key => this.buildXqxlbPayload(key, this.events[key]))
|
||||||
|
.filter(Boolean)
|
||||||
|
if (!cells.length) return Promise.resolve({ ok: 0, fail: 0 })
|
||||||
|
return batchSaveClassCalendarEvent(this.xydxqbh, cells).then(res => {
|
||||||
|
this.$message.success((res && res.message) || '已设定 ' + cells.length + ' 个时间格')
|
||||||
|
this.reloadEvents()
|
||||||
|
return { ok: cells.length, fail: 0 }
|
||||||
|
}).catch(() => {
|
||||||
|
this.$message.error('班历批量保存失败')
|
||||||
|
return { ok: 0, fail: cells.length }
|
||||||
|
})
|
||||||
|
}
|
||||||
const tasks = keys
|
const tasks = keys
|
||||||
.filter(key => this.events[key])
|
.filter(key => this.events[key])
|
||||||
.map(key => {
|
.map(key => {
|
||||||
@@ -1051,8 +1067,29 @@ export default {
|
|||||||
return { ok: ok, fail: fail }
|
return { ok: ok, fail: fail }
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
// 批量删除选中格事件:调用 update 接口并将 jqmc 置空完成删除
|
// 批量删除选中格事件:班历走 batchSave 的 action='clear' 恢复默认,校历仍走 update 置空名称
|
||||||
persistDeleteEvents(keys) {
|
persistDeleteEvents(keys) {
|
||||||
|
if (this.mode === 'class') {
|
||||||
|
const cells = keys
|
||||||
|
.filter(key => this.events[key])
|
||||||
|
.map(key => {
|
||||||
|
const payload = this.buildXqxlbPayload(key, this.events[key])
|
||||||
|
if (!payload) return null
|
||||||
|
payload.jqmc = ''
|
||||||
|
payload.action = 'clear'
|
||||||
|
return payload
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
if (!cells.length) return Promise.resolve({ ok: 0, fail: 0 })
|
||||||
|
return batchSaveClassCalendarEvent(this.xydxqbh, cells).then(() => {
|
||||||
|
this.$message.success('已删除 ' + cells.length + ' 个时间格事件')
|
||||||
|
this.reloadEvents()
|
||||||
|
return { ok: cells.length, fail: 0 }
|
||||||
|
}).catch(() => {
|
||||||
|
this.$message.error('班历批量删除失败')
|
||||||
|
return { ok: 0, fail: cells.length }
|
||||||
|
})
|
||||||
|
}
|
||||||
const tasks = keys
|
const tasks = keys
|
||||||
.filter(key => this.events[key])
|
.filter(key => this.events[key])
|
||||||
.map(key => {
|
.map(key => {
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
<div class="dialog-toolbar">
|
<div class="dialog-toolbar">
|
||||||
<el-button size="small" :disabled="!semesterBh" @click="handleApplyRegion">选定区域应用于其它班次</el-button>
|
<el-button size="small" :disabled="!semesterBh" @click="handleApplyRegion">选定区域应用于其它班次</el-button>
|
||||||
<el-button size="small" :disabled="!semesterBh" @click="handleApplyWhole">整个班历应用于其它班次</el-button>
|
<el-button size="small" :disabled="!semesterBh" @click="handleApplyWhole">整个班历应用于其它班次</el-button>
|
||||||
<span class="hint">应用到其它班次只覆盖班历时间格,不改课程任务。</span>
|
<el-button size="small" :disabled="!semesterBh" :loading="resyncLoading" @click="handleResync">同步校历</el-button>
|
||||||
|
<span class="hint">应用到其它班次只覆盖班历时间格,不改课程任务;同步校历只补缺失格,不覆盖人工改动。</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="editor-wrap">
|
<div class="editor-wrap">
|
||||||
<SchoolCalendarEditor
|
<SchoolCalendarEditor
|
||||||
@@ -42,7 +43,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import SchoolCalendarEditor from '@/views/teachBusiness/calendar/components/SchoolCalendarEditor.vue'
|
import SchoolCalendarEditor from '@/views/teachBusiness/calendar/components/SchoolCalendarEditor.vue'
|
||||||
import ClassTeamSelectDialog from './ClassTeamSelectDialog.vue'
|
import ClassTeamSelectDialog from './ClassTeamSelectDialog.vue'
|
||||||
import { applyClassCalendarEvent } from '@/api/teachOffice/classCalendarEvent'
|
import { applyClassCalendarEvent, resyncClassCalendar } from '@/api/teachOffice/classCalendarEvent'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ClassEventCalendarDialog',
|
name: 'ClassEventCalendarDialog',
|
||||||
@@ -55,7 +56,8 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
teamSelectVisible: false,
|
teamSelectVisible: false,
|
||||||
applyWhole: false
|
applyWhole: false,
|
||||||
|
resyncLoading: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -132,6 +134,32 @@ export default {
|
|||||||
this.teamSelectVisible = false
|
this.teamSelectVisible = false
|
||||||
this.$message.success(`已覆盖 ${res.data || 0} 个时间格`)
|
this.$message.success(`已覆盖 ${res.data || 0} 个时间格`)
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
handleResync() {
|
||||||
|
this.resyncLoading = true
|
||||||
|
resyncClassCalendar(this.semesterBh, false).then(res => {
|
||||||
|
const data = res && res.data
|
||||||
|
if (!data || !data.addCount) {
|
||||||
|
this.$message.info('班历已与校历一致,无需同步')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const lines = (data.items || []).slice(0, 20)
|
||||||
|
.map(i => `${i.jqsj} ${i.courseClass}节 ${i.jqmc || ''}${i.kpk === false ? '(不可排课)' : ''}`)
|
||||||
|
const more = data.addCount > lines.length ? `<br/>…共 ${data.addCount} 格` : ''
|
||||||
|
this.$confirm(
|
||||||
|
`校历中有 ${data.addCount} 个班历缺失的时间格:<br/>${lines.join('<br/>')}${more}<br/><br/>补齐这些格子?(不覆盖已有班历内容)`,
|
||||||
|
'同步校历',
|
||||||
|
{ dangerouslyUseHTMLString: true, confirmButtonText: '补齐', cancelButtonText: '取消' }
|
||||||
|
).then(() => {
|
||||||
|
return resyncClassCalendar(this.semesterBh, true).then(r => {
|
||||||
|
this.$message.success(`已补齐 ${(r.data && r.data.addCount) || 0} 个时间格`)
|
||||||
|
const editor = this.$refs.editor
|
||||||
|
if (editor && editor.reloadEvents) editor.reloadEvents()
|
||||||
|
})
|
||||||
|
}).catch(() => {})
|
||||||
|
}).finally(() => {
|
||||||
|
this.resyncLoading = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user