forked from liweijie/education
排课功能优化
This commit is contained in:
+95
@@ -1,7 +1,10 @@
|
|||||||
package com.roomroot.web.controller.jwgl;
|
package com.roomroot.web.controller.jwgl;
|
||||||
|
|
||||||
|
import com.roomroot.common.exception.ServiceException;
|
||||||
import com.roomroot.jwgl.dto.scheduling.SchedulingArrangeRequest;
|
import com.roomroot.jwgl.dto.scheduling.SchedulingArrangeRequest;
|
||||||
import com.roomroot.jwgl.dto.scheduling.SchedulingCellOpRequest;
|
import com.roomroot.jwgl.dto.scheduling.SchedulingCellOpRequest;
|
||||||
|
import com.roomroot.jwgl.dto.scheduling.SchedulingCourseEditRequest;
|
||||||
|
import com.roomroot.jwgl.service.RunningCoursePublishService;
|
||||||
import com.roomroot.jwgl.service.SchedulingWindowService;
|
import com.roomroot.jwgl.service.SchedulingWindowService;
|
||||||
import com.roomroot.jwgl.unit.Result;
|
import com.roomroot.jwgl.unit.Result;
|
||||||
import com.roomroot.jwgl.vo.scheduling.SchedulingViewVO;
|
import com.roomroot.jwgl.vo.scheduling.SchedulingViewVO;
|
||||||
@@ -14,6 +17,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -30,6 +35,9 @@ public class SchedulingWindowController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private SchedulingWindowService schedulingWindowService;
|
private SchedulingWindowService schedulingWindowService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RunningCoursePublishService runningCoursePublishService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 排课窗组合视图:班次头 + 课程列表(排满标绿)+ 周次×星期×节次格子。
|
* 排课窗组合视图:班次头 + 课程列表(排满标绿)+ 周次×星期×节次格子。
|
||||||
*/
|
*/
|
||||||
@@ -84,4 +92,91 @@ public class SchedulingWindowController {
|
|||||||
@RequestParam(value = "czlx", required = false) String czlx) {
|
@RequestParam(value = "czlx", required = false) String czlx) {
|
||||||
return Result.success(schedulingWindowService.logs(sskcbh, czlx));
|
return Result.success(schedulingWindowService.logs(sskcbh, czlx));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑课程教学任务信息(手册 12.3.1):学时/周课时/课程简称/责任教员/默认场地。
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('teach:schedulingWindow:edit')")
|
||||||
|
@PostMapping("/course/update")
|
||||||
|
public Result<Map<String, Object>> updateCourse(@RequestBody SchedulingCourseEditRequest request) {
|
||||||
|
return Result.success(schedulingWindowService.updateCourse(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 教学班次(合班)当前列表 + 候选班次(手册 12.3.2.2)。
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('teach:schedulingWindow:list')")
|
||||||
|
@GetMapping("/course/teams")
|
||||||
|
public Result<Map<String, Object>> courseTeams(@RequestParam("sskcbh") String sskcbh) {
|
||||||
|
return Result.success(schedulingWindowService.courseTeams(sskcbh));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 教学保障选项:保障类别 + 保障资源(含总量与计量单位,手册 12.3.2.5)。
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('teach:schedulingWindow:list')")
|
||||||
|
@GetMapping("/support/options")
|
||||||
|
public Result<Map<String, Object>> supportOptions() {
|
||||||
|
return Result.success(schedulingWindowService.supportOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量发布【运行课表】(手册 12.1):把班次学期的课程任务发布为运行课程。
|
||||||
|
*
|
||||||
|
* <p>发布前要求教学任务已发布未结束、责任教员与默认场地齐备;逐个班次独立事务,
|
||||||
|
* 单个失败不影响其它班次,失败原因逐条返回。</p>
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('teach:schedulingWindow:edit')")
|
||||||
|
@PostMapping("/publish")
|
||||||
|
public Result<List<Map<String, Object>>> publish(@RequestBody PublishRequest request) {
|
||||||
|
return Result.success(batchPublish(request, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除【运行课表】(手册 12.2):撤回该班次学期的运行课程,已排课次一并删除。
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('teach:schedulingWindow:edit')")
|
||||||
|
@PostMapping("/withdraw")
|
||||||
|
public Result<List<Map<String, Object>>> withdraw(@RequestBody PublishRequest request) {
|
||||||
|
return Result.success(batchPublish(request, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量发布/撤回公共实现 */
|
||||||
|
private List<Map<String, Object>> batchPublish(PublishRequest request, boolean publish) {
|
||||||
|
if (request == null || request.getXydxqbhs() == null || request.getXydxqbhs().isEmpty()) {
|
||||||
|
throw new ServiceException("请选择要操作的班次学期", 400);
|
||||||
|
}
|
||||||
|
List<Map<String, Object>> results = new ArrayList<>();
|
||||||
|
for (String xydxqbh : request.getXydxqbhs()) {
|
||||||
|
Map<String, Object> item = new HashMap<>();
|
||||||
|
item.put("xydxqbh", xydxqbh);
|
||||||
|
try {
|
||||||
|
int count = publish
|
||||||
|
? runningCoursePublishService.publish(xydxqbh)
|
||||||
|
: runningCoursePublishService.withdraw(xydxqbh);
|
||||||
|
item.put("success", true);
|
||||||
|
item.put("count", count);
|
||||||
|
item.put("message", publish ? ("已发布 " + count + " 条运行课程") : ("已删除 " + count + " 条运行课程"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
item.put("success", false);
|
||||||
|
item.put("count", 0);
|
||||||
|
item.put("message", e.getMessage() == null ? "操作失败" : e.getMessage());
|
||||||
|
}
|
||||||
|
results.add(item);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量发布/删除请求体 */
|
||||||
|
public static class PublishRequest {
|
||||||
|
private List<String> xydxqbhs;
|
||||||
|
|
||||||
|
public List<String> getXydxqbhs() {
|
||||||
|
return xydxqbhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setXydxqbhs(List<String> xydxqbhs) {
|
||||||
|
this.xydxqbhs = xydxqbhs;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
@@ -70,4 +70,16 @@ public class TeacherCalendarController {
|
|||||||
public Result<List<JYL>> listByRq(@RequestParam("rq") String rq) {
|
public Result<List<JYL>> listByRq(@RequestParam("rq") String rq) {
|
||||||
return Result.success(jylService.listByRq(rq));
|
return Result.success(jylService.listByRq(rq));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 批量保存教员历(教员历编辑器整片提交,按 教员+日期+节次 幂等覆盖) */
|
||||||
|
@PostMapping("/batchSave")
|
||||||
|
public Result<Integer> batchSave(@RequestBody List<JYL> list) {
|
||||||
|
return Result.success(jylService.batchSave(list));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量删除教员历(本表无 del_flag,物理删除) */
|
||||||
|
@PostMapping("/batchDelete")
|
||||||
|
public Result<Integer> batchDelete(@RequestBody List<String> bhs) {
|
||||||
|
return Result.success(jylService.batchDelete(bhs));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+23
@@ -42,9 +42,32 @@ public class SchedulingArrangeRequest {
|
|||||||
/** 主讲教员,缺省用课程责任教员 */
|
/** 主讲教员,缺省用课程责任教员 */
|
||||||
private String jybh;
|
private String jybh;
|
||||||
|
|
||||||
|
/** 教学班次(学员队编号,本课程默认全部合班班次,可临时增删) */
|
||||||
|
private List<String> xydbhs;
|
||||||
|
|
||||||
|
/** 辅讲教员编号(可多个) */
|
||||||
|
private List<String> fzjybhs;
|
||||||
|
|
||||||
|
/** 教学场地(可多场地),为空则用 jsbh / 课程默认场地 */
|
||||||
|
private List<String> jsbhs;
|
||||||
|
|
||||||
|
/** 教学保障需求(结构化:类别→资源→数量) */
|
||||||
|
private List<Support> supports;
|
||||||
|
|
||||||
/** 所选格子 */
|
/** 所选格子 */
|
||||||
private List<Cell> cells;
|
private List<Cell> cells;
|
||||||
|
|
||||||
|
/** 教学保障需求项 */
|
||||||
|
@Data
|
||||||
|
public static class Support {
|
||||||
|
/** 保障提供明细编号(保障资源库) */
|
||||||
|
private String bztgmxbh;
|
||||||
|
/** 需求数量 */
|
||||||
|
private Integer sl;
|
||||||
|
/** 备注 */
|
||||||
|
private String bz;
|
||||||
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class Cell {
|
public static class Cell {
|
||||||
/** 日期 yyyy-MM-dd */
|
/** 日期 yyyy-MM-dd */
|
||||||
|
|||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
package com.roomroot.jwgl.dto.scheduling;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排课窗口内「编辑课程教学任务信息」请求(手册 12.3.1)。
|
||||||
|
*
|
||||||
|
* <p>只允许改运行编制的可调项:学时、周课时、课程简称、责任教员、默认场地。
|
||||||
|
* 课程简称写回学员队任务表(编制侧)后运行课程同步显示。</p>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SchedulingCourseEditRequest {
|
||||||
|
|
||||||
|
/** 实施_课程编号 */
|
||||||
|
private String sskcbh;
|
||||||
|
|
||||||
|
/** 学时 */
|
||||||
|
private Integer xs;
|
||||||
|
|
||||||
|
/** 周课时 */
|
||||||
|
private Integer zks;
|
||||||
|
|
||||||
|
/** 课程简称 */
|
||||||
|
private String jc;
|
||||||
|
|
||||||
|
/** 责任教员编号 */
|
||||||
|
private String jybh;
|
||||||
|
|
||||||
|
/** 默认教学场地(教室编号) */
|
||||||
|
private String jsbh;
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
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;
|
||||||
@@ -22,66 +23,79 @@ public class BZTGMX {
|
|||||||
/**
|
/**
|
||||||
* 保障类别编号
|
* 保障类别编号
|
||||||
*/
|
*/
|
||||||
|
@TableField("保障类别编号")
|
||||||
private String bzlbbh;
|
private String bzlbbh;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 名称
|
* 名称
|
||||||
*/
|
*/
|
||||||
|
@TableField("名称")
|
||||||
private String mc;
|
private String mc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单位
|
* 单位
|
||||||
*/
|
*/
|
||||||
|
@TableField("单位")
|
||||||
private String dw;
|
private String dw;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数量
|
* 数量
|
||||||
*/
|
*/
|
||||||
|
@TableField("数量")
|
||||||
private Integer sl;
|
private Integer sl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 备注
|
* 备注
|
||||||
*/
|
*/
|
||||||
|
@TableField("备注")
|
||||||
private String bz;
|
private String bz;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 停用
|
* 停用
|
||||||
*/
|
*/
|
||||||
|
@TableField("停用")
|
||||||
private Integer ty;
|
private Integer ty;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 健康状态
|
* 健康状态
|
||||||
*/
|
*/
|
||||||
|
@TableField("健康状态")
|
||||||
private Float jkzt;
|
private Float jkzt;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 详细
|
* 详细
|
||||||
*/
|
*/
|
||||||
|
@TableField("详细")
|
||||||
private String xx;
|
private String xx;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 耗材
|
* 耗材
|
||||||
*/
|
*/
|
||||||
|
@TableField("耗材")
|
||||||
private Integer hc;
|
private Integer hc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 拼音
|
* 拼音
|
||||||
*/
|
*/
|
||||||
|
@TableField("拼音")
|
||||||
private String py;
|
private String py;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 资源类别
|
* 资源类别
|
||||||
*/
|
*/
|
||||||
|
@TableField("资源类别")
|
||||||
private String zylb;
|
private String zylb;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSONZD
|
* JSONZD
|
||||||
*/
|
*/
|
||||||
|
@TableField("JSONZD")
|
||||||
private String jsonzd;
|
private String jsonzd;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 危险品
|
* 危险品
|
||||||
*/
|
*/
|
||||||
|
@TableField("危险品")
|
||||||
private Integer wxp;
|
private Integer wxp;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
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;
|
||||||
@@ -24,36 +25,43 @@ public class KCBBZMX {
|
|||||||
/**
|
/**
|
||||||
* 课程表编号
|
* 课程表编号
|
||||||
*/
|
*/
|
||||||
|
@TableField("课程表编号")
|
||||||
private String kcbbh;
|
private String kcbbh;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保障提供明细编号
|
* 保障提供明细编号
|
||||||
*/
|
*/
|
||||||
|
@TableField("保障提供明细编号")
|
||||||
private String bztgmxbh;
|
private String bztgmxbh;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数量
|
* 数量
|
||||||
*/
|
*/
|
||||||
|
@TableField("数量")
|
||||||
private Integer sl;
|
private Integer sl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 备注
|
* 备注
|
||||||
*/
|
*/
|
||||||
|
@TableField("备注")
|
||||||
private String bz;
|
private String bz;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 年度
|
* 年度
|
||||||
*/
|
*/
|
||||||
|
@TableField("年度")
|
||||||
private Integer nd;
|
private Integer nd;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日期
|
* 日期
|
||||||
*/
|
*/
|
||||||
|
@TableField("日期")
|
||||||
private LocalDateTime rq;
|
private LocalDateTime rq;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 节次
|
* 节次
|
||||||
*/
|
*/
|
||||||
|
@TableField("节次")
|
||||||
private Integer jc;
|
private Integer jc;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,18 @@ public interface SSKCMapper extends BaseMapper<SSKC> {
|
|||||||
|
|
||||||
List<SSKC> selectBySemester(@Param("nd") Integer nd, @Param("xqdc") Integer xqdc);
|
List<SSKC> selectBySemester(@Param("nd") Integer nd, @Param("xqdc") Integer xqdc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按年度查询全部实施_课程(不做停用过滤)。
|
||||||
|
* <p>禁用 {@code selectList(wrapper)} 读本表:中文列名 + VARBINARY 主键下,
|
||||||
|
* MyBatis-Plus 自动 SQL 的列标签与 resultMap 列名不匹配,会整行返回 null 元素。</p>
|
||||||
|
*/
|
||||||
|
List<SSKC> selectByNd(@Param("nd") Integer nd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按科目编号查询实施_课程(同样绕开 selectList,原因见上)。
|
||||||
|
*/
|
||||||
|
List<SSKC> selectByKmbh(@Param("kmbh") String kmbh);
|
||||||
|
|
||||||
List<SSKC> selectByClassBh(@Param("classBh") String classBh, @Param("nd") Integer nd);
|
List<SSKC> selectByClassBh(@Param("classBh") String classBh, @Param("nd") Integer nd);
|
||||||
|
|
||||||
SSKC selectByCourseBhAndClassBh(@Param("courseBh") String courseBh, @Param("classBh") String classBh, @Param("nd") Integer nd);
|
SSKC selectByCourseBhAndClassBh(@Param("courseBh") String courseBh, @Param("classBh") String classBh, @Param("nd") Integer nd);
|
||||||
@@ -20,4 +32,20 @@ public interface SSKCMapper extends BaseMapper<SSKC> {
|
|||||||
* 按编号批量查询(编号为 VARBINARY GUID,不能用 selectBatchIds——主键 typeHandler 对 IN 参数不生效)
|
* 按编号批量查询(编号为 VARBINARY GUID,不能用 selectBatchIds——主键 typeHandler 对 IN 参数不生效)
|
||||||
*/
|
*/
|
||||||
List<SSKC> selectByBhs(@Param("list") List<String> bhs);
|
List<SSKC> selectByBhs(@Param("list") List<String> bhs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按编号批量删除(同上:编号为 VARBINARY GUID,deleteById 直绑字符串会报
|
||||||
|
* dm.jdbc.driver.DMException: Invalid hexadecimal digits)。
|
||||||
|
*
|
||||||
|
* @return 删除行数
|
||||||
|
*/
|
||||||
|
int deleteByBhs(@Param("list") List<String> bhs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按编号更新运行编制可调项(学时 / 周课时 / 责任教员 / 默认场地)。
|
||||||
|
* 同样绕开 GUID 主键直接绑定:updateById 会对主键绑字符串而失败。
|
||||||
|
* 传 null 的字段不更新。
|
||||||
|
*/
|
||||||
|
int updateBasicByBh(@Param("bh") String bh, @Param("xs") Integer xs, @Param("zks") Integer zks,
|
||||||
|
@Param("jybh") String jybh, @Param("jsbh") String jsbh);
|
||||||
}
|
}
|
||||||
@@ -2,9 +2,46 @@
|
|||||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
<mapper namespace="com.roomroot.jwgl.mapper.SSKCMapper">
|
<mapper namespace="com.roomroot.jwgl.mapper.SSKCMapper">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
注意:达梦业务表列名为中文,MyBatis 的 autoMapping 按列标签匹配 Java 属性名,
|
||||||
|
"科目编号" 无法自动映射到 kmbh、"学时" 无法映射到 xs,会全部读出 null。
|
||||||
|
因此这里必须逐列显式声明;autoMapping 仅作兜底。
|
||||||
|
-->
|
||||||
<resultMap id="SSKCMap" type="com.roomroot.jwgl.entity.SSKC" autoMapping="true">
|
<resultMap id="SSKCMap" type="com.roomroot.jwgl.entity.SSKC" autoMapping="true">
|
||||||
<id column="编号" property="bh" jdbcType="VARBINARY"
|
<id column="编号" property="bh" jdbcType="VARBINARY"
|
||||||
typeHandler="com.roomroot.jwgl.handler.DmGuidTypeHandler"/>
|
typeHandler="com.roomroot.jwgl.handler.DmGuidTypeHandler"/>
|
||||||
|
<result column="年度" property="nd"/>
|
||||||
|
<result column="教员编号" property="jybh"/>
|
||||||
|
<result column="教员课次序号" property="jykcxh"/>
|
||||||
|
<result column="科目编号" property="kmbh"/>
|
||||||
|
<result column="教室编号" property="jsbh"/>
|
||||||
|
<result column="学时" property="xs"/>
|
||||||
|
<result column="课类型" property="klx"/>
|
||||||
|
<result column="课程标准编号" property="ktbzbh"/>
|
||||||
|
<result column="课程课时系数" property="kcksxs"/>
|
||||||
|
<result column="课程学员测评状态" property="kcxycpzt"/>
|
||||||
|
<result column="学分" property="xf"/>
|
||||||
|
<result column="创建时间" property="cjsj"/>
|
||||||
|
<result column="变动时间" property="bdsj"/>
|
||||||
|
<result column="课表变动时间" property="kbbdsj"/>
|
||||||
|
<result column="测评结果样式编号" property="cpjgygbh"/>
|
||||||
|
<result column="理论学时" property="llxs"/>
|
||||||
|
<result column="实践学时" property="sjxs"/>
|
||||||
|
<result column="考核学时" property="khxs"/>
|
||||||
|
<result column="运行学时" property="yxxs"/>
|
||||||
|
<result column="运行理论学时" property="yxllxs"/>
|
||||||
|
<result column="运行实践学时" property="yxsjxs"/>
|
||||||
|
<result column="运行考核学时" property="yxkhxs"/>
|
||||||
|
<result column="成绩分制" property="cjfz"/>
|
||||||
|
<result column="不计入学员平均分" property="bjrxypjf"/>
|
||||||
|
<result column="周课时" property="zks"/>
|
||||||
|
<result column="预计测评结束时间" property="yjcpjssj"/>
|
||||||
|
<result column="分值权重标准编号" property="fzqzbzbh"/>
|
||||||
|
<result column="JSONZD" property="jsonzd"/>
|
||||||
|
<result column="评学综合得分" property="pxxhdf"/>
|
||||||
|
<result column="确认评学综合" property="qrpxzh"/>
|
||||||
|
<result column="责任教员编号" property="zrjybh"/>
|
||||||
|
<result column="教员评学_结束状态" property="jypxjszt"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<select id="selectBySemester" resultMap="SSKCMap">
|
<select id="selectBySemester" resultMap="SSKCMap">
|
||||||
@@ -12,6 +49,24 @@
|
|||||||
WHERE 年度 = #{nd} AND 学期第次 = #{xqdc} AND 停用 = 0
|
WHERE 年度 = #{nd} AND 学期第次 = #{xqdc} AND 停用 = 0
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
按年度查询全部实施_课程。
|
||||||
|
注意:本表列名为中文、主键为 VARBINARY(16),MyBatis-Plus 自动 SQL(BaseMapper.selectList 等)
|
||||||
|
的列标签与 resultMap 的列名对不上,会让每一行都判为"未匹配任何列"而返回 null 元素
|
||||||
|
(MyBatis returnInstanceForEmptyRow 默认 false 时会丢弃整行)。
|
||||||
|
因此凡是要按条件读本表,一律走显式 XML,不要用 selectList。
|
||||||
|
-->
|
||||||
|
<select id="selectByNd" resultMap="SSKCMap">
|
||||||
|
SELECT * FROM 实施_课程
|
||||||
|
WHERE 年度 = #{nd}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 同上:按科目编号查询,禁用 selectList -->
|
||||||
|
<select id="selectByKmbh" resultMap="SSKCMap">
|
||||||
|
SELECT * FROM 实施_课程
|
||||||
|
WHERE 科目编号 = #{kmbh}
|
||||||
|
</select>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
达梦:实施_课程.编号 为 VARBINARY(16),实施_课程学员队.实施_课程编号 为 CHAR(36)。
|
达梦:实施_课程.编号 为 VARBINARY(16),实施_课程学员队.实施_课程编号 为 CHAR(36)。
|
||||||
禁止 编号 = 实施_课程编号:达梦会把 CHAR 当十六进制转 VARBINARY,UUID 中的 '-' 报 -6147。
|
禁止 编号 = 实施_课程编号:达梦会把 CHAR 当十六进制转 VARBINARY,UUID 中的 '-' 报 -6147。
|
||||||
@@ -40,4 +95,26 @@
|
|||||||
</foreach>
|
</foreach>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- 批量删除:同一原因,deleteById 直绑字符串会报 Invalid hexadecimal digits -->
|
||||||
|
<delete id="deleteByBhs">
|
||||||
|
DELETE FROM 实施_课程
|
||||||
|
WHERE LOWER(RAWTOHEX("编号")) IN
|
||||||
|
<foreach collection="list" item="bh" open="(" separator="," close=")">
|
||||||
|
LOWER(REPLACE(TRIM(#{bh}), '-', ''))
|
||||||
|
</foreach>
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
<!-- 更新运行编制可调项:同样绕开 GUID 主键直接绑定 -->
|
||||||
|
<update id="updateBasicByBh">
|
||||||
|
UPDATE 实施_课程
|
||||||
|
<set>
|
||||||
|
<if test="xs != null">"学时" = #{xs},</if>
|
||||||
|
<if test="zks != null">"周课时" = #{zks},</if>
|
||||||
|
<if test="jybh != null and jybh != ''">"教员编号" = #{jybh},</if>
|
||||||
|
<if test="jsbh != null and jsbh != ''">"教室编号" = #{jsbh},</if>
|
||||||
|
"变动时间" = CURRENT_TIMESTAMP
|
||||||
|
</set>
|
||||||
|
WHERE LOWER(RAWTOHEX("编号")) = LOWER(REPLACE(TRIM(#{bh}), '-', ''))
|
||||||
|
</update>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -26,4 +26,19 @@ public interface JYLService {
|
|||||||
List<JYL> listByJybh(String jybh);
|
List<JYL> listByJybh(String jybh);
|
||||||
|
|
||||||
List<JYL> listByRq(String rq);
|
List<JYL> listByRq(String rq);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存教员历(供教员历编辑器整片提交)。
|
||||||
|
* <p>按 (教员编号 + 日期 + 节次) 幂等覆盖:已有则更新,没有则新增。</p>
|
||||||
|
*
|
||||||
|
* @return 实际写入/更新的条数
|
||||||
|
*/
|
||||||
|
int batchSave(List<JYL> list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除教员历(本表无 del_flag 审计列,为物理删除)。
|
||||||
|
*
|
||||||
|
* @return 实际删除条数
|
||||||
|
*/
|
||||||
|
int batchDelete(List<String> bhs);
|
||||||
}
|
}
|
||||||
|
|||||||
+10
@@ -2,6 +2,7 @@ package com.roomroot.jwgl.service;
|
|||||||
|
|
||||||
import com.roomroot.jwgl.dto.scheduling.SchedulingArrangeRequest;
|
import com.roomroot.jwgl.dto.scheduling.SchedulingArrangeRequest;
|
||||||
import com.roomroot.jwgl.dto.scheduling.SchedulingCellOpRequest;
|
import com.roomroot.jwgl.dto.scheduling.SchedulingCellOpRequest;
|
||||||
|
import com.roomroot.jwgl.dto.scheduling.SchedulingCourseEditRequest;
|
||||||
import com.roomroot.jwgl.vo.scheduling.SchedulingViewVO;
|
import com.roomroot.jwgl.vo.scheduling.SchedulingViewVO;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -36,4 +37,13 @@ public interface SchedulingWindowService {
|
|||||||
|
|
||||||
/** 排课日志查询:按课程(可叠加操作类型) */
|
/** 排课日志查询:按课程(可叠加操作类型) */
|
||||||
List<Map<String, Object>> logs(String sskcbh, String czlx);
|
List<Map<String, Object>> logs(String sskcbh, String czlx);
|
||||||
|
|
||||||
|
/** 编辑课程教学任务信息:学时/周课时/课程简称/责任教员/默认场地(手册 12.3.1) */
|
||||||
|
Map<String, Object> updateCourse(SchedulingCourseEditRequest request);
|
||||||
|
|
||||||
|
/** 教学班次(合班)当前列表 + 候选班次(手册 12.3.2.2) */
|
||||||
|
Map<String, Object> courseTeams(String sskcbh);
|
||||||
|
|
||||||
|
/** 教学保障选项:保障类别 + 保障资源(含总量与计量单位,手册 12.3.2.5) */
|
||||||
|
Map<String, Object> supportOptions();
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -19,7 +19,8 @@ import java.util.Map;
|
|||||||
* <li>合班分组号用已有列 学员队任务表.编组(bz2),0=未合班;不复用 预设编班号 和 课次序号。</li>
|
* <li>合班分组号用已有列 学员队任务表.编组(bz2),0=未合班;不复用 预设编班号 和 课次序号。</li>
|
||||||
* <li>发布门禁:仅「已发布且未结束」的教学任务可填报;结束后只读(TeachingTaskWriteGuard)。</li>
|
* <li>发布门禁:仅「已发布且未结束」的教学任务可填报;结束后只读(TeachingTaskWriteGuard)。</li>
|
||||||
* <li>责任教员 / 场地修改时,同合班组(同编组,非 0)的行同步。</li>
|
* <li>责任教员 / 场地修改时,同合班组(同编组,非 0)的行同步。</li>
|
||||||
* <li>已发布到运行课表的行须先撤回才能拆班(运行课表属阶段 5,当前先拦截有排课数据的行)。</li>
|
* <li>已发布到运行课表的行须先撤回才能拆班:按「实施_课程学员队」(学员队编号, 年度=6 位学期代号) 判定,
|
||||||
|
* 与运行课表发布的幂等门禁同源;拆班会让任务书合班组与已固化的运行课程不一致。</li>
|
||||||
* </ul></p>
|
* </ul></p>
|
||||||
*/
|
*/
|
||||||
public interface TaskBookFillService {
|
public interface TaskBookFillService {
|
||||||
|
|||||||
+86
@@ -5,13 +5,19 @@ import com.roomroot.common.utils.StringUtils;
|
|||||||
import com.roomroot.jwgl.entity.JYL;
|
import com.roomroot.jwgl.entity.JYL;
|
||||||
import com.roomroot.jwgl.mapper.JYLMapper;
|
import com.roomroot.jwgl.mapper.JYLMapper;
|
||||||
import com.roomroot.jwgl.service.JYLService;
|
import com.roomroot.jwgl.service.JYLService;
|
||||||
|
import com.roomroot.jwgl.unit.BusinessException;
|
||||||
import com.roomroot.jwgl.unit.PageQuery;
|
import com.roomroot.jwgl.unit.PageQuery;
|
||||||
import com.roomroot.jwgl.unit.PageResult;
|
import com.roomroot.jwgl.unit.PageResult;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,6 +87,86 @@ public class JYLServiceImpl implements JYLService {
|
|||||||
.apply("DATE(rq) = {0}", rq));
|
.apply("DATE(rq) = {0}", rq));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存:按 (教员编号 + 日期 + 节次) 幂等覆盖。
|
||||||
|
* <p>先按涉及的教员 + 日期区间一次性把已有记录拉回来判重,避免逐行查库。</p>
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int batchSave(List<JYL> list) {
|
||||||
|
if (list == null || list.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Set<String> jybhSet = new HashSet<>();
|
||||||
|
LocalDate minDay = null;
|
||||||
|
LocalDate maxDay = null;
|
||||||
|
for (JYL item : list) {
|
||||||
|
if (item == null || StringUtils.isEmpty(item.getJybh())) {
|
||||||
|
throw new BusinessException("教员历缺少教员编号");
|
||||||
|
}
|
||||||
|
if (item.getRq() == null || item.getJc() == null) {
|
||||||
|
throw new BusinessException("教员历缺少日期或节次");
|
||||||
|
}
|
||||||
|
item.setJybh(item.getJybh().trim());
|
||||||
|
jybhSet.add(item.getJybh());
|
||||||
|
LocalDate d = item.getRq().toLocalDate();
|
||||||
|
if (minDay == null || d.isBefore(minDay)) {
|
||||||
|
minDay = d;
|
||||||
|
}
|
||||||
|
if (maxDay == null || d.isAfter(maxDay)) {
|
||||||
|
maxDay = d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Map<String, JYL> existing = new HashMap<>();
|
||||||
|
for (JYL row : jylMapper.selectList(new LambdaQueryWrapper<JYL>()
|
||||||
|
.in(JYL::getJybh, jybhSet)
|
||||||
|
.ge(JYL::getRq, minDay.atStartOfDay())
|
||||||
|
.lt(JYL::getRq, maxDay.plusDays(1).atStartOfDay()))) {
|
||||||
|
if (row == null || row.getRq() == null || row.getJc() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
existing.put(cellKey(row.getJybh(), row.getRq().toLocalDate(), row.getJc()), row);
|
||||||
|
}
|
||||||
|
int affected = 0;
|
||||||
|
for (JYL item : list) {
|
||||||
|
JYL old = existing.get(cellKey(item.getJybh(), item.getRq().toLocalDate(), item.getJc()));
|
||||||
|
if (old == null) {
|
||||||
|
if (item.getKpk() == null) {
|
||||||
|
// 教员历的语义是"标出不可排课的时段",新标记默认不可排课
|
||||||
|
item.setKpk(0);
|
||||||
|
}
|
||||||
|
add(item);
|
||||||
|
} else {
|
||||||
|
if (item.getKpk() != null) {
|
||||||
|
old.setKpk(item.getKpk());
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotEmpty(item.getMc())) {
|
||||||
|
old.setMc(item.getMc());
|
||||||
|
}
|
||||||
|
old.setBz(item.getBz());
|
||||||
|
update(old);
|
||||||
|
}
|
||||||
|
affected++;
|
||||||
|
}
|
||||||
|
return affected;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int batchDelete(List<String> bhs) {
|
||||||
|
if (bhs == null || bhs.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
List<String> ids = bhs.stream().filter(StringUtils::isNotEmpty).collect(java.util.stream.Collectors.toList());
|
||||||
|
if (ids.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return jylMapper.delete(new LambdaQueryWrapper<JYL>().in(JYL::getBh, ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 判重键:教员编号|yyyy-MM-dd|节次 */
|
||||||
|
private static String cellKey(String jybh, LocalDate day, Integer jc) {
|
||||||
|
return (jybh == null ? "" : jybh.trim()) + "|" + day + "|" + jc;
|
||||||
|
}
|
||||||
|
|
||||||
private LambdaQueryWrapper<JYL> buildWrapper(JYL condition) {
|
private LambdaQueryWrapper<JYL> buildWrapper(JYL condition) {
|
||||||
LambdaQueryWrapper<JYL> qw = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<JYL> qw = new LambdaQueryWrapper<>();
|
||||||
if (condition != null) {
|
if (condition != null) {
|
||||||
|
|||||||
+24
-4
@@ -138,7 +138,10 @@ public class RunningCoursePublishServiceImpl implements RunningCoursePublishServ
|
|||||||
for (List<XYDRWB> group : groups.values()) {
|
for (List<XYDRWB> group : groups.values()) {
|
||||||
XYDRWB head = group.get(0);
|
XYDRWB head = group.get(0);
|
||||||
SSKC sskc = new SSKC();
|
SSKC sskc = new SSKC();
|
||||||
sskc.setBh(UuidUtil.getUUID());
|
// 实施_课程.编号 是 VARBINARY(16):MyBatis 走 @TableId 元数据不带 typeHandler,
|
||||||
|
// 必须绑 32 位无横线十六进制,带横线会被达梦当普通字符串写入并报 String truncated。
|
||||||
|
String sskcBh = UuidUtil.getUUID();
|
||||||
|
sskc.setBh(sskcBh);
|
||||||
sskc.setNd(ndCode);
|
sskc.setNd(ndCode);
|
||||||
sskc.setJybh(head.getJybh());
|
sskc.setJybh(head.getJybh());
|
||||||
sskc.setKmbh(head.getKbh());
|
sskc.setKmbh(head.getKbh());
|
||||||
@@ -153,12 +156,27 @@ public class RunningCoursePublishServiceImpl implements RunningCoursePublishServ
|
|||||||
sskc.setBjrxypjf(head.getBjrxypjf());
|
sskc.setBjrxypjf(head.getBjrxypjf());
|
||||||
sskc.setCjsj(now);
|
sskc.setCjsj(now);
|
||||||
sskc.setBdsj(now);
|
sskc.setBdsj(now);
|
||||||
|
// 以下非空列必须显式赋值(实施_课程共 26 个 NOT NULL 列,无库级缺省)
|
||||||
|
sskc.setJykcxh(1);
|
||||||
|
sskc.setKcksxs(1.0f);
|
||||||
|
sskc.setKcxycpzt("未开始");
|
||||||
|
sskc.setKbbdsj(now);
|
||||||
|
sskc.setKhxs(head.getKsks() == null ? 0 : head.getKsks());
|
||||||
|
sskc.setYxxs(0);
|
||||||
|
sskc.setYxllxs(0);
|
||||||
|
sskc.setYxsjxs(0);
|
||||||
|
sskc.setYxkhxs(0);
|
||||||
|
sskc.setPxxhdf(0f);
|
||||||
|
sskc.setQrpxzh(0);
|
||||||
|
sskc.setZrjybh(head.getJybh());
|
||||||
|
sskc.setJypxjszt(0);
|
||||||
sskcMapper.insert(sskc);
|
sskcMapper.insert(sskc);
|
||||||
|
|
||||||
for (XYDRWB task : group) {
|
for (XYDRWB task : group) {
|
||||||
SSKCXYD link = new SSKCXYD();
|
SSKCXYD link = new SSKCXYD();
|
||||||
link.setBh(UuidUtil.getUUID());
|
// 实施_课程学员队.编号/实施_课程编号 是 CHAR(36),约定存带横线 36 位 UUID
|
||||||
link.setSskcbh(sskc.getBh());
|
link.setBh(UuidUtil.getOriginalUUID());
|
||||||
|
link.setSskcbh(UuidUtil.fromBytes(UuidUtil.toBytes(sskcBh)));
|
||||||
link.setXydbh(task.getXydbh());
|
link.setXydbh(task.getXydbh());
|
||||||
link.setJc(team != null ? team.getJc() : null);
|
link.setJc(team != null ? team.getJc() : null);
|
||||||
link.setRs(team != null ? team.getXydrs() : null);
|
link.setRs(team != null ? team.getXydrs() : null);
|
||||||
@@ -213,7 +231,9 @@ public class RunningCoursePublishServiceImpl implements RunningCoursePublishServ
|
|||||||
Long remain = sskcxydMapper.selectCount(new LambdaQueryWrapper<SSKCXYD>()
|
Long remain = sskcxydMapper.selectCount(new LambdaQueryWrapper<SSKCXYD>()
|
||||||
.eq(SSKCXYD::getSskcbh, sskcbh));
|
.eq(SSKCXYD::getSskcbh, sskcbh));
|
||||||
if (remain == null || remain == 0) {
|
if (remain == null || remain == 0) {
|
||||||
sskcMapper.deleteById(sskcbh);
|
// 注意:实施_课程.编号为 VARBINARY GUID,deleteById 直绑字符串会报
|
||||||
|
// dm.jdbc.driver.DMException: Invalid hexadecimal digits,必须走 RAWTOHEX
|
||||||
|
sskcMapper.deleteByBhs(java.util.List.of(sskcbh));
|
||||||
}
|
}
|
||||||
removed++;
|
removed++;
|
||||||
}
|
}
|
||||||
|
|||||||
+458
-35
@@ -5,10 +5,16 @@ import com.roomroot.common.exception.ServiceException;
|
|||||||
import com.roomroot.common.utils.SecurityUtils;
|
import com.roomroot.common.utils.SecurityUtils;
|
||||||
import com.roomroot.jwgl.dto.scheduling.SchedulingArrangeRequest;
|
import com.roomroot.jwgl.dto.scheduling.SchedulingArrangeRequest;
|
||||||
import com.roomroot.jwgl.dto.scheduling.SchedulingCellOpRequest;
|
import com.roomroot.jwgl.dto.scheduling.SchedulingCellOpRequest;
|
||||||
|
import com.roomroot.jwgl.dto.scheduling.SchedulingCourseEditRequest;
|
||||||
|
import com.roomroot.jwgl.entity.BZLB;
|
||||||
|
import com.roomroot.jwgl.entity.BZTGMX;
|
||||||
|
import com.roomroot.jwgl.entity.JCSJB;
|
||||||
import com.roomroot.jwgl.entity.JQB;
|
import com.roomroot.jwgl.entity.JQB;
|
||||||
|
import com.roomroot.jwgl.entity.JSB;
|
||||||
import com.roomroot.jwgl.entity.JYB;
|
import com.roomroot.jwgl.entity.JYB;
|
||||||
import com.roomroot.jwgl.entity.JXCDL;
|
import com.roomroot.jwgl.entity.JXCDL;
|
||||||
import com.roomroot.jwgl.entity.JYL;
|
import com.roomroot.jwgl.entity.JYL;
|
||||||
|
import com.roomroot.jwgl.entity.KCBBZMX;
|
||||||
import com.roomroot.jwgl.entity.KCJXYX_CZRZ;
|
import com.roomroot.jwgl.entity.KCJXYX_CZRZ;
|
||||||
import com.roomroot.jwgl.entity.SSKC;
|
import com.roomroot.jwgl.entity.SSKC;
|
||||||
import com.roomroot.jwgl.entity.SSKCB;
|
import com.roomroot.jwgl.entity.SSKCB;
|
||||||
@@ -18,7 +24,13 @@ import com.roomroot.jwgl.entity.SSKCBXYD;
|
|||||||
import com.roomroot.jwgl.entity.SSKCXYD;
|
import com.roomroot.jwgl.entity.SSKCXYD;
|
||||||
import com.roomroot.jwgl.entity.XYDB;
|
import com.roomroot.jwgl.entity.XYDB;
|
||||||
import com.roomroot.jwgl.entity.XYDNDXQJBXXB;
|
import com.roomroot.jwgl.entity.XYDNDXQJBXXB;
|
||||||
|
import com.roomroot.jwgl.entity.XYDRWB;
|
||||||
import com.roomroot.jwgl.entity.XQXLB;
|
import com.roomroot.jwgl.entity.XQXLB;
|
||||||
|
import com.roomroot.jwgl.mapper.BZLBMapper;
|
||||||
|
import com.roomroot.jwgl.mapper.BZTGMXMapper;
|
||||||
|
import com.roomroot.jwgl.mapper.ClassRoomMapper;
|
||||||
|
import com.roomroot.jwgl.mapper.JCSJBMapper;
|
||||||
|
import com.roomroot.jwgl.mapper.KCBBZMXMapper;
|
||||||
import com.roomroot.jwgl.mapper.XYDBMapper;
|
import com.roomroot.jwgl.mapper.XYDBMapper;
|
||||||
import com.roomroot.jwgl.mapper.ClassRoomCalendarMapper;
|
import com.roomroot.jwgl.mapper.ClassRoomCalendarMapper;
|
||||||
import com.roomroot.jwgl.mapper.ClassSemesterMapper;
|
import com.roomroot.jwgl.mapper.ClassSemesterMapper;
|
||||||
@@ -40,6 +52,7 @@ import com.roomroot.jwgl.utils.SemesterCodeUtil;
|
|||||||
import com.roomroot.jwgl.utils.PeriodUtil;
|
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 com.roomroot.common.core.domain.entity.SysUser;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -52,6 +65,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -129,6 +143,24 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
|||||||
@Resource
|
@Resource
|
||||||
private com.roomroot.jwgl.mapper.StudentTeamTaskMapper studentTeamTaskMapper;
|
private com.roomroot.jwgl.mapper.StudentTeamTaskMapper studentTeamTaskMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private JCSJBMapper jcsjbMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ClassRoomMapper classRoomMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private BZLBMapper bzlbMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private BZTGMXMapper bztgmxMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private KCBBZMXMapper kcbbzMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private com.roomroot.system.service.ISysUserService userService;
|
||||||
|
|
||||||
// ==================== 视图 ====================
|
// ==================== 视图 ====================
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -147,6 +179,13 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
|||||||
List<String> sskcIds = links.stream().map(SSKCXYD::getSskcbh).distinct().toList();
|
List<String> sskcIds = links.stream().map(SSKCXYD::getSskcbh).distinct().toList();
|
||||||
Map<String, SSKC> courses = sskcIds.isEmpty() ? Map.of()
|
Map<String, SSKC> courses = sskcIds.isEmpty() ? Map.of()
|
||||||
: sskcMapper.selectByBhs(sskcIds).stream().collect(Collectors.toMap(SSKC::getBh, c -> c, (a, b) -> a));
|
: sskcMapper.selectByBhs(sskcIds).stream().collect(Collectors.toMap(SSKC::getBh, c -> c, (a, b) -> a));
|
||||||
|
// 编制侧任务(排课建议 / 简称 / 计划教员)
|
||||||
|
Map<String, XYDRWB> taskByKbh = new LinkedHashMap<>();
|
||||||
|
for (XYDRWB task : studentTeamTaskMapper.selectList(new LambdaQueryWrapper<XYDRWB>()
|
||||||
|
.eq(XYDRWB::getXydxqbh, ctx.semester.getBh())
|
||||||
|
.eq(XYDRWB::getDelFlag, 0))) {
|
||||||
|
taskByKbh.putIfAbsent(task.getKbh(), task);
|
||||||
|
}
|
||||||
List<SSKCB> lessons = ctx.lessons;
|
List<SSKCB> lessons = ctx.lessons;
|
||||||
Map<String, List<SSKCB>> lessonsByCourse = lessons.stream()
|
Map<String, List<SSKCB>> lessonsByCourse = lessons.stream()
|
||||||
.collect(Collectors.groupingBy(SSKCB::getSskcbh));
|
.collect(Collectors.groupingBy(SSKCB::getSskcbh));
|
||||||
@@ -162,6 +201,25 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
|||||||
c.setZks(course.getZks());
|
c.setZks(course.getZks());
|
||||||
c.setJybh(course.getJybh());
|
c.setJybh(course.getJybh());
|
||||||
c.setJsbh(course.getJsbh());
|
c.setJsbh(course.getJsbh());
|
||||||
|
XYDRWB task = taskByKbh.get(course.getKmbh());
|
||||||
|
if (task != null) {
|
||||||
|
c.setJc(task.getJc());
|
||||||
|
c.setJysjhbz(task.getJysjhbz());
|
||||||
|
c.setJysjhjybh(task.getJysjhjybh());
|
||||||
|
c.setJysjhjyxm(resolveTeacherName(task.getJysjhjybh()));
|
||||||
|
}
|
||||||
|
if (c.getJybh() != null) c.setJyxm(resolveTeacherName(c.getJybh()));
|
||||||
|
if (c.getJsbh() != null) c.setJsmc(resolveRoomName(c.getJsbh()));
|
||||||
|
// ⑤ 合班信息:该运行课程关联的全部班次
|
||||||
|
for (SSKCXYD link : links) {
|
||||||
|
if (!course.getBh().equals(link.getSskcbh())) continue;
|
||||||
|
SchedulingViewVO.Team t = new SchedulingViewVO.Team();
|
||||||
|
t.setXydbh(link.getXydbh());
|
||||||
|
t.setXydxqbh(ctx.semester.getBh());
|
||||||
|
t.setXydmc(link.getXydbh() != null && ctx.team != null && ctx.team.getXydbh().equals(link.getXydbh())
|
||||||
|
? ctx.team.getXydmc() : resolveTeamName(link.getXydbh()));
|
||||||
|
c.getTeams().add(t);
|
||||||
|
}
|
||||||
List<SSKCB> cl = lessonsByCourse.getOrDefault(sskcbh, List.of());
|
List<SSKCB> cl = lessonsByCourse.getOrDefault(sskcbh, List.of());
|
||||||
c.setScheduledHours(cl.stream().mapToInt(l -> HOURS_PER_LESSON + (l.getJcdj() == null ? 0 : l.getJcdj())).sum());
|
c.setScheduledHours(cl.stream().mapToInt(l -> HOURS_PER_LESSON + (l.getJcdj() == null ? 0 : l.getJcdj())).sum());
|
||||||
c.setFull(course.getXs() != null && c.getScheduledHours() >= course.getXs());
|
c.setFull(course.getXs() != null && c.getScheduledHours() >= course.getXs());
|
||||||
@@ -213,14 +271,55 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
|||||||
.collect(Collectors.groupingBy(l -> l.getRq().toLocalDate().toString() + "#" + l.getJc()));
|
.collect(Collectors.groupingBy(l -> l.getRq().toLocalDate().toString() + "#" + l.getJc()));
|
||||||
Map<String, String> courseNames = new HashMap<>();
|
Map<String, String> courseNames = new HashMap<>();
|
||||||
Map<String, String> courseTeachers = new HashMap<>();
|
Map<String, String> courseTeachers = new HashMap<>();
|
||||||
for (SSKC course : sskcMapper.selectByBhs(ctx.links.stream().map(SSKCXYD::getSskcbh).distinct().toList())) {
|
List<String> courseBhList = ctx.links.stream().map(SSKCXYD::getSskcbh).distinct().toList();
|
||||||
|
for (SSKC course : courseBhList.isEmpty() ? List.<SSKC>of() : sskcMapper.selectByBhs(courseBhList)) {
|
||||||
courseNames.put(course.getBh(), resolveCourseName(course.getKmbh()));
|
courseNames.put(course.getBh(), resolveCourseName(course.getKmbh()));
|
||||||
courseTeachers.put(course.getBh(), safe(course.getJybh()));
|
courseTeachers.put(course.getBh(), resolveTeacherName(course.getJybh()));
|
||||||
|
}
|
||||||
|
// 课次明细:学员队 / 教室 / 教员(供 ② 冲突显示选项与双击回填使用)
|
||||||
|
Map<String, List<String>> teamsOfLesson = new HashMap<>();
|
||||||
|
Map<String, List<String>> roomsOfLesson = new HashMap<>();
|
||||||
|
Map<String, List<String>> teachersOfLesson = new HashMap<>();
|
||||||
|
Map<String, String> mainTeacherOfLesson = new HashMap<>();
|
||||||
|
List<String> allLessonIds = ctx.lessons.stream().map(SSKCB::getBh).distinct().toList();
|
||||||
|
if (!allLessonIds.isEmpty()) {
|
||||||
|
for (SSKCBXYD l : sskcbxydMapper.selectList(new LambdaQueryWrapper<SSKCBXYD>()
|
||||||
|
.in(SSKCBXYD::getSskcbbh, allLessonIds))) {
|
||||||
|
teamsOfLesson.computeIfAbsent(l.getSskcbbh(), k -> new ArrayList<>()).add(l.getXydbh());
|
||||||
|
}
|
||||||
|
for (SSKCBJS l : sskcbjsMapper.selectList(new LambdaQueryWrapper<SSKCBJS>()
|
||||||
|
.in(SSKCBJS::getSskcbbh, allLessonIds))) {
|
||||||
|
roomsOfLesson.computeIfAbsent(l.getSskcbbh(), k -> new ArrayList<>()).add(l.getJsbh());
|
||||||
|
}
|
||||||
|
for (SSKCBFZJY l : sskcbfzjyMapper.selectList(new LambdaQueryWrapper<SSKCBFZJY>()
|
||||||
|
.in(SSKCBFZJY::getSskcbbh, allLessonIds))) {
|
||||||
|
teachersOfLesson.computeIfAbsent(l.getSskcbbh(), k -> new ArrayList<>()).add(l.getFzjybh());
|
||||||
|
if (l.getZjy() != null && l.getZjy() == 1) {
|
||||||
|
mainTeacherOfLesson.put(l.getSskcbbh(), l.getFzjybh());
|
||||||
|
} else {
|
||||||
|
mainTeacherOfLesson.putIfAbsent(l.getSskcbbh(), l.getFzjybh());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 场地历 / 教员历 不可用(按 date#jc 聚合,供冲突显示选项分类着色)
|
||||||
|
Map<String, List<String>> blockedRoomsByCell = new HashMap<>();
|
||||||
|
for (JXCDL row : classRoomCalendarMapper.selectList(new LambdaQueryWrapper<JXCDL>()
|
||||||
|
.eq(JXCDL::getKpk, false).eq(JXCDL::getDelFlag, 0))) {
|
||||||
|
if (row.getRq() == null || row.getJsbh() == null) continue;
|
||||||
|
blockedRoomsByCell.computeIfAbsent(row.getRq().toLocalDate() + "#" + row.getJc(), k -> new ArrayList<>())
|
||||||
|
.add(row.getJsbh());
|
||||||
|
}
|
||||||
|
Map<String, List<String>> blockedTeachersByCell = new HashMap<>();
|
||||||
|
for (JYL row : jylMapper.selectList(new LambdaQueryWrapper<JYL>().eq(JYL::getKpk, 0))) {
|
||||||
|
if (row.getRq() == null || row.getJybh() == null) continue;
|
||||||
|
blockedTeachersByCell.computeIfAbsent(row.getRq().toLocalDate() + "#" + row.getJc(), k -> new ArrayList<>())
|
||||||
|
.add(row.getJybh());
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalDate weekStart = kxrq.with(DayOfWeek.MONDAY);
|
LocalDate weekStart = kxrq.with(DayOfWeek.MONDAY);
|
||||||
int weekNo = 1;
|
int weekNo = 1;
|
||||||
int[] periodCount = {8};
|
int[] periodCount = {resolvePeriodCount()};
|
||||||
|
vo.setPeriods(resolvePeriods());
|
||||||
while (!weekStart.isAfter(jsrq)) {
|
while (!weekStart.isAfter(jsrq)) {
|
||||||
SchedulingViewVO.Week week = new SchedulingViewVO.Week();
|
SchedulingViewVO.Week week = new SchedulingViewVO.Week();
|
||||||
week.setWeekNo(weekNo);
|
week.setWeekNo(weekNo);
|
||||||
@@ -238,23 +337,41 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
|||||||
String key = date.toString() + "#" + jc;
|
String key = date.toString() + "#" + jc;
|
||||||
if (schoolHard.contains(key) || classHard.contains(key)) {
|
if (schoolHard.contains(key) || classHard.contains(key)) {
|
||||||
cell.setUnavailable(true);
|
cell.setUnavailable(true);
|
||||||
cell.setReason("不可排课(" + (schoolHard.contains(key) ? "校历" : "班历") + ")");
|
cell.setCalendarKind(schoolHard.contains(key) ? "SCHOOL" : "CLASS");
|
||||||
|
cell.setReason("不可排课(" + ("SCHOOL".equals(cell.getCalendarKind()) ? "校历" : "班历") + ")");
|
||||||
} else {
|
} else {
|
||||||
if (schoolSoft.contains(key) || classSoft.contains(key)) {
|
if (schoolSoft.contains(key) || classSoft.contains(key)) {
|
||||||
cell.setWarning("非正课时段");
|
cell.setWarning("非正课时段");
|
||||||
|
cell.setSoft(true);
|
||||||
}
|
}
|
||||||
cell.setUnavailable(false);
|
cell.setUnavailable(false);
|
||||||
}
|
}
|
||||||
|
cell.setBlockedRooms(blockedRoomsByCell.getOrDefault(key, new ArrayList<>()));
|
||||||
|
cell.setBlockedTeachers(blockedTeachersByCell.getOrDefault(key, new ArrayList<>()));
|
||||||
for (SSKCB lesson : lessonsByGrid.getOrDefault(key, List.of())) {
|
for (SSKCB lesson : lessonsByGrid.getOrDefault(key, List.of())) {
|
||||||
SchedulingViewVO.Lesson l = new SchedulingViewVO.Lesson();
|
SchedulingViewVO.Lesson l = new SchedulingViewVO.Lesson();
|
||||||
l.setBh(lesson.getBh());
|
l.setBh(lesson.getBh());
|
||||||
l.setSskcbh(lesson.getSskcbh());
|
l.setSskcbh(lesson.getSskcbh());
|
||||||
l.setKcmc(courseNames.getOrDefault(lesson.getSskcbh(), "-"));
|
l.setKcmc(courseNames.getOrDefault(lesson.getSskcbh(), "-"));
|
||||||
l.setJxnr(lesson.getJxnr());
|
l.setJxnr(lesson.getJxnr());
|
||||||
|
l.setJxyd(lesson.getJxyd());
|
||||||
l.setJxff(lesson.getJxff());
|
l.setJxff(lesson.getJxff());
|
||||||
l.setJcdj(lesson.getJcdj());
|
l.setJcdj(lesson.getJcdj());
|
||||||
l.setYcxx(lesson.getYcxx());
|
l.setYcxx(lesson.getYcxx());
|
||||||
|
l.setJxbzbz(lesson.getJxbzbz());
|
||||||
|
l.setBzms(lesson.getBzms());
|
||||||
l.setJyxm(courseTeachers.getOrDefault(lesson.getSskcbh(), "-"));
|
l.setJyxm(courseTeachers.getOrDefault(lesson.getSskcbh(), "-"));
|
||||||
|
l.setJybh(mainTeacherOfLesson.get(lesson.getBh()));
|
||||||
|
if (l.getJybh() != null) {
|
||||||
|
l.setJyxm(resolveTeacherName(l.getJybh()));
|
||||||
|
}
|
||||||
|
l.setXydbhs(teamsOfLesson.getOrDefault(lesson.getBh(), new ArrayList<>()));
|
||||||
|
l.setJsbhs(roomsOfLesson.getOrDefault(lesson.getBh(), new ArrayList<>()));
|
||||||
|
l.setJybhs(teachersOfLesson.getOrDefault(lesson.getBh(), new ArrayList<>()));
|
||||||
|
if (!l.getJsbhs().isEmpty()) {
|
||||||
|
l.setJsbh(l.getJsbhs().get(0));
|
||||||
|
l.setJsmc(resolveRoomName(l.getJsbhs().get(0)));
|
||||||
|
}
|
||||||
cell.getLessons().add(l);
|
cell.getLessons().add(l);
|
||||||
}
|
}
|
||||||
day.getCells().add(cell);
|
day.getCells().add(cell);
|
||||||
@@ -268,6 +385,34 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
|||||||
vo.setTotalWeeks(weekNo - 1);
|
vo.setTotalWeeks(weekNo - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 节次字典:节次时间表展开为 1..N,供前端显示控制 */
|
||||||
|
private List<SchedulingViewVO.Period> resolvePeriods() {
|
||||||
|
List<SchedulingViewVO.Period> periods = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
for (JCSJB row : jcsjbMapper.selectList(new LambdaQueryWrapper<JCSJB>())) {
|
||||||
|
String label = row.getJc() != null && !row.getJc().isEmpty() ? row.getJc() : row.getJcsy();
|
||||||
|
for (Integer jc : PeriodUtil.parsePeriods(row.getJcsy())) {
|
||||||
|
if (periods.stream().anyMatch(p -> p.getJc().equals(jc))) continue;
|
||||||
|
SchedulingViewVO.Period p = new SchedulingViewVO.Period();
|
||||||
|
p.setJc(jc);
|
||||||
|
p.setLabel(label);
|
||||||
|
p.setSd(row.getSd());
|
||||||
|
periods.add(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("[排课窗] 读取节次字典失败,退化为默认 1-8: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
periods.sort(Comparator.comparing(SchedulingViewVO.Period::getJc));
|
||||||
|
return periods;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 最大节次数:优先取节次时间表,缺省 8 */
|
||||||
|
private int resolvePeriodCount() {
|
||||||
|
List<SchedulingViewVO.Period> periods = resolvePeriods();
|
||||||
|
return periods.isEmpty() ? 8 : periods.get(periods.size() - 1).getJc();
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 安排所选节次 ====================
|
// ==================== 安排所选节次 ====================
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -281,11 +426,51 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
|||||||
if (!canEdit(course, ctx)) {
|
if (!canEdit(course, ctx)) {
|
||||||
throw new ServiceException("没有该课程的排课权限", FORBIDDEN);
|
throw new ServiceException("没有该课程的排课权限", FORBIDDEN);
|
||||||
}
|
}
|
||||||
String jsbh = request.getJsbh() != null && !request.getJsbh().isEmpty() ? request.getJsbh() : course.getJsbh();
|
// 教学场地(支持多场地):jsbhs > jsbh > 课程默认场地
|
||||||
if (jsbh == null || jsbh.isEmpty()) {
|
List<String> rooms = new ArrayList<>();
|
||||||
|
if (request.getJsbhs() != null) {
|
||||||
|
request.getJsbhs().stream().filter(s -> s != null && !s.isEmpty()).distinct().forEach(rooms::add);
|
||||||
|
}
|
||||||
|
if (rooms.isEmpty() && request.getJsbh() != null && !request.getJsbh().isEmpty()) {
|
||||||
|
rooms.add(request.getJsbh());
|
||||||
|
}
|
||||||
|
if (rooms.isEmpty() && course.getJsbh() != null && !course.getJsbh().isEmpty()) {
|
||||||
|
rooms.add(course.getJsbh());
|
||||||
|
}
|
||||||
|
if (rooms.isEmpty()) {
|
||||||
throw new ServiceException("请指定场地(课程未设置默认场地)", BAD_REQUEST);
|
throw new ServiceException("请指定场地(课程未设置默认场地)", BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
String jsbh = rooms.get(0);
|
||||||
String jybh = request.getJybh() != null && !request.getJybh().isEmpty() ? request.getJybh() : course.getJybh();
|
String jybh = request.getJybh() != null && !request.getJybh().isEmpty() ? request.getJybh() : course.getJybh();
|
||||||
|
List<String> fzjybhs = request.getFzjybhs() == null ? List.of()
|
||||||
|
: request.getFzjybhs().stream().filter(s -> s != null && !s.isEmpty()).distinct().toList();
|
||||||
|
// 教学保障:数量不得超过资源库总量
|
||||||
|
List<KCBBZMX> supportRows = new ArrayList<>();
|
||||||
|
if (request.getSupports() != null) {
|
||||||
|
for (SchedulingArrangeRequest.Support s : request.getSupports()) {
|
||||||
|
if (s == null || s.getBztgmxbh() == null || s.getBztgmxbh().isEmpty()) continue;
|
||||||
|
if (s.getSl() == null || s.getSl() <= 0) {
|
||||||
|
throw new ServiceException("教学保障需求数量必须大于 0", BAD_REQUEST);
|
||||||
|
}
|
||||||
|
BZTGMX res = bztgmxMapper.selectById(s.getBztgmxbh());
|
||||||
|
if (res == null) {
|
||||||
|
throw new ServiceException("教学保障资源不存在:" + s.getBztgmxbh(), BAD_REQUEST);
|
||||||
|
}
|
||||||
|
if (res.getSl() != null && s.getSl() > res.getSl()) {
|
||||||
|
throw new ServiceException("教学保障「" + res.getMc() + "」需求数量 " + s.getSl()
|
||||||
|
+ " 超过总量 " + res.getSl() + (res.getDw() == null ? "" : res.getDw()), BAD_REQUEST);
|
||||||
|
}
|
||||||
|
KCBBZMX row = new KCBBZMX();
|
||||||
|
row.setBztgmxbh(s.getBztgmxbh());
|
||||||
|
row.setSl(s.getSl());
|
||||||
|
row.setBz(s.getBz());
|
||||||
|
row.setNd(ctx.ndCode);
|
||||||
|
supportRows.add(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 教学班次(可临时增删):为空用课程的全部合班班次
|
||||||
|
List<String> xydbhs = request.getXydbhs() == null ? List.of()
|
||||||
|
: request.getXydbhs().stream().filter(s -> s != null && !s.isEmpty()).distinct().toList();
|
||||||
|
|
||||||
// 硬冲突判定所需数据
|
// 硬冲突判定所需数据
|
||||||
List<SSKCB> semesterLessons = ctx.lessons;
|
List<SSKCB> semesterLessons = ctx.lessons;
|
||||||
@@ -351,9 +536,9 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
|||||||
ignored.add(ignoredCell(cell, "班历不可排课"));
|
ignored.add(ignoredCell(cell, "班历不可排课"));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String roomKey = key + "#" + jsbh;
|
String blockedRoom = rooms.stream().filter(r -> roomBlocked.containsKey(key + "#" + r)).findFirst().orElse(null);
|
||||||
if (roomBlocked.containsKey(roomKey)) {
|
if (blockedRoom != null) {
|
||||||
ignored.add(ignoredCell(cell, "场地历不可用"));
|
ignored.add(ignoredCell(cell, "场地历不可用(教室 " + blockedRoom + ")"));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (jybh != null && teacherBlocked.containsKey(key + "#" + jybh)) {
|
if (jybh != null && teacherBlocked.containsKey(key + "#" + jybh)) {
|
||||||
@@ -375,7 +560,7 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
|||||||
busy = true; busyReason = "班次该节已有其它课程";
|
busy = true; busyReason = "班次该节已有其它课程";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (roomsOfLesson.getOrDefault(occupant.getBh(), List.of()).contains(jsbh)) {
|
if (roomsOfLesson.getOrDefault(occupant.getBh(), List.of()).stream().anyMatch(rooms::contains)) {
|
||||||
busy = true; busyReason = "教室该节已被占用";
|
busy = true; busyReason = "教室该节已被占用";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -396,7 +581,7 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
|||||||
}
|
}
|
||||||
if (!ownOccupied) {
|
if (!ownOccupied) {
|
||||||
SSKCB lesson = new SSKCB();
|
SSKCB lesson = new SSKCB();
|
||||||
lesson.setBh(UuidUtil.getUUID());
|
lesson.setBh(UuidUtil.getOriginalUUID());
|
||||||
lesson.setSskcbh(course.getBh());
|
lesson.setSskcbh(course.getBh());
|
||||||
lesson.setJhsjap(0);
|
lesson.setJhsjap(0);
|
||||||
lesson.setSjsjap(0);
|
lesson.setSjsjap(0);
|
||||||
@@ -415,39 +600,92 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
|||||||
lesson.setCjsj(now);
|
lesson.setCjsj(now);
|
||||||
lesson.setBdsj(now);
|
lesson.setBdsj(now);
|
||||||
sskcbMapper.insert(lesson);
|
sskcbMapper.insert(lesson);
|
||||||
// 学员队 / 教室 / 主讲教员
|
// 学员队(教学班次,可临时增删)
|
||||||
for (SSKCXYD link : ctx.links) {
|
if (xydbhs.isEmpty()) {
|
||||||
if (!course.getBh().equals(link.getSskcbh())) continue;
|
for (SSKCXYD link : ctx.links) {
|
||||||
SSKCBXYD lxyd = new SSKCBXYD();
|
if (!course.getBh().equals(link.getSskcbh())) continue;
|
||||||
lxyd.setBh(UuidUtil.getUUID());
|
SSKCBXYD lxyd = new SSKCBXYD();
|
||||||
lxyd.setSskcbbh(lesson.getBh());
|
lxyd.setBh(UuidUtil.getOriginalUUID());
|
||||||
lxyd.setXydbh(link.getXydbh());
|
lxyd.setSskcbbh(lesson.getBh());
|
||||||
lxyd.setRs(link.getRs());
|
lxyd.setXydbh(link.getXydbh());
|
||||||
lxyd.setJc(link.getJc());
|
lxyd.setRs(link.getRs());
|
||||||
lxyd.setNd(ctx.ndCode);
|
lxyd.setJc(link.getJc());
|
||||||
lxyd.setRq(lesson.getRq());
|
lxyd.setNd(ctx.ndCode);
|
||||||
lxyd.setJc2(cell.getJc());
|
lxyd.setRq(lesson.getRq());
|
||||||
sskcbxydMapper.insert(lxyd);
|
lxyd.setJc2(cell.getJc());
|
||||||
|
sskcbxydMapper.insert(lxyd);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (String xydbh : xydbhs) {
|
||||||
|
SSKCXYD link = ctx.links.stream()
|
||||||
|
.filter(k -> course.getBh().equals(k.getSskcbh()) && xydbh.equals(k.getXydbh()))
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
XYDB team = xydbMapper.selectById(xydbh);
|
||||||
|
SSKCBXYD lxyd = new SSKCBXYD();
|
||||||
|
lxyd.setBh(UuidUtil.getOriginalUUID());
|
||||||
|
lxyd.setSskcbbh(lesson.getBh());
|
||||||
|
lxyd.setXydbh(xydbh);
|
||||||
|
lxyd.setRs(link != null ? link.getRs() : (team != null ? team.getXydrs() : null));
|
||||||
|
lxyd.setJc(link != null ? link.getJc() : (team != null ? team.getJc() : null));
|
||||||
|
lxyd.setNd(ctx.ndCode);
|
||||||
|
lxyd.setRq(lesson.getRq());
|
||||||
|
lxyd.setJc2(cell.getJc());
|
||||||
|
sskcbxydMapper.insert(lxyd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SSKCBJS ljs = new SSKCBJS();
|
// 教学场地(可多场地)
|
||||||
ljs.setBh(UuidUtil.getUUID());
|
for (String room : rooms) {
|
||||||
ljs.setSskcbbh(lesson.getBh());
|
SSKCBJS ljs = new SSKCBJS();
|
||||||
ljs.setJsbh(jsbh);
|
ljs.setBh(UuidUtil.getOriginalUUID());
|
||||||
ljs.setNd(ctx.ndCode);
|
ljs.setSskcbbh(lesson.getBh());
|
||||||
ljs.setRq(lesson.getRq());
|
ljs.setJsbh(room);
|
||||||
ljs.setJc(cell.getJc());
|
ljs.setNd(ctx.ndCode);
|
||||||
sskcbjsMapper.insert(ljs);
|
ljs.setRq(lesson.getRq());
|
||||||
|
ljs.setJc(cell.getJc());
|
||||||
|
sskcbjsMapper.insert(ljs);
|
||||||
|
}
|
||||||
|
// 授课教员:主讲 zjy=1 + 辅讲 zjy=0
|
||||||
if (jybh != null) {
|
if (jybh != null) {
|
||||||
SSKCBFZJY lfz = new SSKCBFZJY();
|
SSKCBFZJY lfz = new SSKCBFZJY();
|
||||||
lfz.setBh(UuidUtil.getUUID());
|
lfz.setBh(UuidUtil.getOriginalUUID());
|
||||||
lfz.setSskcbbh(lesson.getBh());
|
lfz.setSskcbbh(lesson.getBh());
|
||||||
lfz.setFzjybh(jybh);
|
lfz.setFzjybh(jybh);
|
||||||
lfz.setZjy(1);
|
lfz.setZjy(1);
|
||||||
lfz.setNd(ctx.ndCode);
|
lfz.setNd(ctx.ndCode);
|
||||||
|
// 参与学员测评 / 学员测评平均分 为非空列:新排课次默认参与、分数待评
|
||||||
|
lfz.setCyxycp(1);
|
||||||
|
lfz.setXycppjf(0f);
|
||||||
lfz.setRq(lesson.getRq());
|
lfz.setRq(lesson.getRq());
|
||||||
lfz.setJc(cell.getJc());
|
lfz.setJc(cell.getJc());
|
||||||
sskcbfzjyMapper.insert(lfz);
|
sskcbfzjyMapper.insert(lfz);
|
||||||
}
|
}
|
||||||
|
for (String fzjybh : fzjybhs) {
|
||||||
|
if (fzjybh.equals(jybh)) continue;
|
||||||
|
SSKCBFZJY lfz = new SSKCBFZJY();
|
||||||
|
lfz.setBh(UuidUtil.getOriginalUUID());
|
||||||
|
lfz.setSskcbbh(lesson.getBh());
|
||||||
|
lfz.setFzjybh(fzjybh);
|
||||||
|
lfz.setZjy(0);
|
||||||
|
lfz.setNd(ctx.ndCode);
|
||||||
|
lfz.setCyxycp(1);
|
||||||
|
lfz.setXycppjf(0f);
|
||||||
|
lfz.setRq(lesson.getRq());
|
||||||
|
lfz.setJc(cell.getJc());
|
||||||
|
sskcbfzjyMapper.insert(lfz);
|
||||||
|
}
|
||||||
|
// 教学保障明细
|
||||||
|
for (KCBBZMX template : supportRows) {
|
||||||
|
KCBBZMX row = new KCBBZMX();
|
||||||
|
row.setBh(UuidUtil.getOriginalUUID());
|
||||||
|
row.setKcbbh(lesson.getBh());
|
||||||
|
row.setBztgmxbh(template.getBztgmxbh());
|
||||||
|
row.setSl(template.getSl());
|
||||||
|
row.setBz(template.getBz());
|
||||||
|
row.setNd(template.getNd());
|
||||||
|
row.setRq(lesson.getRq());
|
||||||
|
row.setJc(cell.getJc());
|
||||||
|
kcbbzMapper.insert(row);
|
||||||
|
}
|
||||||
arranged++;
|
arranged++;
|
||||||
logOperation(course, "安排", "安排 " + cell.getRq() + " 第" + cell.getJc() + "节"
|
logOperation(course, "安排", "安排 " + cell.getRq() + " 第" + cell.getJc() + "节"
|
||||||
+ (ownOccupied ? "(覆盖原课次)" : ""),
|
+ (ownOccupied ? "(覆盖原课次)" : ""),
|
||||||
@@ -551,7 +789,8 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
|||||||
}
|
}
|
||||||
sskcxydMapper.delete(new LambdaQueryWrapper<SSKCXYD>()
|
sskcxydMapper.delete(new LambdaQueryWrapper<SSKCXYD>()
|
||||||
.eq(SSKCXYD::getSskcbh, course.getBh()));
|
.eq(SSKCXYD::getSskcbh, course.getBh()));
|
||||||
sskcMapper.deleteById(course.getBh());
|
// 实施_课程.编号为 VARBINARY GUID,deleteById 会报 Invalid hexadecimal digits,须走 RAWTOHEX
|
||||||
|
sskcMapper.deleteByBhs(java.util.List.of(course.getBh()));
|
||||||
logOperation(course, "删除", "彻底删除运行课程", null);
|
logOperation(course, "删除", "彻底删除运行课程", null);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -570,11 +809,160 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
|||||||
m.put("czlx", r.getCzlx());
|
m.put("czlx", r.getCzlx());
|
||||||
m.put("cznr", r.getCznr());
|
m.put("cznr", r.getCznr());
|
||||||
m.put("czrbh", r.getCzrbh());
|
m.put("czrbh", r.getCzrbh());
|
||||||
|
m.put("czrmc", resolveOperatorName(r.getCzrbh()));
|
||||||
m.put("czsj", r.getCzsj());
|
m.put("czsj", r.getCzsj());
|
||||||
return m;
|
return m;
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 操作人显示名:按登录名取用户昵称,取不到则回显登录名 */
|
||||||
|
private String resolveOperatorName(String username) {
|
||||||
|
if (username == null || username.isEmpty()) return "-";
|
||||||
|
try {
|
||||||
|
SysUser user = userService.selectUserByUserName(username);
|
||||||
|
if (user != null && user.getNickName() != null) {
|
||||||
|
return user.getNickName();
|
||||||
|
}
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
// 取不到昵称时回显登录名
|
||||||
|
}
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 编辑课程教学任务信息(手册 12.3.1) ====================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Map<String, Object> updateCourse(SchedulingCourseEditRequest request) {
|
||||||
|
if (request == null || request.getSskcbh() == null || request.getSskcbh().isEmpty()) {
|
||||||
|
throw new ServiceException("请指定运行课程", BAD_REQUEST);
|
||||||
|
}
|
||||||
|
SSKC course = requireCourse(request.getSskcbh());
|
||||||
|
Ctx ctx = loadCtxForCourse(course);
|
||||||
|
if (!canEdit(course, ctx)) {
|
||||||
|
throw new ServiceException("没有该课程的信息修改权限", FORBIDDEN);
|
||||||
|
}
|
||||||
|
boolean changed = false;
|
||||||
|
Integer newXs = null;
|
||||||
|
Integer newZks = null;
|
||||||
|
String newJy = null;
|
||||||
|
String newJs = null;
|
||||||
|
if (request.getXs() != null && !request.getXs().equals(course.getXs())) {
|
||||||
|
newXs = request.getXs(); changed = true;
|
||||||
|
}
|
||||||
|
if (request.getZks() != null && !request.getZks().equals(course.getZks())) {
|
||||||
|
newZks = request.getZks(); changed = true;
|
||||||
|
}
|
||||||
|
if (request.getJybh() != null && !request.getJybh().isEmpty() && !request.getJybh().equals(course.getJybh())) {
|
||||||
|
newJy = request.getJybh(); changed = true;
|
||||||
|
}
|
||||||
|
if (request.getJsbh() != null && !request.getJsbh().isEmpty() && !request.getJsbh().equals(course.getJsbh())) {
|
||||||
|
newJs = request.getJsbh(); changed = true;
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
// 实施_课程.编号为 VARBINARY GUID,updateById 会对主键绑字符串而报 Invalid hexadecimal digits
|
||||||
|
sskcMapper.updateBasicByBh(course.getBh(), newXs, newZks, newJy, newJs);
|
||||||
|
}
|
||||||
|
// 课程简称回写编制侧任务表(同一班次学期 + 同一课程编号)
|
||||||
|
if (request.getJc() != null && ctx.semester != null) {
|
||||||
|
for (XYDRWB task : studentTeamTaskMapper.selectList(new LambdaQueryWrapper<XYDRWB>()
|
||||||
|
.eq(XYDRWB::getXydxqbh, ctx.semester.getBh())
|
||||||
|
.eq(XYDRWB::getKbh, course.getKmbh())
|
||||||
|
.eq(XYDRWB::getDelFlag, 0))) {
|
||||||
|
task.setJc(request.getJc());
|
||||||
|
task.setBdsj(LocalDateTime.now());
|
||||||
|
studentTeamTaskMapper.updateById(task);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
logOperation(course, "修改", "编辑课程教学任务信息(学时=" + (newXs != null ? newXs : course.getXs())
|
||||||
|
+ ",周课时=" + (newZks != null ? newZks : course.getZks())
|
||||||
|
+ ",简称=" + request.getJc() + ")", null);
|
||||||
|
}
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("updated", changed);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 教学班次(合班)与候选班次(手册 12.3.2.2) ====================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> courseTeams(String sskcbh) {
|
||||||
|
SSKC course = requireCourse(sskcbh);
|
||||||
|
Ctx ctx = loadCtxForCourse(course);
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
// 当前教学班次:该运行课程关联的学员队
|
||||||
|
List<Map<String, Object>> current = new ArrayList<>();
|
||||||
|
for (SSKCXYD link : ctx.links) {
|
||||||
|
if (!course.getBh().equals(link.getSskcbh())) continue;
|
||||||
|
Map<String, Object> m = new HashMap<>();
|
||||||
|
m.put("xydbh", link.getXydbh());
|
||||||
|
m.put("xydmc", resolveTeamName(link.getXydbh()));
|
||||||
|
m.put("rs", link.getRs());
|
||||||
|
current.add(m);
|
||||||
|
}
|
||||||
|
// 候选班次:同学期开设同一课程编号的其它班次学期
|
||||||
|
List<Map<String, Object>> candidates = new ArrayList<>();
|
||||||
|
java.util.LinkedHashSet<String> seen = new java.util.LinkedHashSet<>();
|
||||||
|
for (XYDRWB task : studentTeamTaskMapper.selectList(new LambdaQueryWrapper<XYDRWB>()
|
||||||
|
.eq(XYDRWB::getKbh, course.getKmbh())
|
||||||
|
.eq(XYDRWB::getDelFlag, 0))) {
|
||||||
|
if (Objects.equals(task.getXydxqbh(), ctx.semester == null ? null : ctx.semester.getBh())) continue;
|
||||||
|
if (task.getXydxqbh() == null || !seen.add(task.getXydxqbh())) continue;
|
||||||
|
XYDNDXQJBXXB semester = classSemesterMapper.selectById(task.getXydxqbh());
|
||||||
|
if (semester == null) continue;
|
||||||
|
Map<String, Object> m = new HashMap<>();
|
||||||
|
m.put("xydbh", task.getXydbh());
|
||||||
|
m.put("xydxqbh", task.getXydxqbh());
|
||||||
|
m.put("xydmc", resolveTeamName(task.getXydbh()));
|
||||||
|
m.put("nd", semester.getNd());
|
||||||
|
m.put("xqdc", semester.getXqdc());
|
||||||
|
candidates.add(m);
|
||||||
|
}
|
||||||
|
result.put("current", current);
|
||||||
|
result.put("candidates", candidates);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 教学保障选项(手册 12.3.2.5) ====================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> supportOptions() {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
List<Map<String, Object>> categories = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
for (BZLB lb : bzlbMapper.selectList(new LambdaQueryWrapper<BZLB>())) {
|
||||||
|
if (lb.getTy() != null && lb.getTy() != 0) continue;
|
||||||
|
Map<String, Object> m = new HashMap<>();
|
||||||
|
m.put("bh", lb.getBh());
|
||||||
|
m.put("mc", lb.getMc());
|
||||||
|
categories.add(m);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("[排课窗] 读取保障类别失败: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
List<Map<String, Object>> resources = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
for (BZTGMX mx : bztgmxMapper.selectList(new LambdaQueryWrapper<BZTGMX>())) {
|
||||||
|
if (mx.getTy() != null && mx.getTy() != 0) continue;
|
||||||
|
Map<String, Object> m = new HashMap<>();
|
||||||
|
m.put("bh", mx.getBh());
|
||||||
|
m.put("mc", mx.getMc());
|
||||||
|
m.put("dw", mx.getDw());
|
||||||
|
m.put("total", mx.getSl());
|
||||||
|
m.put("bzlbbh", mx.getBzlbbh());
|
||||||
|
m.put("zylb", mx.getZylb());
|
||||||
|
resources.add(m);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("[排课窗] 读取保障资源失败: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
result.put("categories", categories);
|
||||||
|
result.put("resources", resources);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 公共 ====================
|
// ==================== 公共 ====================
|
||||||
|
|
||||||
/** 上下文:班次学期 + 学员队 + 6 位代号 + 本班次已发布课程与课次 */
|
/** 上下文:班次学期 + 学员队 + 6 位代号 + 本班次已发布课程与课次 */
|
||||||
@@ -688,6 +1076,7 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
|||||||
sskcbxydMapper.delete(new LambdaQueryWrapper<SSKCBXYD>().eq(SSKCBXYD::getSskcbbh, sskcbbh));
|
sskcbxydMapper.delete(new LambdaQueryWrapper<SSKCBXYD>().eq(SSKCBXYD::getSskcbbh, sskcbbh));
|
||||||
sskcbjsMapper.delete(new LambdaQueryWrapper<SSKCBJS>().eq(SSKCBJS::getSskcbbh, sskcbbh));
|
sskcbjsMapper.delete(new LambdaQueryWrapper<SSKCBJS>().eq(SSKCBJS::getSskcbbh, sskcbbh));
|
||||||
sskcbfzjyMapper.delete(new LambdaQueryWrapper<SSKCBFZJY>().eq(SSKCBFZJY::getSskcbbh, sskcbbh));
|
sskcbfzjyMapper.delete(new LambdaQueryWrapper<SSKCBFZJY>().eq(SSKCBFZJY::getSskcbbh, sskcbbh));
|
||||||
|
kcbbzMapper.delete(new LambdaQueryWrapper<KCBBZMX>().eq(KCBBZMX::getKcbbh, sskcbbh));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 校历/班历不可排格键集合 */
|
/** 校历/班历不可排格键集合 */
|
||||||
@@ -757,7 +1146,8 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
|||||||
private void logOperation(SSKC course, String czlx, String cznr, SchedulingArrangeRequest ignored) {
|
private void logOperation(SSKC course, String czlx, String cznr, SchedulingArrangeRequest ignored) {
|
||||||
try {
|
try {
|
||||||
KCJXYX_CZRZ row = new KCJXYX_CZRZ();
|
KCJXYX_CZRZ row = new KCJXYX_CZRZ();
|
||||||
row.setBh(UuidUtil.getUUID());
|
// 课程教学运行_操作日志.编号 为 CHAR(36),按项目约定存带横线 36 位 UUID
|
||||||
|
row.setBh(UuidUtil.getOriginalUUID());
|
||||||
row.setTybh(course.getBh());
|
row.setTybh(course.getBh());
|
||||||
row.setCzlx(czlx);
|
row.setCzlx(czlx);
|
||||||
row.setCznr(cznr);
|
row.setCznr(cznr);
|
||||||
@@ -779,6 +1169,39 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
|||||||
return kb != null && kb.getKmc() != null ? kb.getKmc() : kbh;
|
return kb != null && kb.getKmc() != null ? kb.getKmc() : kbh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 教员姓名(失败返回编号) */
|
||||||
|
private String resolveTeacherName(String jybh) {
|
||||||
|
if (jybh == null || jybh.isEmpty()) return null;
|
||||||
|
try {
|
||||||
|
JYB jyb = jybMapper.selectById(jybh);
|
||||||
|
return jyb != null && jyb.getJyxm() != null ? jyb.getJyxm() : jybh;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return jybh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 教室名称(失败返回编号) */
|
||||||
|
private String resolveRoomName(String jsbh) {
|
||||||
|
if (jsbh == null || jsbh.isEmpty()) return null;
|
||||||
|
try {
|
||||||
|
List<JSB> rooms = classRoomMapper.selectList(new LambdaQueryWrapper<JSB>().eq(JSB::getJsbh, jsbh));
|
||||||
|
return rooms.isEmpty() ? jsbh : (rooms.get(0).getJsmc() != null ? rooms.get(0).getJsmc() : jsbh);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return jsbh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 学员队名称(失败返回编号) */
|
||||||
|
private String resolveTeamName(String xydbh) {
|
||||||
|
if (xydbh == null || xydbh.isEmpty()) return null;
|
||||||
|
try {
|
||||||
|
XYDB team = xydbMapper.selectById(xydbh);
|
||||||
|
return team != null && team.getXydmc() != null ? team.getXydmc() : xydbh;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return xydbh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static String intDate(Integer d) {
|
private static String intDate(Integer d) {
|
||||||
String s = String.valueOf(d);
|
String s = String.valueOf(d);
|
||||||
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;
|
||||||
|
|||||||
+4
-1
@@ -595,8 +595,11 @@ public class StudentTeamTaskServiceImpl implements StudentTeamTaskService {
|
|||||||
if (row.getKbh() == null || row.getXydbh() == null) {
|
if (row.getKbh() == null || row.getXydbh() == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
List<SSKC> courses = sskcMapper.selectList(new LambdaQueryWrapper<SSKC>().eq(SSKC::getKmbh, row.getKbh()));
|
// 注意:实施_课程 为中文列名 + VARBINARY 主键,selectList 会整行返回 null 元素,
|
||||||
|
// 这里必须走显式 XML(否则下面的 course.getBh() 会 NPE)。
|
||||||
|
List<SSKC> courses = sskcMapper.selectByKmbh(row.getKbh());
|
||||||
for (SSKC course : courses) {
|
for (SSKC course : courses) {
|
||||||
|
if (course == null) continue;
|
||||||
Long linked = sskcxydMapper.selectCount(new LambdaQueryWrapper<SSKCXYD>()
|
Long linked = sskcxydMapper.selectCount(new LambdaQueryWrapper<SSKCXYD>()
|
||||||
.eq(SSKCXYD::getSskcbh, course.getBh())
|
.eq(SSKCXYD::getSskcbh, course.getBh())
|
||||||
.eq(SSKCXYD::getXydbh, row.getXydbh()));
|
.eq(SSKCXYD::getXydbh, row.getXydbh()));
|
||||||
|
|||||||
+38
-1
@@ -8,10 +8,12 @@ import com.roomroot.jwgl.dto.taskbook.TaskBookRoomRequest;
|
|||||||
import com.roomroot.jwgl.dto.taskbook.TaskBookTeacherRequest;
|
import com.roomroot.jwgl.dto.taskbook.TaskBookTeacherRequest;
|
||||||
import com.roomroot.jwgl.entity.JYB;
|
import com.roomroot.jwgl.entity.JYB;
|
||||||
import com.roomroot.jwgl.entity.JXRW;
|
import com.roomroot.jwgl.entity.JXRW;
|
||||||
|
import com.roomroot.jwgl.entity.SSKCXYD;
|
||||||
import com.roomroot.jwgl.entity.XYDNDXQJBXXB;
|
import com.roomroot.jwgl.entity.XYDNDXQJBXXB;
|
||||||
import com.roomroot.jwgl.entity.XYDRWB;
|
import com.roomroot.jwgl.entity.XYDRWB;
|
||||||
import com.roomroot.jwgl.mapper.ClassSemesterMapper;
|
import com.roomroot.jwgl.mapper.ClassSemesterMapper;
|
||||||
import com.roomroot.jwgl.mapper.JYBMapper;
|
import com.roomroot.jwgl.mapper.JYBMapper;
|
||||||
|
import com.roomroot.jwgl.mapper.SSKCXYDMapper;
|
||||||
import com.roomroot.jwgl.mapper.StudentTeamTaskMapper;
|
import com.roomroot.jwgl.mapper.StudentTeamTaskMapper;
|
||||||
import com.roomroot.jwgl.mapper.TeachingTaskMapper;
|
import com.roomroot.jwgl.mapper.TeachingTaskMapper;
|
||||||
import com.roomroot.jwgl.service.TaskBookFillService;
|
import com.roomroot.jwgl.service.TaskBookFillService;
|
||||||
@@ -25,10 +27,12 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static com.roomroot.jwgl.utils.BasicManagementConstants.BAD_REQUEST;
|
import static com.roomroot.jwgl.utils.BasicManagementConstants.BAD_REQUEST;
|
||||||
import static com.roomroot.jwgl.utils.BasicManagementConstants.CONFLICT;
|
import static com.roomroot.jwgl.utils.BasicManagementConstants.CONFLICT;
|
||||||
@@ -55,6 +59,9 @@ public class TaskBookFillServiceImpl implements TaskBookFillService {
|
|||||||
@Resource
|
@Resource
|
||||||
private TeachingTaskWriteGuard guard;
|
private TeachingTaskWriteGuard guard;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SSKCXYDMapper sskcxydMapper;
|
||||||
|
|
||||||
// ==================== 列表 ====================
|
// ==================== 列表 ====================
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -191,7 +198,8 @@ public class TaskBookFillServiceImpl implements TaskBookFillService {
|
|||||||
public int split(TaskBookBhListRequest request) {
|
public int split(TaskBookBhListRequest request) {
|
||||||
List<XYDRWB> rows = loadRows(request);
|
List<XYDRWB> rows = loadRows(request);
|
||||||
assertAllFillable(rows);
|
assertAllFillable(rows);
|
||||||
// TODO(阶段 5):行已发布到运行课表的须先撤回,再拆班。阶段 5 落地后在此校验。
|
// 阶段 5 已落地:行所属班次学期若已发布到运行课表,合班组已被固化成运行课程,必须先撤回。
|
||||||
|
assertNotPublishedToRunning(rows);
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime now = LocalDateTime.now();
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (XYDRWB row : rows) {
|
for (XYDRWB row : rows) {
|
||||||
@@ -375,4 +383,33 @@ public class TaskBookFillServiceImpl implements TaskBookFillService {
|
|||||||
assertFillable(row);
|
assertFillable(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阶段 5 反向门禁:拆班前确认行所在班次学期尚未发布到运行课表。
|
||||||
|
*
|
||||||
|
* <p>判定信号与 {@code RunningCoursePublishServiceImpl#publish} 的幂等门禁同源:
|
||||||
|
* 「实施_课程学员队」按 (学员队编号, 年度=6 位学期代号) 命中即说明该班次学期已整批发布。
|
||||||
|
* 发布后合班组已固化为运行课程的 实施_课程.编号,拆班会让任务书与运行课表不一致。</p>
|
||||||
|
*
|
||||||
|
* <p>不用「实施_课程表.教学保障备注」或「课表变动时间」判定:前者由 logOperation 只更新自己
|
||||||
|
* 那门课,后者发布时不写,两者都不可靠。</p>
|
||||||
|
*/
|
||||||
|
private void assertNotPublishedToRunning(List<XYDRWB> rows) {
|
||||||
|
Set<String> checked = new HashSet<>();
|
||||||
|
for (XYDRWB row : rows) {
|
||||||
|
String xydxqbh = row.getXydxqbh();
|
||||||
|
if (xydxqbh == null || xydxqbh.isEmpty() || !checked.add(xydxqbh)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
XYDNDXQJBXXB semester = requireSemester(xydxqbh);
|
||||||
|
Integer ndCode = SemesterCodeUtil.resolve(semester.getNd(), semester.getXqdc());
|
||||||
|
Long published = sskcxydMapper.selectCount(new LambdaQueryWrapper<SSKCXYD>()
|
||||||
|
.eq(SSKCXYD::getXydbh, semester.getXydbh())
|
||||||
|
.eq(SSKCXYD::getNd, ndCode));
|
||||||
|
if (published != null && published > 0) {
|
||||||
|
throw new ServiceException("班次学期 " + semester.getXydbh() + "(" + ndCode + ")已发布 "
|
||||||
|
+ published + " 条运行课程,请先在运行课表撤回后再拆班", CONFLICT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+111
-10
@@ -4,8 +4,10 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.roomroot.jwgl.entity.JQB;
|
import com.roomroot.jwgl.entity.JQB;
|
||||||
import com.roomroot.jwgl.entity.JXCDL;
|
import com.roomroot.jwgl.entity.JXCDL;
|
||||||
|
import com.roomroot.jwgl.entity.JYL;
|
||||||
import com.roomroot.jwgl.entity.SSKC;
|
import com.roomroot.jwgl.entity.SSKC;
|
||||||
import com.roomroot.jwgl.entity.SSKCB;
|
import com.roomroot.jwgl.entity.SSKCB;
|
||||||
|
import com.roomroot.jwgl.entity.SSKCBFZJY;
|
||||||
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;
|
||||||
@@ -14,6 +16,7 @@ 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.ClassSemesterMapper;
|
||||||
import com.roomroot.jwgl.mapper.JQBMapper;
|
import com.roomroot.jwgl.mapper.JQBMapper;
|
||||||
|
import com.roomroot.jwgl.mapper.JYLMapper;
|
||||||
import com.roomroot.jwgl.mapper.KBMapper;
|
import com.roomroot.jwgl.mapper.KBMapper;
|
||||||
import com.roomroot.jwgl.mapper.SSKCBFZJYMapper;
|
import com.roomroot.jwgl.mapper.SSKCBFZJYMapper;
|
||||||
import com.roomroot.jwgl.mapper.SSKCBJSMapper;
|
import com.roomroot.jwgl.mapper.SSKCBJSMapper;
|
||||||
@@ -96,6 +99,9 @@ public class TimetableConflictServiceImpl implements TimetableConflictService {
|
|||||||
@Resource
|
@Resource
|
||||||
private ClassRoomCalendarMapper classRoomCalendarMapper;
|
private ClassRoomCalendarMapper classRoomCalendarMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private JYLMapper jylMapper;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private SSKCBMapper sskcbMapper;
|
private SSKCBMapper sskcbMapper;
|
||||||
|
|
||||||
@@ -258,7 +264,13 @@ public class TimetableConflictServiceImpl implements TimetableConflictService {
|
|||||||
details = switch (dimension) {
|
details = switch (dimension) {
|
||||||
case TEAM_CONFLICT -> sskcbxydMapper.selectTeamConflictDetails(nd);
|
case TEAM_CONFLICT -> sskcbxydMapper.selectTeamConflictDetails(nd);
|
||||||
case ELECTIVE_REQUIRED_CONFLICT -> sskcbxydMapper.selectElectiveRequiredConflictDetails(nd);
|
case ELECTIVE_REQUIRED_CONFLICT -> sskcbxydMapper.selectElectiveRequiredConflictDetails(nd);
|
||||||
case TEACHER_CONFLICT -> sskcbfzjyMapper.selectTeacherConflictDetails(nd);
|
case TEACHER_CONFLICT -> {
|
||||||
|
// 口径统一(与排课窗口硬冲突一致):教员时间冲突 = 教员双占 + 教员历不可排
|
||||||
|
List<TimetableConflictDetailVO> teacherDup =
|
||||||
|
new ArrayList<>(sskcbfzjyMapper.selectTeacherConflictDetails(nd));
|
||||||
|
teacherDup.addAll(checkTeacherCalendarConflicts(nd));
|
||||||
|
yield teacherDup;
|
||||||
|
}
|
||||||
case CLASSROOM_CONFLICT -> {
|
case CLASSROOM_CONFLICT -> {
|
||||||
// 阶段 5.1:教室重复占用 + 场地历不可用
|
// 阶段 5.1:教室重复占用 + 场地历不可用
|
||||||
List<TimetableConflictDetailVO> dup = sskcbjsMapper.selectClassroomConflictDetails(nd);
|
List<TimetableConflictDetailVO> dup = sskcbjsMapper.selectClassroomConflictDetails(nd);
|
||||||
@@ -626,17 +638,92 @@ public class TimetableConflictServiceImpl implements TimetableConflictService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 构建 不可排课格 类冲突明细 */
|
/**
|
||||||
|
* TEACHER_CONFLICT 追加:教员历 标记不可排课(可排课=0)但该教员该格已有课次。
|
||||||
|
* <p>
|
||||||
|
* 与 {@link #checkClassroomCalendarConflicts} 同为"不可排课格"类检查,只是主体换成教员:
|
||||||
|
* 排课窗口硬冲突里含「教员历不可用」,批量检查此前缺失该维度,此处补齐以保证两套口径一致。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 匹配口径与排课窗口一致:按 <b>日期(天) + 节次 + 教员编号</b> 三元组,不比对具体时分。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
private List<TimetableConflictDetailVO> checkTeacherCalendarConflicts(Integer nd) {
|
||||||
|
List<TimetableConflictDetailVO> result = new ArrayList<>();
|
||||||
|
List<SSKCB> lessons = sskcbMapper.selectList(new LambdaQueryWrapper<SSKCB>()
|
||||||
|
.eq(SSKCB::getNd, nd)
|
||||||
|
.eq(SSKCB::getSczt, 0)
|
||||||
|
.isNotNull(SSKCB::getRq));
|
||||||
|
if (lessons.isEmpty()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// 教员历 无"删除状态"列,只需 可排课 = 0
|
||||||
|
List<JYL> unavailable = jylMapper.selectList(new LambdaQueryWrapper<JYL>()
|
||||||
|
.eq(JYL::getKpk, 0)
|
||||||
|
.isNotNull(JYL::getRq));
|
||||||
|
if (unavailable.isEmpty()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
Set<String> lessonIds = lessons.stream().map(SSKCB::getBh).collect(Collectors.toSet());
|
||||||
|
// 课次 → 教员编号集合(主讲 + 辅讲统一纳入检查)
|
||||||
|
Map<String, List<String>> teachersOfLesson = new HashMap<>();
|
||||||
|
for (SSKCBFZJY link : sskcbfzjyMapper.selectList(new LambdaQueryWrapper<SSKCBFZJY>()
|
||||||
|
.in(SSKCBFZJY::getSskcbbh, lessonIds))) {
|
||||||
|
if (link.getFzjybh() == null) continue;
|
||||||
|
teachersOfLesson.computeIfAbsent(link.getSskcbbh(), k -> new ArrayList<>()).add(link.getFzjybh());
|
||||||
|
}
|
||||||
|
if (teachersOfLesson.isEmpty()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
Map<String, String> courseNames = loadCourseNames(nd);
|
||||||
|
// 按 教员编号#日期#节次 → 该格已有课次,便于一次聚合出明细
|
||||||
|
Map<String, List<SSKCB>> lessonsByTeacherCell = new HashMap<>();
|
||||||
|
Map<String, SSKCB> lessonByBh = lessons.stream()
|
||||||
|
.collect(Collectors.toMap(SSKCB::getBh, l -> l, (a, b) -> a));
|
||||||
|
for (Map.Entry<String, List<String>> e : teachersOfLesson.entrySet()) {
|
||||||
|
SSKCB lesson = lessonByBh.get(e.getKey());
|
||||||
|
if (lesson == null || lesson.getRq() == null || lesson.getJc() == null) continue;
|
||||||
|
String dateKey = lesson.getRq().toLocalDate().toString() + "#" + lesson.getJc();
|
||||||
|
for (String jybh : e.getValue()) {
|
||||||
|
lessonsByTeacherCell.computeIfAbsent(jybh + "#" + dateKey, k -> new ArrayList<>()).add(lesson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (JYL row : unavailable) {
|
||||||
|
if (row.getRq() == null || row.getJybh() == null) continue;
|
||||||
|
String dateStr = row.getRq().toLocalDate().toString();
|
||||||
|
String key = row.getJybh() + "#" + dateStr + "#" + row.getJc();
|
||||||
|
List<SSKCB> hit = lessonsByTeacherCell.get(key);
|
||||||
|
if (hit == null || hit.isEmpty()) continue;
|
||||||
|
TimetableConflictDetailVO d = buildEventDetail(
|
||||||
|
"TEACHERCAL|" + row.getJybh() + "|" + dateStr + "#" + row.getJc(),
|
||||||
|
safeName(row.getMc()), dateStr, row.getJc(), hit, courseNames,
|
||||||
|
"教员历标记该教员该时段不可排课,但已有课次安排",
|
||||||
|
TimetableConflictDimension.TEACHER_CONFLICT);
|
||||||
|
result.add(d);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 构建 不可排课格 类冲突明细(默认归属"日历冲突"维度) */
|
||||||
private TimetableConflictDetailVO buildEventDetail(String resourceId, String resourceName,
|
private TimetableConflictDetailVO buildEventDetail(String resourceId, String resourceName,
|
||||||
String dateStr, Integer jc, List<SSKCB> lessons,
|
String dateStr, Integer jc, List<SSKCB> lessons,
|
||||||
Map<String, String> courseNames, String reason) {
|
Map<String, String> courseNames, String reason) {
|
||||||
|
return buildEventDetail(resourceId, resourceName, dateStr, jc, lessons, courseNames, reason,
|
||||||
|
TimetableConflictDimension.EVENT_CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 构建 不可排课格 类冲突明细(可指定归属维度,使明细归属与其所在卡片一致) */
|
||||||
|
private TimetableConflictDetailVO buildEventDetail(String resourceId, String resourceName,
|
||||||
|
String dateStr, Integer jc, List<SSKCB> lessons,
|
||||||
|
Map<String, String> courseNames, String reason,
|
||||||
|
TimetableConflictDimension dimension) {
|
||||||
String courses = lessons.stream()
|
String courses = lessons.stream()
|
||||||
.map(l -> courseNames.getOrDefault(l.getSskcbh(), l.getSskcbh()))
|
.map(l -> courseNames.getOrDefault(l.getSskcbh(), l.getSskcbh()))
|
||||||
.distinct()
|
.distinct()
|
||||||
.collect(Collectors.joining(";"));
|
.collect(Collectors.joining(";"));
|
||||||
return TimetableConflictDetailVO.builder()
|
return TimetableConflictDetailVO.builder()
|
||||||
.dimensionCode("EVENT_CONFLICT")
|
.dimensionCode(dimension.getCode())
|
||||||
.dimensionTitle("日历冲突")
|
.dimensionTitle(dimension.getTitle())
|
||||||
.resourceId(resourceId)
|
.resourceId(resourceId)
|
||||||
.resourceName(resourceName)
|
.resourceName(resourceName)
|
||||||
.rq(LocalDate.parse(dateStr).atStartOfDay())
|
.rq(LocalDate.parse(dateStr).atStartOfDay())
|
||||||
@@ -652,13 +739,27 @@ public class TimetableConflictServiceImpl implements TimetableConflictService {
|
|||||||
/** 实施_课程编号 → 课程名称(经 实施_课程.科目编号 → 课表.课名称) */
|
/** 实施_课程编号 → 课程名称(经 实施_课程.科目编号 → 课表.课名称) */
|
||||||
private Map<String, String> loadCourseNames(Integer nd) {
|
private Map<String, String> loadCourseNames(Integer nd) {
|
||||||
Map<String, String> names = new HashMap<>();
|
Map<String, String> names = new HashMap<>();
|
||||||
List<SSKC> courses = sskcMapper.selectList(new LambdaQueryWrapper<SSKC>().eq(SSKC::getNd, nd));
|
// 必须走显式 XML:selectList 在本表(中文列名 + VARBINARY 主键)会整行返回 null 元素
|
||||||
if (courses.isEmpty()) return names;
|
List<SSKC> courses = sskcMapper.selectByNd(nd);
|
||||||
Set<String> kbhs = courses.stream().map(SSKC::getKmbh).collect(Collectors.toSet());
|
if (courses == null || courses.isEmpty()) {
|
||||||
Map<String, String> kbhNames = kbMapper.selectBatchIds(kbhs).stream()
|
return names;
|
||||||
.collect(Collectors.toMap(com.roomroot.jwgl.entity.KB::getKbh, k -> safeName(k.getKmc()), (a, b) -> a));
|
}
|
||||||
|
Set<String> kbhs = courses.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(SSKC::getKmbh)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
Map<String, String> kbhNames = new HashMap<>();
|
||||||
|
if (!kbhs.isEmpty()) {
|
||||||
|
for (com.roomroot.jwgl.entity.KB k : kbMapper.selectBatchIds(kbhs)) {
|
||||||
|
if (k == null || k.getKbh() == null) continue;
|
||||||
|
kbhNames.put(k.getKbh(), safeName(k.getKmc()));
|
||||||
|
}
|
||||||
|
}
|
||||||
for (SSKC c : courses) {
|
for (SSKC c : courses) {
|
||||||
names.put(c.getBh(), kbhNames.getOrDefault(c.getKmbh(), c.getKmbh()));
|
if (c == null) continue;
|
||||||
|
names.put(c.getBh(), c.getKmbh() == null ? safeName(c.getBh())
|
||||||
|
: kbhNames.getOrDefault(c.getKmbh(), safeName(c.getKmbh())));
|
||||||
}
|
}
|
||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-4
@@ -52,33 +52,48 @@ public enum TimetableConflictDimension {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 教员时间冲突检查。
|
* 教员时间冲突检查。
|
||||||
* <p>检查逻辑:同一教员(主讲+辅讲统一在此维度)在同一日期同一节次被安排多门课程 → 冲突</p>
|
* <p>检查逻辑(两部分,均为硬冲突):
|
||||||
|
* <ol>
|
||||||
|
* <li>同一教员(主讲+辅讲统一在此维度)在同一日期同一节次被安排多门课程 → 冲突</li>
|
||||||
|
* <li>教员历(教员工作时间表)标记"可排课=0"的日期节次上,该教员仍有已排课次 → 冲突</li>
|
||||||
|
* </ol>
|
||||||
|
* </p>
|
||||||
* <p>涉及数据表:
|
* <p>涉及数据表:
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>实施_课程表 SSKCB —— 主表,日期/节次/年度/删除状态</li>
|
* <li>实施_课程表 SSKCB —— 主表,日期/节次/年度/删除状态</li>
|
||||||
* <li>实施_课程表_辅助教员 SSKCBFZJY —— 课次与教员的关联表(含主讲ZJY=1 与辅讲ZJY=0),获取教员编号(FZJYBH)</li>
|
* <li>实施_课程表_辅助教员 SSKCBFZJY —— 课次与教员的关联表(含主讲ZJY=1 与辅讲ZJY=0),获取教员编号(FZJYBH)</li>
|
||||||
|
* <li>教员历 JYL —— 获取教员"可排课"标记(可排课=0 表示不可排)</li>
|
||||||
* <li>教员表 JYB / 教研室人员 JYSX —— 翻译中文姓名展示</li>
|
* <li>教员表 JYB / 教研室人员 JYSX —— 翻译中文姓名展示</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
* </p>
|
* </p>
|
||||||
|
* <p><b>口径说明:</b>排课窗口的硬冲突包含「教员历不可用」,为此本维度同样纳入教员历检查,
|
||||||
|
* 使批量冲突检查与排课窗口判断一致。实现方式与「场地历不可用」并入
|
||||||
|
* {@link #CLASSROOM_CONFLICT} 保持一致,不再单开卡片,避免同一事实被重复计数。</p>
|
||||||
*/
|
*/
|
||||||
TEACHER_CONFLICT("TEACHER_CONFLICT",
|
TEACHER_CONFLICT("TEACHER_CONFLICT",
|
||||||
"教员时间冲突检查",
|
"教员时间冲突检查",
|
||||||
"同一教员在同一时间段被安排多门课程"),
|
"同一教员在同一时间段被安排多门课程,或教员历不可排课时段已有课程"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 教室时间冲突检查。
|
* 教室时间冲突检查。
|
||||||
* <p>检查逻辑:同一教室(场地)在同一日期同一节次被多门课程占用 → 冲突</p>
|
* <p>检查逻辑(两部分,均为硬冲突):
|
||||||
|
* <ol>
|
||||||
|
* <li>同一教室(场地)在同一日期同一节次被多门课程占用 → 冲突</li>
|
||||||
|
* <li>教学场地历标记"可排课=0"的教室节次上仍有已排课次 → 冲突(场地历不可用)</li>
|
||||||
|
* </ol>
|
||||||
|
* </p>
|
||||||
* <p>涉及数据表:
|
* <p>涉及数据表:
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>实施_课程表 SSKCB —— 主表,日期/节次/年度/删除状态</li>
|
* <li>实施_课程表 SSKCB —— 主表,日期/节次/年度/删除状态</li>
|
||||||
* <li>实施_课程表_教室 SSKCBJS —— 课次与教室的关联表,获取教室编号(JSBH)</li>
|
* <li>实施_课程表_教室 SSKCBJS —— 课次与教室的关联表,获取教室编号(JSBH)</li>
|
||||||
|
* <li>教学场地历 JXCDL —— 获取场地"可排课"标记</li>
|
||||||
* <li>教室表 JSB —— 翻译中文名称(含容纳人数/设施等信息)</li>
|
* <li>教室表 JSB —— 翻译中文名称(含容纳人数/设施等信息)</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
CLASSROOM_CONFLICT("CLASSROOM_CONFLICT",
|
CLASSROOM_CONFLICT("CLASSROOM_CONFLICT",
|
||||||
"教室时间冲突检查",
|
"教室时间冲突检查",
|
||||||
"同一教室在同一时间段被多门课程占用"),
|
"同一教室在同一时间段被多门课程占用,或场地历不可用时段已有课程"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 教学保障资源冲突检查(预留/按需开启)。
|
* 教学保障资源冲突检查(预留/按需开启)。
|
||||||
|
|||||||
+49
@@ -34,11 +34,26 @@ public class SchedulingViewVO {
|
|||||||
/** 时间区:按周切分,格子叠加校历/班历不可排与已排课次 */
|
/** 时间区:按周切分,格子叠加校历/班历不可排与已排课次 */
|
||||||
private List<Week> weeks = new ArrayList<>();
|
private List<Week> weeks = new ArrayList<>();
|
||||||
|
|
||||||
|
/** 节次字典(节次时间表),供前端显示控制(7-8 节 / 晚上 / 夜间) */
|
||||||
|
private List<Period> periods = new ArrayList<>();
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Period {
|
||||||
|
/** 节次号 */
|
||||||
|
private Integer jc;
|
||||||
|
/** 节次名称/简称 */
|
||||||
|
private String label;
|
||||||
|
/** 时段:上午 / 下午 / 晚上 */
|
||||||
|
private String sd;
|
||||||
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class Course {
|
public static class Course {
|
||||||
private String sskcbh;
|
private String sskcbh;
|
||||||
private String kbh;
|
private String kbh;
|
||||||
private String kcmc;
|
private String kcmc;
|
||||||
|
/** 课程简称(任务表 简称,格内优先显示) */
|
||||||
|
private String jc;
|
||||||
private String klx;
|
private String klx;
|
||||||
private Integer xs;
|
private Integer xs;
|
||||||
private Integer zks;
|
private Integer zks;
|
||||||
@@ -46,12 +61,27 @@ public class SchedulingViewVO {
|
|||||||
private String jyxm;
|
private String jyxm;
|
||||||
private String jsbh;
|
private String jsbh;
|
||||||
private String jsmc;
|
private String jsmc;
|
||||||
|
/** 排课建议(教研室任务书填报的 教研室计划备注) */
|
||||||
|
private String jysjhbz;
|
||||||
|
/** 教研室计划教员编号 / 姓名 */
|
||||||
|
private String jysjhjybh;
|
||||||
|
private String jysjhjyxm;
|
||||||
/** 已排学时(每节 2 学时 + 节次调节) */
|
/** 已排学时(每节 2 学时 + 节次调节) */
|
||||||
private Integer scheduledHours;
|
private Integer scheduledHours;
|
||||||
/** 是否已排满(scheduledHours >= xs) */
|
/** 是否已排满(scheduledHours >= xs) */
|
||||||
private Boolean full;
|
private Boolean full;
|
||||||
/** 当前用户是否可编辑该课程 */
|
/** 当前用户是否可编辑该课程 */
|
||||||
private Boolean editable;
|
private Boolean editable;
|
||||||
|
/** 合班班次(⑤ 合班信息) */
|
||||||
|
private List<Team> teams = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 班次学期(学员队 + 学期)简项 */
|
||||||
|
@Data
|
||||||
|
public static class Team {
|
||||||
|
private String xydxqbh;
|
||||||
|
private String xydbh;
|
||||||
|
private String xydmc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@@ -78,8 +108,16 @@ public class SchedulingViewVO {
|
|||||||
private Boolean unavailable;
|
private Boolean unavailable;
|
||||||
/** 硬冲突原因(不可排课来源:校历/班历等) */
|
/** 硬冲突原因(不可排课来源:校历/班历等) */
|
||||||
private String reason;
|
private String reason;
|
||||||
|
/** 不可排来源类别:SCHOOL 校历 / CLASS 班历 */
|
||||||
|
private String calendarKind;
|
||||||
/** 软提示(非正课 / 配当周次不符) */
|
/** 软提示(非正课 / 配当周次不符) */
|
||||||
private String warning;
|
private String warning;
|
||||||
|
/** 是否为非正课时段 */
|
||||||
|
private Boolean soft;
|
||||||
|
/** 该节次被场地历标记不可用的教室编号 */
|
||||||
|
private List<String> blockedRooms = new ArrayList<>();
|
||||||
|
/** 该节次被教员历标记不可用的教员编号 */
|
||||||
|
private List<String> blockedTeachers = new ArrayList<>();
|
||||||
private List<Lesson> lessons = new ArrayList<>();
|
private List<Lesson> lessons = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,10 +127,21 @@ public class SchedulingViewVO {
|
|||||||
private String sskcbh;
|
private String sskcbh;
|
||||||
private String kcmc;
|
private String kcmc;
|
||||||
private String jxnr;
|
private String jxnr;
|
||||||
|
private String jxyd;
|
||||||
private String jxff;
|
private String jxff;
|
||||||
private Integer jcdj;
|
private Integer jcdj;
|
||||||
private String jyxm;
|
private String jyxm;
|
||||||
|
private String jybh;
|
||||||
private String jsmc;
|
private String jsmc;
|
||||||
|
private String jsbh;
|
||||||
|
private String jxbzbz;
|
||||||
|
private String bzms;
|
||||||
private String ycxx;
|
private String ycxx;
|
||||||
|
/** 本次课次的学员队(教学班次) */
|
||||||
|
private List<String> xydbhs = new ArrayList<>();
|
||||||
|
/** 本次课次的教室(可多场地) */
|
||||||
|
private List<String> jsbhs = new ArrayList<>();
|
||||||
|
/** 本次课次的授课教员(含主讲/辅讲) */
|
||||||
|
private List<String> jybhs = new ArrayList<>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,3 +58,47 @@ export function listSchedulingLogs(sskcbh, czlx) {
|
|||||||
params: { sskcbh, czlx }
|
params: { sskcbh, czlx }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 编辑课程教学任务信息(学时/周课时/课程简称/责任教员/默认场地)
|
||||||
|
export function updateSchedulingCourse(data) {
|
||||||
|
return request({
|
||||||
|
url: '/schedulingWindow/course/update',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 教学班次(合班)当前列表 + 候选班次
|
||||||
|
export function getCourseTeams(sskcbh) {
|
||||||
|
return request({
|
||||||
|
url: '/schedulingWindow/course/teams',
|
||||||
|
method: 'get',
|
||||||
|
params: { sskcbh }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 教学保障选项(保障类别 + 保障资源,含总量与单位)
|
||||||
|
export function getSupportOptions() {
|
||||||
|
return request({
|
||||||
|
url: '/schedulingWindow/support/options',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量发布【运行课表】(手册 12.1)
|
||||||
|
export function publishRunningCourses(xydxqbhs) {
|
||||||
|
return request({
|
||||||
|
url: '/schedulingWindow/publish',
|
||||||
|
method: 'post',
|
||||||
|
data: { xydxqbhs }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除【运行课表】(手册 12.2)
|
||||||
|
export function withdrawRunningCourses(xydxqbhs) {
|
||||||
|
return request({
|
||||||
|
url: '/schedulingWindow/withdraw',
|
||||||
|
method: 'post',
|
||||||
|
data: { xydxqbhs }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 教员历(后端 /teacherCalendar/jyl)。
|
||||||
|
// 教员历 = 教员自己的工作时间表:标出的时段「不可排课」。
|
||||||
|
// 排课窗口的硬冲突「教员历不可用」与批量冲突检查的教员时间冲突都读这张表。
|
||||||
|
|
||||||
|
// 按教员编号查询该教员已登记的教员历(返回全部,前端按周过滤)
|
||||||
|
export function listTeacherCalendarByJybh(jybh) {
|
||||||
|
return request({
|
||||||
|
url: '/teacherCalendar/jyl/listByJybh',
|
||||||
|
method: 'get',
|
||||||
|
params: { jybh }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量保存(按 教员编号+日期+节次 幂等覆盖)
|
||||||
|
export function batchSaveTeacherCalendar(list) {
|
||||||
|
return request({
|
||||||
|
url: '/teacherCalendar/jyl/batchSave',
|
||||||
|
method: 'post',
|
||||||
|
data: list
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除(教员历表无 del_flag 审计列,为物理删除)
|
||||||
|
export function batchDeleteTeacherCalendar(bhs) {
|
||||||
|
return request({
|
||||||
|
url: '/teacherCalendar/jyl/batchDelete',
|
||||||
|
method: 'post',
|
||||||
|
data: bhs
|
||||||
|
})
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -99,11 +99,12 @@ import { listAllSemester } from '@/api/teachBusiness/semester'
|
|||||||
|
|
||||||
// 四类检查卡片定义(标题/描述与后端 TimetableConflictDimension 枚举一致,作为页面布局常量;
|
// 四类检查卡片定义(标题/描述与后端 TimetableConflictDimension 枚举一致,作为页面布局常量;
|
||||||
// 检查状态与冲突数完全来自后端接口)
|
// 检查状态与冲突数完全来自后端接口)
|
||||||
|
// 注意:教员卡片含「教员历不可排」、教室卡片含「场地历不可用」,与排课窗口硬冲突口径一致
|
||||||
const CARD_DEFS = [
|
const CARD_DEFS = [
|
||||||
{ key: 'TEAM_CONFLICT', label: '教学班时间冲突检查', desc: '同一教学班次在同一时间段被安排多门课程' },
|
{ key: 'TEAM_CONFLICT', label: '教学班时间冲突检查', desc: '同一教学班次在同一时间段被安排多门课程' },
|
||||||
{ key: 'ELECTIVE_REQUIRED_CONFLICT', label: '教学班必修与选修时间冲突检查', desc: '教学班次必修课与选修课时间重叠' },
|
{ key: 'ELECTIVE_REQUIRED_CONFLICT', label: '教学班必修与选修时间冲突检查', desc: '教学班次必修课与选修课时间重叠' },
|
||||||
{ key: 'TEACHER_CONFLICT', label: '教员时间冲突检查', desc: '同一教员在同一时间段被安排多门课程' },
|
{ key: 'TEACHER_CONFLICT', label: '教员时间冲突检查', desc: '同一教员在同一时间段被安排多门课程,或教员历不可排课时段已有课程' },
|
||||||
{ key: 'CLASSROOM_CONFLICT', label: '教室时间冲突检查', desc: '同一教室在同一时间段被多门课程占用' }
|
{ key: 'CLASSROOM_CONFLICT', label: '教室时间冲突检查', desc: '同一教室在同一时间段被多门课程占用,或场地历不可用时段已有课程' }
|
||||||
]
|
]
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -64,6 +64,8 @@
|
|||||||
<el-button type="success" plain icon="el-icon-date" :disabled="!currentRow" @click="handleEditEventCalendar">编辑班历</el-button>
|
<el-button type="success" plain icon="el-icon-date" :disabled="!currentRow" @click="handleEditEventCalendar">编辑班历</el-button>
|
||||||
<el-button type="success" plain icon="el-icon-calendar" :disabled="!currentRow" @click="handleEditCalendar">编辑班次教学任务</el-button>
|
<el-button type="success" plain icon="el-icon-calendar" :disabled="!currentRow" @click="handleEditCalendar">编辑班次教学任务</el-button>
|
||||||
<el-button type="success" plain icon="el-icon-data-line" :disabled="!currentRow" @click="handleAllocation">教学配当</el-button>
|
<el-button type="success" plain icon="el-icon-data-line" :disabled="!currentRow" @click="handleAllocation">教学配当</el-button>
|
||||||
|
<el-button type="primary" plain icon="el-icon-upload2" :disabled="!selection.length" @click="handlePublishRunning">发布运行课表</el-button>
|
||||||
|
<el-button type="danger" plain icon="el-icon-delete-solid" :disabled="!selection.length" @click="handleWithdrawRunning">删除运行课表</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -333,6 +335,7 @@ import { batchAutoGenerateTasks } from '@/api/studentRecords/classCalendar'
|
|||||||
import { listSemester, allSemester, addSemester, updateSemester, delSemester, batchDeleteSemester, batchUpdateDateRange, batchUpdateJxrwbh, batchCreateSemester, batchUpdateXqdc, batchUpdateKfjypk, batchWaveMerge, batchWaveSplit, listCreatableClasses, presetSemesterDates } from '@/api/studentRecords/semester'
|
import { listSemester, allSemester, addSemester, updateSemester, delSemester, batchDeleteSemester, batchUpdateDateRange, batchUpdateJxrwbh, batchCreateSemester, batchUpdateXqdc, batchUpdateKfjypk, batchWaveMerge, batchWaveSplit, listCreatableClasses, presetSemesterDates } from '@/api/studentRecords/semester'
|
||||||
import { listAllSemester } from '@/api/teachBusiness/semester'
|
import { listAllSemester } from '@/api/teachBusiness/semester'
|
||||||
import { listTeachingTask } from '@/api/teachBusiness/teachingTask'
|
import { listTeachingTask } from '@/api/teachBusiness/teachingTask'
|
||||||
|
import { publishRunningCourses, withdrawRunningCourses } from '@/api/teachBusiness/schedulingWindow'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ShiftSemester',
|
name: 'ShiftSemester',
|
||||||
@@ -346,6 +349,8 @@ export default {
|
|||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
saving: false,
|
saving: false,
|
||||||
|
/** 运行课表批量发布/删除进行中 */
|
||||||
|
runningPublishing: false,
|
||||||
|
|
||||||
// ==================== 查询条件 ====================
|
// ==================== 查询条件 ====================
|
||||||
searchForm: { xydbh: '', xqdc: undefined },
|
searchForm: { xydbh: '', xqdc: undefined },
|
||||||
@@ -663,6 +668,70 @@ export default {
|
|||||||
this.allocationVisible = true
|
this.allocationVisible = true
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 批量发布【运行课表】(手册 12.1):把课程任务发布为运行课程,之后才能在排课窗口排课
|
||||||
|
handlePublishRunning() {
|
||||||
|
const rows = this.selection.slice()
|
||||||
|
if (!rows.length) {
|
||||||
|
this.$message.warning('请先勾选班次学期')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const ids = rows.map(item => item.bh || item.xydxqbh).filter(Boolean)
|
||||||
|
this.$confirm(
|
||||||
|
`将发布所选 ${ids.length} 个班次学期的课程到运行课表。<br/>发布前要求:教学任务已发布未结束、每门课程已指定责任教员与默认场地。`,
|
||||||
|
'批量发布【运行课表】',
|
||||||
|
{ type: 'warning', dangerouslyUseHTMLString: true }
|
||||||
|
).then(() => {
|
||||||
|
this.batchRunningPublish(publishRunningCourses, ids, '发布')
|
||||||
|
}).catch(() => {})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 批量删除【运行课表】(手册 12.2):撤回运行课程,已排课次一并删除
|
||||||
|
handleWithdrawRunning() {
|
||||||
|
const rows = this.selection.slice()
|
||||||
|
if (!rows.length) {
|
||||||
|
this.$message.warning('请先勾选班次学期')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const ids = rows.map(item => item.bh || item.xydxqbh).filter(Boolean)
|
||||||
|
this.$confirm(
|
||||||
|
`将从运行课表删除所选 ${ids.length} 个班次学期的全部课程。<br/><b>这些班次的排课信息(已排课次)将一并删除</b>,编制侧任务保留。`,
|
||||||
|
'批量删除【运行课表】',
|
||||||
|
{ type: 'warning', dangerouslyUseHTMLString: true }
|
||||||
|
).then(() => {
|
||||||
|
this.batchRunningPublish(withdrawRunningCourses, ids, '删除')
|
||||||
|
}).catch(() => {})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 批量发布/删除公共处理:逐条展示成功与失败原因
|
||||||
|
batchRunningPublish(api, ids, action) {
|
||||||
|
this.runningPublishing = true
|
||||||
|
api(ids).then(res => {
|
||||||
|
const list = (res && res.data) || []
|
||||||
|
const ok = list.filter(i => i.success)
|
||||||
|
const fail = list.filter(i => !i.success)
|
||||||
|
const okText = ok.length
|
||||||
|
? `成功 ${ok.length} 个:` + ok.map(i => `${this.semesterNameOf(i.xydxqbh)}(${i.message})`).join(';')
|
||||||
|
: ''
|
||||||
|
const failText = fail.length
|
||||||
|
? `失败 ${fail.length} 个:` + fail.map(i => `${this.semesterNameOf(i.xydxqbh)}(${i.message})`).join(';')
|
||||||
|
: ''
|
||||||
|
const html = [okText, failText].filter(Boolean).join('<br/>')
|
||||||
|
if (fail.length) {
|
||||||
|
this.$alert(html, `运行课表${action}结果`, { dangerouslyUseHTMLString: true })
|
||||||
|
} else {
|
||||||
|
this.$message.success(`运行课表${action}完成:成功 ${ok.length} 个`)
|
||||||
|
}
|
||||||
|
this.loadList()
|
||||||
|
}).finally(() => {
|
||||||
|
this.runningPublishing = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
semesterNameOf(bh) {
|
||||||
|
const row = this.selection.find(i => (i.bh || i.xydxqbh) === bh)
|
||||||
|
return row ? (row.xydmc || row.bh) : bh
|
||||||
|
},
|
||||||
|
|
||||||
handleBatchGenerateTasks() {
|
handleBatchGenerateTasks() {
|
||||||
if (!this.selection.length) {
|
if (!this.selection.length) {
|
||||||
this.$message.warning('请先勾选班次学期')
|
this.$message.warning('请先勾选班次学期')
|
||||||
|
|||||||
@@ -0,0 +1,461 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:visible="dialogVisible"
|
||||||
|
:title="`教员历——${teacherTitle}`"
|
||||||
|
width="92%"
|
||||||
|
top="4vh"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
class="teacher-calendar-dialog"
|
||||||
|
@update:visible="val => dialogVisible = val"
|
||||||
|
>
|
||||||
|
<div class="dialog-toolbar">
|
||||||
|
<span class="toolbar-label">学期</span>
|
||||||
|
<el-select
|
||||||
|
v-model="nd"
|
||||||
|
size="small"
|
||||||
|
filterable
|
||||||
|
:loading="semesterLoading"
|
||||||
|
placeholder="请选择学期"
|
||||||
|
class="semester-select"
|
||||||
|
@change="onSemesterChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in semesterOptions"
|
||||||
|
:key="item.nd"
|
||||||
|
:label="getSemesterName(item.nd)"
|
||||||
|
:value="item.nd"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<span class="toolbar-label">周次</span>
|
||||||
|
<el-select v-model="weekIndex" size="small" class="week-select" :disabled="!weeks.length">
|
||||||
|
<el-option
|
||||||
|
v-for="(w, i) in weeks"
|
||||||
|
:key="'w-' + i"
|
||||||
|
:label="`第 ${i + 1} 周(${w.rangeText})`"
|
||||||
|
:value="i"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-button size="small" :disabled="weekIndex <= 0" @click="handlePrevWeek">上一周</el-button>
|
||||||
|
<el-button size="small" :disabled="weekIndex >= weeks.length - 1" @click="handleNextWeek">下一周</el-button>
|
||||||
|
<el-button size="small" type="warning" plain @click="handleClearWeek">清除本周标记</el-button>
|
||||||
|
<span class="hint">点击格子标记「不可排课」。标记后,排课窗口会判为硬冲突「教员历不可用」,批量冲突检查的「教员时间冲突」也会报出。</span>
|
||||||
|
</div>
|
||||||
|
<div v-loading="loading" class="editor-wrap">
|
||||||
|
<table v-if="dayColumns.length" class="tcal-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="jc-col">节次</th>
|
||||||
|
<th
|
||||||
|
v-for="d in dayColumns"
|
||||||
|
:key="d.dateStr"
|
||||||
|
:class="{ weekend: d.weekend }"
|
||||||
|
>
|
||||||
|
<span class="day-label">{{ d.weekLabel }}</span>
|
||||||
|
<span class="day-date">{{ d.dateStr.slice(5) }}</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="jc in JC_LIST" :key="'jc-' + jc">
|
||||||
|
<td class="jc-col">
|
||||||
|
第{{ jc }}节<span class="slot-hint">{{ slotGroup(jc) }}</span>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
v-for="d in dayColumns"
|
||||||
|
:key="d.dateStr + '#' + jc"
|
||||||
|
class="tcal-cell"
|
||||||
|
:class="{ marked: isMarked(d.dateStr, jc), weekend: d.weekend }"
|
||||||
|
@click="toggleCell(d.dateStr, jc)"
|
||||||
|
>
|
||||||
|
<span v-if="isMarked(d.dateStr, jc)">不可排课</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<el-empty v-else description="请选择学期(学期需先维护开学/结束日期)" :image-size="60" />
|
||||||
|
</div>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<span class="pending-tip">
|
||||||
|
本次改动:新增/更新 {{ addCount }} 格,取消 {{ removeCount }} 格
|
||||||
|
</span>
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="saving" @click="handleSave">保存</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { listAllSemester, getSemester } from '@/api/teachBusiness/semester'
|
||||||
|
import {
|
||||||
|
listTeacherCalendarByJybh,
|
||||||
|
batchSaveTeacherCalendar,
|
||||||
|
batchDeleteTeacherCalendar
|
||||||
|
} from '@/api/teachOffice/teacherCalendar'
|
||||||
|
|
||||||
|
// 节次 1-12:与 实施_课程表.节次 / 教学场地历.节次 同一套编号,
|
||||||
|
// 冲突检查按 (教员 + 日期 + 节次) 精确比对,所以这里按单节次存储,不做 1-2 节合并。
|
||||||
|
const JC_LIST = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
||||||
|
const SLOT_GROUP = {
|
||||||
|
1: '上午', 2: '上午', 3: '上午', 4: '上午',
|
||||||
|
5: '下午', 6: '下午', 7: '下午', 8: '下午',
|
||||||
|
9: '晚上', 10: '晚上', 11: '夜间', 12: '夜间'
|
||||||
|
}
|
||||||
|
const WEEK_LABELS = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
|
||||||
|
|
||||||
|
function pad2(n) {
|
||||||
|
return n < 10 ? '0' + n : '' + n
|
||||||
|
}
|
||||||
|
|
||||||
|
function fmtDate(d) {
|
||||||
|
return d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate())
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'TeacherCalendarDialog',
|
||||||
|
props: {
|
||||||
|
visible: { type: Boolean, default: false },
|
||||||
|
// 教员行(教员表):需要 jybh / jyxm
|
||||||
|
teacher: { type: Object, default: null }
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
JC_LIST,
|
||||||
|
nd: '',
|
||||||
|
semesterOptions: [],
|
||||||
|
semesterLoading: false,
|
||||||
|
startDate: null,
|
||||||
|
endDate: null,
|
||||||
|
weeks: [],
|
||||||
|
weekIndex: 0,
|
||||||
|
// 'yyyy-MM-dd#jc' -> 记录编号(新增未保存的格值为 null)
|
||||||
|
marked: {},
|
||||||
|
// 载入时的原始快照:'yyyy-MM-dd#jc' -> 记录编号
|
||||||
|
original: {},
|
||||||
|
loading: false,
|
||||||
|
saving: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
dialogVisible: {
|
||||||
|
get() { return this.visible },
|
||||||
|
set(val) { this.$emit('update:visible', val) }
|
||||||
|
},
|
||||||
|
teacherTitle() {
|
||||||
|
const t = this.teacher || {}
|
||||||
|
return t.jyxm || t.jybh || '教员历'
|
||||||
|
},
|
||||||
|
jybh() {
|
||||||
|
const t = this.teacher || {}
|
||||||
|
return t.jybh || ''
|
||||||
|
},
|
||||||
|
dayColumns() {
|
||||||
|
const w = this.weeks[this.weekIndex]
|
||||||
|
if (!w || !w.days) return []
|
||||||
|
return w.days.map((d, i) => ({
|
||||||
|
dateStr: d,
|
||||||
|
weekLabel: WEEK_LABELS[i],
|
||||||
|
weekend: i >= 5
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
addCount() {
|
||||||
|
return Object.keys(this.marked).filter(k => !this.original[k]).length
|
||||||
|
},
|
||||||
|
removeCount() {
|
||||||
|
return Object.keys(this.original).filter(k => !(k in this.marked)).length
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(val) {
|
||||||
|
if (val) {
|
||||||
|
this.loadSemesters()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
jybh(val) {
|
||||||
|
if (val && this.visible) {
|
||||||
|
this.loadMarks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
slotGroup(jc) {
|
||||||
|
return SLOT_GROUP[jc] || ''
|
||||||
|
},
|
||||||
|
isMarked(dateStr, jc) {
|
||||||
|
return Object.prototype.hasOwnProperty.call(this.marked, dateStr + '#' + jc)
|
||||||
|
},
|
||||||
|
toggleCell(dateStr, jc) {
|
||||||
|
const key = dateStr + '#' + jc
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.marked, key)) {
|
||||||
|
this.$delete(this.marked, key)
|
||||||
|
} else {
|
||||||
|
this.$set(this.marked, key, null)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handlePrevWeek() {
|
||||||
|
if (this.weekIndex > 0) this.weekIndex -= 1
|
||||||
|
},
|
||||||
|
handleNextWeek() {
|
||||||
|
if (this.weekIndex < this.weeks.length - 1) this.weekIndex += 1
|
||||||
|
},
|
||||||
|
handleClearWeek() {
|
||||||
|
const cols = this.dayColumns
|
||||||
|
if (!cols.length) return
|
||||||
|
cols.forEach(d => {
|
||||||
|
this.JC_LIST.forEach(jc => {
|
||||||
|
const key = d.dateStr + '#' + jc
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.marked, key)) {
|
||||||
|
this.$delete(this.marked, key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.$message.info('已清除本周标记,记得点「保存」')
|
||||||
|
},
|
||||||
|
|
||||||
|
/* ---------- 学期与周次 ---------- */
|
||||||
|
loadSemesters() {
|
||||||
|
if (this.semesterOptions.length && this.nd) {
|
||||||
|
this.loadMarks()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.semesterLoading = true
|
||||||
|
listAllSemester().then(response => {
|
||||||
|
const list = Array.isArray(response.data) ? response.data : (response.data && response.data.records) || []
|
||||||
|
this.semesterOptions = list
|
||||||
|
if (!this.nd) {
|
||||||
|
const current = list.find(i => i.dqxq) || list[0]
|
||||||
|
if (current) this.nd = current.nd
|
||||||
|
}
|
||||||
|
return this.loadSemesterRange()
|
||||||
|
}).catch(() => {
|
||||||
|
this.semesterOptions = []
|
||||||
|
}).finally(() => {
|
||||||
|
this.semesterLoading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onSemesterChange() {
|
||||||
|
this.weekIndex = 0
|
||||||
|
return this.loadSemesterRange()
|
||||||
|
},
|
||||||
|
loadSemesterRange() {
|
||||||
|
if (!this.nd) return Promise.resolve()
|
||||||
|
return getSemester(this.nd).then(res => {
|
||||||
|
const data = res.data || res || {}
|
||||||
|
const kx = (data.kxrq || '').toString().slice(0, 10)
|
||||||
|
const jx = (data.jsrq || '').toString().slice(0, 10)
|
||||||
|
if (kx && jx) {
|
||||||
|
this.startDate = new Date(kx.replace(/-/g, '/'))
|
||||||
|
this.endDate = new Date(jx.replace(/-/g, '/'))
|
||||||
|
this.buildWeeks()
|
||||||
|
} else {
|
||||||
|
this.weeks = []
|
||||||
|
}
|
||||||
|
return this.loadMarks()
|
||||||
|
}).catch(() => {
|
||||||
|
this.weeks = []
|
||||||
|
this.$message.warning('学期详情获取失败,请确认该学期已维护开学/结束日期')
|
||||||
|
return this.loadMarks()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getMonday(date) {
|
||||||
|
const day = date.getDay()
|
||||||
|
const diff = date.getDate() - day + (day === 0 ? -6 : 1)
|
||||||
|
const d = new Date(date)
|
||||||
|
d.setDate(diff)
|
||||||
|
return d
|
||||||
|
},
|
||||||
|
buildWeeks() {
|
||||||
|
if (!this.startDate || !this.endDate) return
|
||||||
|
const start = this.getMonday(new Date(this.startDate))
|
||||||
|
const end = new Date(this.endDate)
|
||||||
|
const days = Math.round((end - start) / (24 * 3600 * 1000)) + 1
|
||||||
|
const totalWeeks = Math.max(1, Math.ceil(days / 7))
|
||||||
|
const weeks = []
|
||||||
|
for (let w = 0; w < totalWeeks; w++) {
|
||||||
|
const wkStart = new Date(start)
|
||||||
|
wkStart.setDate(start.getDate() + w * 7)
|
||||||
|
const wkEnd = new Date(wkStart)
|
||||||
|
wkEnd.setDate(wkStart.getDate() + 6)
|
||||||
|
const dayList = []
|
||||||
|
for (let i = 0; i < 7; i++) {
|
||||||
|
const d = new Date(wkStart)
|
||||||
|
d.setDate(wkStart.getDate() + i)
|
||||||
|
dayList.push(fmtDate(d))
|
||||||
|
}
|
||||||
|
weeks.push({
|
||||||
|
rangeText: pad2(wkStart.getMonth() + 1) + '-' + pad2(wkStart.getDate()) +
|
||||||
|
'至' + pad2(wkEnd.getMonth() + 1) + '-' + pad2(wkEnd.getDate()),
|
||||||
|
days: dayList
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.weeks = weeks
|
||||||
|
if (this.weekIndex > weeks.length - 1) this.weekIndex = 0
|
||||||
|
},
|
||||||
|
/** 学期代号 -> 学期名称(如 202601 -> 2026年春季学期) */
|
||||||
|
getSemesterName(nd) {
|
||||||
|
if (!nd) return ''
|
||||||
|
const s = String(nd)
|
||||||
|
const xn = s.slice(0, 4)
|
||||||
|
const xq = s.slice(4)
|
||||||
|
const map = { '01': '春季学期', '02': '夏季学期', '03': '秋季学期' }
|
||||||
|
return xn + '年' + (map[xq] || '第' + xq + '学期')
|
||||||
|
},
|
||||||
|
|
||||||
|
/* ---------- 教员历读取与保存 ---------- */
|
||||||
|
loadMarks() {
|
||||||
|
if (!this.jybh) {
|
||||||
|
this.marked = {}
|
||||||
|
this.original = {}
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
this.loading = true
|
||||||
|
return listTeacherCalendarByJybh(this.jybh).then(res => {
|
||||||
|
const list = Array.isArray(res.data) ? res.data : (res.data && res.data.records) || []
|
||||||
|
const marked = {}
|
||||||
|
const original = {}
|
||||||
|
list.forEach(row => {
|
||||||
|
if (!row || !row.rq || row.jc === null || row.jc === undefined) return
|
||||||
|
const key = String(row.rq).slice(0, 10) + '#' + row.jc
|
||||||
|
marked[key] = row.bh
|
||||||
|
original[key] = row.bh
|
||||||
|
})
|
||||||
|
this.marked = marked
|
||||||
|
this.original = original
|
||||||
|
}).catch(() => {
|
||||||
|
this.marked = {}
|
||||||
|
this.original = {}
|
||||||
|
}).finally(() => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleSave() {
|
||||||
|
if (!this.jybh) {
|
||||||
|
this.$message.warning('缺少教员编号,无法保存')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const rows = []
|
||||||
|
const del = []
|
||||||
|
Object.keys(this.marked).forEach(k => {
|
||||||
|
if (this.original[k]) return // 原有记录且未改动
|
||||||
|
const parts = k.split('#')
|
||||||
|
rows.push({
|
||||||
|
jybh: this.jybh,
|
||||||
|
// JYL.rq 是 LocalDateTime,Jackson 只认 ISO-8601,必须带 'T';
|
||||||
|
// 用 'yyyy-MM-dd HH:mm:ss'(场地历 JXCDL 是 java.util.Date 才吃这种)会 400。
|
||||||
|
rq: parts[0] + 'T00:00:00',
|
||||||
|
jc: Number(parts[1]),
|
||||||
|
kpk: 0, // 教员历的语义:标出的时段不可排课
|
||||||
|
mc: '不可排课',
|
||||||
|
bz: null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Object.keys(this.original).forEach(k => {
|
||||||
|
if (!(k in this.marked)) del.push(this.original[k])
|
||||||
|
})
|
||||||
|
if (!rows.length && !del.length) {
|
||||||
|
this.$message.info('没有改动需要保存')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.saving = true
|
||||||
|
const tasks = []
|
||||||
|
if (rows.length) tasks.push(batchSaveTeacherCalendar(rows))
|
||||||
|
if (del.length) tasks.push(batchDeleteTeacherCalendar(del))
|
||||||
|
Promise.all(tasks).then(() => {
|
||||||
|
this.$message.success('已保存:新增/更新 ' + rows.length + ' 格,取消 ' + del.length + ' 格')
|
||||||
|
return this.loadMarks()
|
||||||
|
}).catch(() => {
|
||||||
|
this.$message.error('教员历保存失败')
|
||||||
|
}).finally(() => {
|
||||||
|
this.saving = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dialog-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.toolbar-label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
.semester-select {
|
||||||
|
width: 180px;
|
||||||
|
}
|
||||||
|
.week-select {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
.hint {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.editor-wrap {
|
||||||
|
height: 68vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.tcal-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
.tcal-table th,
|
||||||
|
.tcal-table td {
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #303133;
|
||||||
|
height: 34px;
|
||||||
|
}
|
||||||
|
.tcal-table thead th {
|
||||||
|
background: #f5f7fa;
|
||||||
|
color: #606266;
|
||||||
|
font-weight: 500;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.jc-col {
|
||||||
|
width: 96px;
|
||||||
|
background: #fafafa;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
.slot-hint {
|
||||||
|
display: block;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
.day-date {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
.weekend {
|
||||||
|
background: #fdf6ec;
|
||||||
|
}
|
||||||
|
.tcal-cell {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.tcal-cell:hover {
|
||||||
|
background: #ecf5ff;
|
||||||
|
}
|
||||||
|
.tcal-cell.marked {
|
||||||
|
background: #fef0f0;
|
||||||
|
color: #f56c6c;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.pending-tip {
|
||||||
|
margin-right: auto;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -78,10 +78,11 @@
|
|||||||
@click="handleCreateAccount(row)">创建账号</el-button>
|
@click="handleCreateAccount(row)">创建账号</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" align="center" fixed="right" width="200">
|
<el-table-column label="操作" align="center" fixed="right" width="260">
|
||||||
<template slot-scope="{ row }">
|
<template slot-scope="{ row }">
|
||||||
<el-button type="text" size="small" icon="el-icon-view" @click="handleViewAttribute(row)">查看属性</el-button>
|
<el-button type="text" size="small" icon="el-icon-view" @click="handleViewAttribute(row)">查看属性</el-button>
|
||||||
<el-button type="text" size="small" icon="el-icon-edit" @click="handleEdit(row)">编辑</el-button>
|
<el-button type="text" size="small" icon="el-icon-edit" @click="handleEdit(row)">编辑</el-button>
|
||||||
|
<el-button type="text" size="small" icon="el-icon-date" @click="handleTeacherCalendar(row)">教员历</el-button>
|
||||||
<el-button type="text" size="small" class="danger-text" @click="handleDisable(row)">离职</el-button>
|
<el-button type="text" size="small" class="danger-text" @click="handleDisable(row)">离职</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -700,6 +701,12 @@
|
|||||||
|
|
||||||
<!-- 对齐结果清单弹窗 -->
|
<!-- 对齐结果清单弹窗 -->
|
||||||
<org-sync-result-dialog ref="alignResult" title="对齐结果" />
|
<org-sync-result-dialog ref="alignResult" title="对齐结果" />
|
||||||
|
|
||||||
|
<!-- 教员历编辑弹窗(标记不可排课时段,供排课窗口与冲突检查使用) -->
|
||||||
|
<teacher-calendar-dialog
|
||||||
|
:visible.sync="teacherCalendarVisible"
|
||||||
|
:teacher="teacherCalendarRow"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -723,10 +730,11 @@ import { getDicts } from '@/api/system/dict/data'
|
|||||||
import { listRole } from '@/api/system/role'
|
import { listRole } from '@/api/system/role'
|
||||||
import { saveAs } from 'file-saver'
|
import { saveAs } from 'file-saver'
|
||||||
import OrgSyncResultDialog from '@/components/OrgSyncResultDialog'
|
import OrgSyncResultDialog from '@/components/OrgSyncResultDialog'
|
||||||
|
import TeacherCalendarDialog from './components/TeacherCalendarDialog.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Teacher',
|
name: 'Teacher',
|
||||||
components: { OrgSyncResultDialog },
|
components: { OrgSyncResultDialog, TeacherCalendarDialog },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
// 查询条件(仅后端 JYBMapper.xml 支持的字段:jyxm 模糊 / jysdh 等值 / zc 模糊 / jylb 等值)
|
// 查询条件(仅后端 JYBMapper.xml 支持的字段:jyxm 模糊 / jysdh 等值 / zc 模糊 / jylb 等值)
|
||||||
@@ -790,6 +798,10 @@ export default {
|
|||||||
attributeData: {},
|
attributeData: {},
|
||||||
attributeForm: {},
|
attributeForm: {},
|
||||||
attributeLoading: false,
|
attributeLoading: false,
|
||||||
|
|
||||||
|
// 教员历(标记不可排课时段)
|
||||||
|
teacherCalendarVisible: false,
|
||||||
|
teacherCalendarRow: null,
|
||||||
attributeSaving: false,
|
attributeSaving: false,
|
||||||
attributeJybh: '',
|
attributeJybh: '',
|
||||||
// 字典选项
|
// 字典选项
|
||||||
@@ -1086,6 +1098,17 @@ export default {
|
|||||||
return payload
|
return payload
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ==================== 教员历 ====================
|
||||||
|
/** 打开该教员的教员历编辑弹窗(标记不可排课时段) */
|
||||||
|
handleTeacherCalendar(row) {
|
||||||
|
if (!row || !row.jybh) {
|
||||||
|
this.$message.warning('该教员缺少教员工号,无法维护教员历')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.teacherCalendarRow = row
|
||||||
|
this.teacherCalendarVisible = true
|
||||||
|
},
|
||||||
|
|
||||||
// ==================== 编辑 ====================
|
// ==================== 编辑 ====================
|
||||||
handleEdit(row) {
|
handleEdit(row) {
|
||||||
this.editDialogVisible = true
|
this.editDialogVisible = true
|
||||||
|
|||||||
Reference in New Issue
Block a user