forked from liweijie/education
feat: 提交课表冲突检查相关功能模块
This commit is contained in:
+284
@@ -0,0 +1,284 @@
|
|||||||
|
package com.roomroot.web.controller.jwgl;
|
||||||
|
|
||||||
|
import com.roomroot.jwgl.service.TimetableConflictService;
|
||||||
|
import com.roomroot.jwgl.unit.PageResult;
|
||||||
|
import com.roomroot.jwgl.unit.Result;
|
||||||
|
import com.roomroot.jwgl.unit.conflict.TimetableConflictDimension;
|
||||||
|
import com.roomroot.jwgl.vo.timetableconflict.TimetableConflictCardVO;
|
||||||
|
import com.roomroot.jwgl.vo.timetableconflict.TimetableConflictDetailVO;
|
||||||
|
import com.roomroot.jwgl.vo.timetableconflict.TimetableConflictSummaryVO;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 课表冲突检查控制器。
|
||||||
|
* <p>
|
||||||
|
* 【涉及数据库表总览】(每类冲突用到的具体表详见各方法内部注释):
|
||||||
|
* <pre>
|
||||||
|
* 核心表(必用):
|
||||||
|
* 实施_课程表 SSKCB —— 日期、节次、年度、删除状态、课程科目编号
|
||||||
|
* 实施_课程表_学员队 SSKCBXYD —— 课次与教学班次(学员队)的多对多关联
|
||||||
|
* 实施_课程表_辅助教员 SSKCBFZJY —— 课次与教员(主讲/辅讲)的多对多关联
|
||||||
|
* 实施_课程表_教室 SSKCBJS —— 课次与教室的多对多关联
|
||||||
|
* 翻译表(可选,用于中文名展示;即使不存在也不会阻断聚合结果):
|
||||||
|
* 学员队表 XYDB —— 班次中文名
|
||||||
|
* 教员表 / 教研室人员 JYB / JYSX —— 教员姓名
|
||||||
|
* 教室表 JSB —— 教室名称
|
||||||
|
* 课表 KB —— 课程名称、课程类型(必修/选修)
|
||||||
|
* </pre>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author management
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/timetable-conflict")
|
||||||
|
public class TimetableConflictController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 课表冲突检查业务服务。
|
||||||
|
* 内部维护 ConcurrentHashMap 按「年度_维度编码」为 key 缓存每次检查结果明细,
|
||||||
|
* 避免重复执行 GROUP BY+HAVING COUNT 聚合 SQL。
|
||||||
|
*/
|
||||||
|
@Resource
|
||||||
|
private TimetableConflictService timetableConflictService;
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// 接口 1:单维度冲突检查(原接口2,移除/summary后接口序号前移)
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行指定维度的冲突检查。
|
||||||
|
* <p>
|
||||||
|
* 对应页面每张卡片右下角「检查」按钮。根据前端传入的 dimensionCode,
|
||||||
|
* 分发到 Service → Mapper,执行对应的 GROUP BY + HAVING COUNT(DISTINCT) 聚合 SQL。
|
||||||
|
* 执行完成后:① 结果写入缓存 ② 可选写落库表 ③ 返回该卡片的最新状态。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 不同 dimensionCode 用到的数据库表:
|
||||||
|
* <ul>
|
||||||
|
* <li>TEAM_CONFLICT(教学班时间冲突):
|
||||||
|
* 实施_课程表 SSKCB + 实施_课程表_学员队 SSKCBXYD + 学员队表 XYDB + 课表 KB</li>
|
||||||
|
* <li>ELECTIVE_REQUIRED_CONFLICT(必修选修冲突):
|
||||||
|
* 实施_课程表 SSKCB + 实施_课程表_学员队 SSKCBXYD + 学员队表 XYDB + 课表 KB(课程类型字段)</li>
|
||||||
|
* <li>TEACHER_CONFLICT(教员时间冲突):
|
||||||
|
* 实施_课程表 SSKCB + 实施_课程表_辅助教员 SSKCBFZJY + 教员表 JYB/教研室人员 + 课表 KB</li>
|
||||||
|
* <li>CLASSROOM_CONFLICT(教室时间冲突):
|
||||||
|
* 实施_课程表 SSKCB + 实施_课程表_教室 SSKCBJS + 教室表 JSB + 课表 KB</li>
|
||||||
|
* <li>GUARANTEE_CONFLICT(保障资源冲突,预留):
|
||||||
|
* 课程保障明细表 KCBBZMX + 保障类别 BZLB,当前返回空不报错</li>
|
||||||
|
* <li>EVENT_CONFLICT(活动事件冲突,预留):
|
||||||
|
* 学期校历 XQ + 学员队期班历 XYJXQJB,当前返回空不报错</li>
|
||||||
|
* </ul>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param body 请求体 JSON,结构:
|
||||||
|
* {
|
||||||
|
* "nd": 2026, // 年度,必需
|
||||||
|
* "dimensionCode": "TEAM_CONFLICT", // 维度编码(见枚举 TimetableConflictDimension.code)
|
||||||
|
* "writeToDb": false, // 【可选】是否同时写入"课表_冲突检查结果"表,默认 false
|
||||||
|
* "jcpch": null // 【可选】批次号(writeToDb=true 时,不传则自动生成一个)
|
||||||
|
* }
|
||||||
|
* @return 单卡片检查结果 VO
|
||||||
|
*/
|
||||||
|
@PostMapping("/check")
|
||||||
|
public Result<TimetableConflictCardVO> checkDimension(@RequestBody Map<String, Object> body) {
|
||||||
|
// 1. 提取并校验参数
|
||||||
|
Object ndObj = body.get("nd");
|
||||||
|
if (ndObj == null) {
|
||||||
|
return Result.badRequest("年度(nd)不能为空");
|
||||||
|
}
|
||||||
|
Integer nd = Integer.parseInt(String.valueOf(ndObj));
|
||||||
|
|
||||||
|
String code = (String) body.get("dimensionCode");
|
||||||
|
TimetableConflictDimension dim = TimetableConflictDimension.of(code);
|
||||||
|
if (dim == null) {
|
||||||
|
return Result.badRequest("无效的冲突检查维度编码:dimensionCode=" + code);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 新参数:是否落库 / 指定批次号
|
||||||
|
boolean writeToDb = Boolean.TRUE.equals(body.get("writeToDb"));
|
||||||
|
String jcpch = (String) body.get("jcpch");
|
||||||
|
|
||||||
|
// 3. 执行业务层单维度检查(内存 + 可选落库)
|
||||||
|
TimetableConflictCardVO card = timetableConflictService.checkDimension(nd, dim, jcpch, writeToDb);
|
||||||
|
|
||||||
|
// 4. 组装提示信息(落库模式下把批次号带给前端,便于后续 /details 用 useDb 查询)
|
||||||
|
String msg;
|
||||||
|
if (writeToDb) {
|
||||||
|
// 单维度 check:jcpch 传了就用传的,没传就是 service 内部生成的一个——这里拿不到内部生成的,
|
||||||
|
// 只能提示"已落库";如果批次号对前端重要,建议前端调 /check-all 统一拿批次号。
|
||||||
|
msg = "【" + card.getTitle() + "】检查完成(已落库),发现 "
|
||||||
|
+ card.getConflictCount() + " 条冲突";
|
||||||
|
} else {
|
||||||
|
msg = "【" + card.getTitle() + "】检查完成,发现 "
|
||||||
|
+ card.getConflictCount() + " 条冲突";
|
||||||
|
}
|
||||||
|
return Result.success(msg, card);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// 接口 2:执行全部维度检查
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行所有维度的课表冲突检查。
|
||||||
|
* <p>
|
||||||
|
* 对应页面右上角「执行全部检查」按钮。内部依次对 6 个维度调用 {@link #checkDimension}:
|
||||||
|
* 教学班 → 必修选修 → 教员 → 教室 → 保障资源(占位) → 活动事件(占位)
|
||||||
|
* 所有维度 SQL 执行完后汇总返回。fullyChecked 会被标记为 true。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 方案 A 落库模式:当 writeToDb=true 时,内部自动生成一个统一批次号(jcpch,格式 {@code 年度_时间戳毫秒}),
|
||||||
|
* 6 个维度的所有冲突明细都会带上这个批次号一起写进【课表_冲突检查结果】表;
|
||||||
|
* 返回的 Result.message 会把本次批次号拼在提示文字里,前端可以保存并在后续
|
||||||
|
* {@code GET /details?useDb=true&jcpch=xxx} 中拉取该次检查的历史明细。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param body 请求体 JSON,结构:
|
||||||
|
* {
|
||||||
|
* "nd": 2026, // 年度,必需
|
||||||
|
* "writeToDb": false // 【可选】true=同时写入"课表_冲突检查结果"表(生成统一批次号),默认 false
|
||||||
|
* }
|
||||||
|
* @return 完整汇总 VO(6 张卡片 + 总冲突数 + fullyChecked=true)。
|
||||||
|
* 落库模式下 message 形如:"全维度检查完成(批次号:2026_20260821-153000-123),共发现 N 条冲突"
|
||||||
|
*/
|
||||||
|
@PostMapping("/check-all")
|
||||||
|
public Result<TimetableConflictSummaryVO> checkAll(@RequestBody Map<String, Object> body) {
|
||||||
|
Object ndObj = body.get("nd");
|
||||||
|
if (ndObj == null) {
|
||||||
|
return Result.badRequest("年度(nd)不能为空");
|
||||||
|
}
|
||||||
|
Integer nd = Integer.parseInt(String.valueOf(ndObj));
|
||||||
|
|
||||||
|
// 新参数:是否落库(=true 时 service 内部生成一个统一批次号,6 个维度共用)
|
||||||
|
boolean writeToDb = Boolean.TRUE.equals(body.get("writeToDb"));
|
||||||
|
|
||||||
|
TimetableConflictSummaryVO result = timetableConflictService.checkAll(nd, writeToDb);
|
||||||
|
|
||||||
|
// 组装返回 message,落库模式带上批次号(前端保存后可在 /details 用 useDb=true 查询)
|
||||||
|
String msg;
|
||||||
|
if (writeToDb) {
|
||||||
|
String batchNo = timetableConflictService.getLastBatchNo();
|
||||||
|
msg = "全维度检查完成(批次号:" + batchNo + "),共发现 "
|
||||||
|
+ result.getTotalConflictCount() + " 条冲突";
|
||||||
|
} else {
|
||||||
|
msg = "全维度检查完成,共发现 "
|
||||||
|
+ result.getTotalConflictCount() + " 条冲突";
|
||||||
|
}
|
||||||
|
return Result.success(msg, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// 接口 3:重置检查结果(清空缓存)
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置当前学期的课表冲突检查结果。
|
||||||
|
* <p>
|
||||||
|
* 对应页面「重置」按钮。默认行为:
|
||||||
|
* 清除 Service 层 ConcurrentHashMap 中所有以 "{nd}_" 开头的 key 的缓存记录,
|
||||||
|
* 所有卡片恢复 checked=false / conflictCount=0 的初始状态。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 可选行为(clearDb=true):同步 DELETE 【课表_冲突检查结果】表中该年度的全部批次记录,
|
||||||
|
* 用于"彻底清空历史检查痕迹"的场景(默认 false,保持历史,避免误删)。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param body 请求体 JSON,结构:
|
||||||
|
* {
|
||||||
|
* "nd": 2026, // 年度,必需
|
||||||
|
* "clearDb": false // 【可选】true=同时清空落库表中该年度所有批次,默认 false
|
||||||
|
* }
|
||||||
|
* @return 重置后的汇总 VO(所有卡片均为未检查状态)
|
||||||
|
*/
|
||||||
|
@PostMapping("/reset")
|
||||||
|
public Result<TimetableConflictSummaryVO> reset(@RequestBody Map<String, Object> body) {
|
||||||
|
Object ndObj = body.get("nd");
|
||||||
|
if (ndObj == null) {
|
||||||
|
return Result.badRequest("年度(nd)不能为空");
|
||||||
|
}
|
||||||
|
Integer nd = Integer.parseInt(String.valueOf(ndObj));
|
||||||
|
|
||||||
|
// 新参数:是否同时清落库表(默认只清内存,保留历史)
|
||||||
|
boolean clearDb = Boolean.TRUE.equals(body.get("clearDb"));
|
||||||
|
|
||||||
|
TimetableConflictSummaryVO result = timetableConflictService.reset(nd, clearDb);
|
||||||
|
String msg = clearDb
|
||||||
|
? "检查结果已重置(已同步清空落库表中该年度的所有历史批次)"
|
||||||
|
: "检查结果已重置(仅清内存缓存,落库历史记录保留)";
|
||||||
|
return Result.success(msg, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// 接口 4:分页查询冲突明细
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询冲突明细(页面下方"冲突明细"表格使用)。
|
||||||
|
* <p>
|
||||||
|
* <b>两种数据源(useDb 参数切换):</b>
|
||||||
|
* <ul>
|
||||||
|
* <li>useDb=false(默认,兼容旧前端):从 Service 内存缓存(ConcurrentHashMap)读。
|
||||||
|
* <b>必须先点击单卡片"检查"按钮或"执行全部检查"后,才能查到对应明细。</b>
|
||||||
|
* 若尚未检查 → 返回 records=[](前端显示"请先点击上方卡片「检查」按钮"的空状态)。</li>
|
||||||
|
* <li>useDb=true(方案 A 落库模式):从【课表_冲突检查结果】表读。
|
||||||
|
* 好处:JVM 重启不丢、可回看任意一次历史批次。
|
||||||
|
* 此时 jcpch 参数有意义:传具体批次号 → 只查那一次;不传 → 自动取该年度的最新批次。</li>
|
||||||
|
* </ul>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param nd 年度(学期过滤),必需
|
||||||
|
* @param dimensionCode 维度编码(可选)。传 null / "ALL" 表示合并展示所有维度的冲突;
|
||||||
|
* 也可传单一维度编码(如 "TEACHER_CONFLICT")只看教员冲突。
|
||||||
|
* @param pageNum 当前页码(从 1 开始,默认 1)
|
||||||
|
* @param pageSize 每页大小(默认 20 条)
|
||||||
|
* @param useDb 【可选】true=从落库表查;false=从内存缓存查(默认)
|
||||||
|
* @param jcpch 【可选,useDb=true 时生效】查哪一个检查批次号。
|
||||||
|
* 不传则自动取该年度在 DB 中的"最新批次"(MAX 创建时间)。
|
||||||
|
* @return 分页结果。每条明细包含:冲突号、课程、日期、节次、班次、教员、场地、责任单位
|
||||||
|
*/
|
||||||
|
@GetMapping("/details")
|
||||||
|
public Result<PageResult<TimetableConflictDetailVO>> getDetails(
|
||||||
|
@RequestParam(value = "nd") Integer nd,
|
||||||
|
@RequestParam(value = "dimensionCode", required = false) String dimensionCode,
|
||||||
|
@RequestParam(value = "pageNum", required = false) Integer pageNum,
|
||||||
|
@RequestParam(value = "pageSize", required = false) Integer pageSize,
|
||||||
|
@RequestParam(value = "useDb", required = false, defaultValue = "false") boolean useDb,
|
||||||
|
@RequestParam(value = "jcpch", required = false) String jcpch) {
|
||||||
|
|
||||||
|
PageResult<TimetableConflictDetailVO> page
|
||||||
|
= timetableConflictService.getDetailsPage(nd, dimensionCode, pageNum, pageSize, useDb, jcpch);
|
||||||
|
String msg = useDb
|
||||||
|
? "查询成功(数据源:落库表" + (jcpch != null && !jcpch.isEmpty() ? ",批次号=" + jcpch : ",最新批次") + ")"
|
||||||
|
: "查询成功(数据源:内存缓存)";
|
||||||
|
return Result.success(msg, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// 接口 5:查询指定年度的检查批次号列表(落库模式辅助接口)
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定年度的全部检查批次号(按创建时间倒序,最新排第一)。
|
||||||
|
* <p>
|
||||||
|
* 前端用途:"查看历史检查"下拉框,用户选择某次批次号后,
|
||||||
|
* 再调用 {@code GET /details?useDb=true&jcpch=xxx} 拉取那次的明细。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param nd 年度,必需
|
||||||
|
* @return 批次号字符串列表(空表示无历史)
|
||||||
|
*/
|
||||||
|
@GetMapping("/batch-nos")
|
||||||
|
public Result<java.util.List<String>> listBatchNos(
|
||||||
|
@RequestParam(value = "nd") Integer nd) {
|
||||||
|
java.util.List<String> list = timetableConflictService.listBatchNos(nd);
|
||||||
|
return Result.success("查询成功,共 " + list.size() + " 个批次", list);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
restart.include.json=/com.alibaba.fastjson2.*.jar
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# 数据源配置
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
type: com.alibaba.druid.pool.DruidDataSource
|
||||||
|
# 达梦连接串:ip:端口,schema指定默认模式
|
||||||
|
url: jdbc:dm://10.1.1.17:5236?schema=JIAOWU&useUnicode=true&characterEncoding=utf-8
|
||||||
|
username: JIAOWU
|
||||||
|
password: Jiaowu123
|
||||||
|
driver-class-name: dm.jdbc.driver.DmDriver
|
||||||
|
|
||||||
|
# Druid 基础参数
|
||||||
|
druid:
|
||||||
|
# 初始连接数
|
||||||
|
initialSize: 5
|
||||||
|
# 最小连接池数量
|
||||||
|
minIdle: 10
|
||||||
|
# 最大连接池数量
|
||||||
|
maxActive: 20
|
||||||
|
# 配置获取连接等待超时的时间
|
||||||
|
maxWait: 60000
|
||||||
|
# 配置连接超时时间
|
||||||
|
connectTimeout: 30000
|
||||||
|
# 配置网络超时时间
|
||||||
|
socketTimeout: 60000
|
||||||
|
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
|
||||||
|
timeBetweenEvictionRunsMillis: 60000
|
||||||
|
# 配置一个连接在池中最小生存的时间,单位是毫秒
|
||||||
|
minEvictableIdleTimeMillis: 300000
|
||||||
|
# 配置一个连接在池中最大生存的时间,单位是毫秒
|
||||||
|
maxEvictableIdleTimeMillis: 900000
|
||||||
|
# 配置检测连接是否有效
|
||||||
|
validationQuery: SELECT 1 FROM DUAL
|
||||||
|
testWhileIdle: true
|
||||||
|
testOnBorrow: false
|
||||||
|
testOnReturn: false
|
||||||
|
webStatFilter:
|
||||||
|
enabled: true
|
||||||
|
statViewServlet:
|
||||||
|
enabled: true
|
||||||
|
# 设置白名单,不填则允许所有访问
|
||||||
|
allow:
|
||||||
|
url-pattern: /druid/*
|
||||||
|
# 控制台管理用户名和密码
|
||||||
|
login-username: roomroot
|
||||||
|
login-password: 123456
|
||||||
|
filter:
|
||||||
|
stat:
|
||||||
|
enabled: true
|
||||||
|
# 慢SQL记录
|
||||||
|
log-slow-sql: true
|
||||||
|
slow-sql-millis: 1000
|
||||||
|
merge-sql: true
|
||||||
|
wall:
|
||||||
|
config:
|
||||||
|
multi-statement-allow: true
|
||||||
|
db-type: dm
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
# 项目相关配置
|
||||||
|
roomroot:
|
||||||
|
# 名称
|
||||||
|
name: roomroot
|
||||||
|
# 版本
|
||||||
|
version: 3.9.2
|
||||||
|
# 版权年份
|
||||||
|
copyrightYear: 2026
|
||||||
|
# 文件路径 示例( Windows配置D:/roomroot/uploadPath,Linux配置 /home/roomroot/uploadPath)
|
||||||
|
profile: D:/roomroot/uploadPath
|
||||||
|
# 获取ip地址开关
|
||||||
|
addressEnabled: false
|
||||||
|
# 验证码类型 math 数字计算 char 字符验证
|
||||||
|
captchaType: math
|
||||||
|
|
||||||
|
# 开发环境配置
|
||||||
|
server:
|
||||||
|
# 服务器的HTTP端口,默认为8080
|
||||||
|
port: 8080
|
||||||
|
servlet:
|
||||||
|
# 应用的访问路径
|
||||||
|
context-path: /api
|
||||||
|
tomcat:
|
||||||
|
# tomcat的URI编码
|
||||||
|
uri-encoding: UTF-8
|
||||||
|
# 连接数满后的排队数,默认为100
|
||||||
|
accept-count: 1000
|
||||||
|
threads:
|
||||||
|
# tomcat最大线程数,默认为200
|
||||||
|
max: 800
|
||||||
|
# Tomcat启动初始化的线程数,默认值10
|
||||||
|
min-spare: 100
|
||||||
|
|
||||||
|
# 日志配置
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
com.roomroot: debug
|
||||||
|
#org.springframework: warn
|
||||||
|
|
||||||
|
# 用户配置
|
||||||
|
user:
|
||||||
|
password:
|
||||||
|
# 密码最大错误次数
|
||||||
|
maxRetryCount: 5
|
||||||
|
# 密码锁定时间(默认10分钟)
|
||||||
|
lockTime: 10
|
||||||
|
|
||||||
|
# Spring配置
|
||||||
|
spring:
|
||||||
|
# 资源信息
|
||||||
|
messages:
|
||||||
|
# 国际化资源文件路径
|
||||||
|
basename: i18n/messages
|
||||||
|
profiles:
|
||||||
|
active: druid
|
||||||
|
# 文件上传
|
||||||
|
servlet:
|
||||||
|
multipart:
|
||||||
|
# 单个文件大小
|
||||||
|
max-file-size: 10MB
|
||||||
|
# 设置总上传的文件大小
|
||||||
|
max-request-size: 20MB
|
||||||
|
jackson:
|
||||||
|
time-zone: GMT+8
|
||||||
|
date-format: yyyy-MM-dd HH:mm:ss
|
||||||
|
# 服务模块
|
||||||
|
devtools:
|
||||||
|
restart:
|
||||||
|
# 热部署开关
|
||||||
|
enabled: true
|
||||||
|
data:
|
||||||
|
# redis 配置
|
||||||
|
redis:
|
||||||
|
# 地址
|
||||||
|
host: localhost
|
||||||
|
# 端口,默认为6379
|
||||||
|
port: 6379
|
||||||
|
# 数据库索引
|
||||||
|
database: 0
|
||||||
|
# 密码
|
||||||
|
password:
|
||||||
|
# 连接超时时间
|
||||||
|
timeout: 10s
|
||||||
|
lettuce:
|
||||||
|
pool:
|
||||||
|
# 连接池中的最小空闲连接
|
||||||
|
min-idle: 0
|
||||||
|
# 连接池中的最大空闲连接
|
||||||
|
max-idle: 8
|
||||||
|
# 连接池的最大数据库连接数
|
||||||
|
max-active: 8
|
||||||
|
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||||
|
max-wait: -1ms
|
||||||
|
|
||||||
|
# token配置
|
||||||
|
token:
|
||||||
|
# 令牌自定义标识
|
||||||
|
header: Authorization
|
||||||
|
# 令牌密钥
|
||||||
|
secret: abcdefghijklmnopqrstuvwxyz
|
||||||
|
# 令牌有效期(默认30分钟)
|
||||||
|
expireTime: 30
|
||||||
|
|
||||||
|
# MyBatis-Plus配置
|
||||||
|
mybatis-plus:
|
||||||
|
# 搜索指定包别名
|
||||||
|
typeAliasesPackage: com.roomroot.**.domain
|
||||||
|
# 配置mapper的扫描,找到所有的mapper.xml映射文件
|
||||||
|
mapperLocations: classpath*:mapper/**/*Mapper.xml,classpath*:com/roomroot/**/mapper/*.xml
|
||||||
|
# 加载全局的配置文件
|
||||||
|
configLocation: classpath:mybatis/mybatis-config.xml
|
||||||
|
|
||||||
|
# Springdoc配置
|
||||||
|
springdoc:
|
||||||
|
api-docs:
|
||||||
|
path: /v3/api-docs
|
||||||
|
swagger-ui:
|
||||||
|
enabled: true
|
||||||
|
path: /swagger-ui.html
|
||||||
|
tags-sorter: alpha
|
||||||
|
group-configs:
|
||||||
|
- group: 'default'
|
||||||
|
display-name: '测试模块'
|
||||||
|
paths-to-match: '/**'
|
||||||
|
packages-to-scan: com.roomroot.web.controller.tool
|
||||||
|
|
||||||
|
# 防盗链配置
|
||||||
|
referer:
|
||||||
|
# 防盗链开关
|
||||||
|
enabled: false
|
||||||
|
# 允许的域名列表
|
||||||
|
allowed-domains: localhost,127.0.0.1,roomroot.vip,www.roomroot.vip
|
||||||
|
|
||||||
|
# 防止XSS攻击
|
||||||
|
xss:
|
||||||
|
# 过滤开关
|
||||||
|
enabled: true
|
||||||
|
# 排除链接(多个用逗号分隔)
|
||||||
|
excludes: /system/notice
|
||||||
|
# 匹配链接
|
||||||
|
urlPatterns: /system/*,/monitor/*,/tool/*
|
||||||
|
|
||||||
|
# 自定义内嵌Redis开关
|
||||||
|
custom:
|
||||||
|
embedded-redis:
|
||||||
|
# dev环境true自动启动内嵌Redis;prod改为false使用外部Redis
|
||||||
|
enable: false
|
||||||
|
port: 6379
|
||||||
|
max-memory: 128M
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Application Version: ${roomroot.version}
|
||||||
|
Spring Boot Version: ${spring-boot.version}
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// 启动开始 永不宕机 永无BUG //
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,38 @@
|
|||||||
|
#错误消息
|
||||||
|
not.null=* 必须填写
|
||||||
|
user.jcaptcha.error=验证码错误
|
||||||
|
user.jcaptcha.expire=验证码已失效
|
||||||
|
user.not.exists=用户不存在/密码错误
|
||||||
|
user.password.not.match=用户不存在/密码错误
|
||||||
|
user.password.retry.limit.count=密码输入错误{0}次
|
||||||
|
user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
|
||||||
|
user.password.delete=对不起,您的账号已被删除
|
||||||
|
user.blocked=用户已封禁,请联系管理员
|
||||||
|
role.blocked=角色已封禁,请联系管理员
|
||||||
|
login.blocked=很遗憾,访问IP已被列入系统黑名单
|
||||||
|
user.logout.success=退出成功
|
||||||
|
|
||||||
|
length.not.valid=长度必须在{min}到{max}个字符之间
|
||||||
|
|
||||||
|
user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
|
||||||
|
user.password.not.valid=* 5-50个字符
|
||||||
|
|
||||||
|
user.email.not.valid=邮箱格式错误
|
||||||
|
user.mobile.phone.number.not.valid=手机号格式错误
|
||||||
|
user.login.success=登录成功
|
||||||
|
user.register.success=注册成功
|
||||||
|
user.notfound=请重新登录
|
||||||
|
user.forcelogout=管理员强制退出,请重新登录
|
||||||
|
user.unknown.error=未知错误,请重新登录
|
||||||
|
|
||||||
|
##文件上传消息
|
||||||
|
upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
|
||||||
|
upload.filename.exceed.length=上传的文件名最长{0}个字符
|
||||||
|
|
||||||
|
##权限
|
||||||
|
no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
|
||||||
|
no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
|
||||||
|
no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
|
||||||
|
no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
|
||||||
|
no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
|
||||||
|
no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
<!-- 日志存放路径 -->
|
||||||
|
<property name="log.path" value="/home/roomroot/logs" />
|
||||||
|
<!-- 日志输出格式 -->
|
||||||
|
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
|
||||||
|
|
||||||
|
<!-- 控制台输出 -->
|
||||||
|
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>${log.pattern}</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 系统日志输出 -->
|
||||||
|
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${log.path}/sys-info.log</file>
|
||||||
|
<!-- 循环政策:基于时间创建日志文件 -->
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<!-- 日志文件名格式 -->
|
||||||
|
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||||
|
<!-- 日志最大的历史 60天 -->
|
||||||
|
<maxHistory>60</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>${log.pattern}</pattern>
|
||||||
|
</encoder>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
<!-- 过滤的级别 -->
|
||||||
|
<level>INFO</level>
|
||||||
|
<!-- 匹配时的操作:接收(记录) -->
|
||||||
|
<onMatch>ACCEPT</onMatch>
|
||||||
|
<!-- 不匹配时的操作:拒绝(不记录) -->
|
||||||
|
<onMismatch>DENY</onMismatch>
|
||||||
|
</filter>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${log.path}/sys-error.log</file>
|
||||||
|
<!-- 循环政策:基于时间创建日志文件 -->
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<!-- 日志文件名格式 -->
|
||||||
|
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||||
|
<!-- 日志最大的历史 60天 -->
|
||||||
|
<maxHistory>60</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>${log.pattern}</pattern>
|
||||||
|
</encoder>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
<!-- 过滤的级别 -->
|
||||||
|
<level>ERROR</level>
|
||||||
|
<!-- 匹配时的操作:接收(记录) -->
|
||||||
|
<onMatch>ACCEPT</onMatch>
|
||||||
|
<!-- 不匹配时的操作:拒绝(不记录) -->
|
||||||
|
<onMismatch>DENY</onMismatch>
|
||||||
|
</filter>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 用户访问日志输出 -->
|
||||||
|
<appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${log.path}/sys-user.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<!-- 按天回滚 daily -->
|
||||||
|
<fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||||
|
<!-- 日志最大的历史 60天 -->
|
||||||
|
<maxHistory>60</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>${log.pattern}</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 系统模块日志级别控制 -->
|
||||||
|
<logger name="com.roomroot" level="info" />
|
||||||
|
<!-- Spring日志级别控制 -->
|
||||||
|
<logger name="org.springframework" level="warn" />
|
||||||
|
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="console" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
<!--系统操作日志-->
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="file_info" />
|
||||||
|
<appender-ref ref="file_error" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
<!--系统用户操作日志-->
|
||||||
|
<logger name="sys-user" level="info">
|
||||||
|
<appender-ref ref="sys-user"/>
|
||||||
|
</logger>
|
||||||
|
</configuration>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE configuration
|
||||||
|
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-config.dtd">
|
||||||
|
<configuration>
|
||||||
|
<!-- 全局参数 -->
|
||||||
|
<settings>
|
||||||
|
<!-- 使全局的映射器启用或禁用缓存 -->
|
||||||
|
<setting name="cacheEnabled" value="true" />
|
||||||
|
<!-- 允许JDBC 支持自动生成主键 -->
|
||||||
|
<setting name="useGeneratedKeys" value="true" />
|
||||||
|
<!-- 配置默认的执行器.SIMPLE就是普通执行器;REUSE执行器会重用预处理语句(prepared statements);BATCH执行器将重用语句并执行批量更新 -->
|
||||||
|
<setting name="defaultExecutorType" value="SIMPLE" />
|
||||||
|
<!-- 指定 MyBatis 所用日志的具体实现 -->
|
||||||
|
<setting name="logImpl" value="SLF4J" />
|
||||||
|
<!-- 使用驼峰命名法转换字段 -->
|
||||||
|
<!-- <setting name="mapUnderscoreToCamelCase" value="true"/> -->
|
||||||
|
</settings>
|
||||||
|
|
||||||
|
</configuration>
|
||||||
+167
@@ -0,0 +1,167 @@
|
|||||||
|
package com.roomroot.jwgl.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 课表冲突检查结果实体。
|
||||||
|
* <p>
|
||||||
|
* 对应数据库表:课表_冲突检查结果(方案 A 落库表)。
|
||||||
|
* 每条记录 = 一次检查中的一条冲突明细,与 TimetableConflictDetailVO 字段一一对应,
|
||||||
|
* 额外加:年度、检查批次号、编号主键、创建时间 4 个落库字段。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 命名风格与项目其他实体保持一致:
|
||||||
|
* 属性=拼音/缩写,列名=中文,显式 @TableField 映射
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author management
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("课表_冲突检查结果")
|
||||||
|
public class TimetableConflictResult {
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// 落库扩展字段
|
||||||
|
// =========================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键编号(BIGINT 自增,对应表"编号"列,IDENTITY(1,1))
|
||||||
|
*/
|
||||||
|
@TableId(value = "编号", type = IdType.AUTO)
|
||||||
|
private Long bh;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 年度(学期过滤,例:2026)
|
||||||
|
*/
|
||||||
|
@TableField("年度")
|
||||||
|
private Integer nd;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查批次号(同一次"执行全部检查"共用,例:20260821-153000-123)
|
||||||
|
*/
|
||||||
|
@TableField("检查批次号")
|
||||||
|
private String jcpch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间(DEFAULT SYSDATE,MP 不做填充,数据库默认值即可)
|
||||||
|
*/
|
||||||
|
@TableField("创建时间")
|
||||||
|
private LocalDateTime cjsj;
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// 维度信息
|
||||||
|
// =========================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 维度编码,对应 TimetableConflictDimension.code
|
||||||
|
* 取值:TEAM_CONFLICT / ELECTIVE_REQUIRED_CONFLICT / TEACHER_CONFLICT / CLASSROOM_CONFLICT
|
||||||
|
*/
|
||||||
|
@TableField("维度编码")
|
||||||
|
private String wdbm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 维度标题(中文,例:教员时间冲突)
|
||||||
|
*/
|
||||||
|
@TableField("维度标题")
|
||||||
|
private String wdbt;
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// 冲突资源信息
|
||||||
|
// =========================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源编号(学员队编号/教员编号/教室编号)
|
||||||
|
*/
|
||||||
|
@TableField("资源编号")
|
||||||
|
private String zybh;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源中文名(例:张教授、101多媒体教室)
|
||||||
|
*/
|
||||||
|
@TableField("资源名称")
|
||||||
|
private String zymc;
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// 日期节次
|
||||||
|
// =========================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 冲突日期
|
||||||
|
*/
|
||||||
|
@TableField("日期")
|
||||||
|
private LocalDateTime rq;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 冲突节次(1-N)
|
||||||
|
*/
|
||||||
|
@TableField("节次")
|
||||||
|
private Integer jc;
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// 冲突课次信息
|
||||||
|
// =========================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 占用课次数(同一资源同时被排了几门课,一般 ≥2)
|
||||||
|
*/
|
||||||
|
@TableField("占用课次数")
|
||||||
|
private Integer zykcsl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 课程名称列表(WM_CONCAT 拼接,例:高等数学;大学英语)
|
||||||
|
*/
|
||||||
|
@TableField("课程名称列表")
|
||||||
|
private String kcmclb;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 冲突课次编号列表(= 实施_课程表.编号,逗号分隔,便于跳转到课次详情)
|
||||||
|
*/
|
||||||
|
@TableField("冲突课次编号列表")
|
||||||
|
private String ctkcbhlb;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 冲突原因中文整句(直接用于弹框展示)
|
||||||
|
*/
|
||||||
|
@TableField("冲突原因")
|
||||||
|
private String ctyy;
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// UI 明细表格 5 个业务字段(冲突号/班次/教员/场地/责任单位)
|
||||||
|
// =========================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 冲突号(规则:YYYYMMDD_节次2位_资源编号,例:20260910_03_XYD00001)
|
||||||
|
*/
|
||||||
|
@TableField("冲突号")
|
||||||
|
private String cth;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班次名称列表(学员队名称,WM_CONCAT 去重拼接)
|
||||||
|
*/
|
||||||
|
@TableField("班次名称列表")
|
||||||
|
private String bcmclb;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 教员姓名列表(含【主】【辅】标注)
|
||||||
|
*/
|
||||||
|
@TableField("教员姓名列表")
|
||||||
|
private String jyxmlb;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场地/教室名称列表
|
||||||
|
*/
|
||||||
|
@TableField("场地名称列表")
|
||||||
|
private String cdmclb;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 责任单位列表(班次的所属单位聚合)
|
||||||
|
*/
|
||||||
|
@TableField("责任单位列表")
|
||||||
|
private String zrdwlb;
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.roomroot.jwgl.mapper;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.roomroot.jwgl.entity.SSKCBFZJY;
|
import com.roomroot.jwgl.entity.SSKCBFZJY;
|
||||||
|
import com.roomroot.jwgl.vo.timetableconflict.TimetableConflictDetailVO;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
@@ -13,4 +14,12 @@ public interface SSKCBFZJYMapper extends BaseMapper<SSKCBFZJY> {
|
|||||||
List<SSKCBFZJY> selectBySskcbh(@Param("sskcbh") String sskcbh);
|
List<SSKCBFZJY> selectBySskcbh(@Param("sskcbh") String sskcbh);
|
||||||
|
|
||||||
int deleteBySskcbbh(@Param("sskcbbh") String sskcbbh);
|
int deleteBySskcbbh(@Param("sskcbbh") String sskcbbh);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【课表冲突检查】教员时间冲突明细查询。
|
||||||
|
*
|
||||||
|
* @param nd 年度
|
||||||
|
* @return 教员时间冲突明细列表
|
||||||
|
*/
|
||||||
|
List<TimetableConflictDetailVO> selectTeacherConflictDetails(@Param("nd") Integer nd);
|
||||||
}
|
}
|
||||||
@@ -3,11 +3,112 @@
|
|||||||
<mapper namespace="com.roomroot.jwgl.mapper.SSKCBFZJYMapper">
|
<mapper namespace="com.roomroot.jwgl.mapper.SSKCBFZJYMapper">
|
||||||
|
|
||||||
<select id="selectBySskcbh" resultType="com.roomroot.jwgl.entity.SSKCBFZJY">
|
<select id="selectBySskcbh" resultType="com.roomroot.jwgl.entity.SSKCBFZJY">
|
||||||
SELECT * FROM 实施_课程表_辅助教员 WHERE 实施_课程表编号 = #{sskcbh}
|
SELECT * FROM "实施_课程表_辅助教员" WHERE "实施_课程表编号" = #{sskcbh}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<delete id="deleteBySskcbbh">
|
<delete id="deleteBySskcbbh">
|
||||||
DELETE FROM 实施_课程表_辅助教员 WHERE 实施_课程表编号 = #{sskcbbh}
|
DELETE FROM "实施_课程表_辅助教员" WHERE "实施_课程表编号" = #{sskcbbh}
|
||||||
</delete>
|
</delete>
|
||||||
|
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<!-- 课表冲突检查 - 维度3:教员时间冲突明细查询 -->
|
||||||
|
<!-- 检查逻辑:同一教员(主讲+辅讲统一查)在同一日期节次,被安排多个课次 -->
|
||||||
|
<!-- 用到的表: -->
|
||||||
|
<!-- 1. 实施_课程表 SSKCB → 日期/节次/年度/删除状态 -->
|
||||||
|
<!-- 2. 实施_课程表_辅助教员 SSKCBFZJY → 教员编号(FZJYBH) -->
|
||||||
|
<!-- 3. 教员表/教研室人员表 JYB/JYSX → 翻译教员姓名 -->
|
||||||
|
<!-- 4. 课表 KB → 翻译课程名称 -->
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<select id="selectTeacherConflictDetails"
|
||||||
|
resultType="com.roomroot.jwgl.vo.timetableconflict.TimetableConflictDetailVO">
|
||||||
|
SELECT
|
||||||
|
/* 固定维度编码:教员时间冲突 */
|
||||||
|
'TEACHER_CONFLICT' AS dimension_code,
|
||||||
|
'教员时间冲突' AS dimension_title,
|
||||||
|
/* 发生冲突的资源(教员)*/
|
||||||
|
g."辅助教员编号" AS resource_id,
|
||||||
|
COALESCE(j."教员姓名", g."辅助教员编号") AS resource_name,
|
||||||
|
/* 冲突日期节次:统一取自主表 s */
|
||||||
|
g."日期" AS rq,
|
||||||
|
g."节次" AS jc,
|
||||||
|
/* 占用课次数:同一教员同一时间排了几门不同的课 */
|
||||||
|
g."占用课次数" AS occupied_lesson_count,
|
||||||
|
/* 冲突课程名 + 主讲/辅讲标注 */
|
||||||
|
g."课程名称列表" AS course_names,
|
||||||
|
/* 冲突课次编号列表,逗号分隔 */
|
||||||
|
g."课次编号列表" AS conflict_sskcb_bhs,
|
||||||
|
/* 中文原因 */
|
||||||
|
'教员【' || COALESCE(j."教员姓名", g."辅助教员编号") || '】在 '
|
||||||
|
|| TO_CHAR(g."日期", 'YYYY-MM-DD') || ' 第' || CAST(g."节次" AS VARCHAR) || '节'
|
||||||
|
|| ' 被同时安排 ' || CAST(g."占用课次数" AS VARCHAR) || ' 门课程:'
|
||||||
|
|| COALESCE(g."课程名称列表", '(课次号:' || g."课次编号列表" || ')')
|
||||||
|
AS reason,
|
||||||
|
/* ========== UI 明细新增 5 字段 ========== */
|
||||||
|
/* 冲突号:日期8位_节次2位_辅助教员编号 */
|
||||||
|
TO_CHAR(g."日期", 'YYYYMMDD')
|
||||||
|
|| '_' || LPAD(CAST(g."节次" AS VARCHAR), 2, '0')
|
||||||
|
|| '_' || g."辅助教员编号" AS conflict_no,
|
||||||
|
/* 班次名称列表(这些课次涉及的所有学员队/班次)*/
|
||||||
|
COALESCE(g.xyd_names_list, '(无)') AS xyd_names,
|
||||||
|
/* 教员姓名列表(含【主/辅】,该冲突课次涉及的所有教员)*/
|
||||||
|
COALESCE(g.teacher_names_list, '(无)') AS teacher_names,
|
||||||
|
/* 场地/教室名称列表 */
|
||||||
|
COALESCE(g.classroom_names_list, '(无)') AS classroom_names,
|
||||||
|
/* 责任单位(从班次所属单位聚合)*/
|
||||||
|
COALESCE(g.responsible_dept_list, '(无)')AS responsible_dept
|
||||||
|
FROM (
|
||||||
|
/*
|
||||||
|
* 内层:按教员编号+日期+节次 GROUP BY,筛选出现>1课次的冲突
|
||||||
|
*
|
||||||
|
* 关键修正:
|
||||||
|
* 1. 关联链路:SSKCB → 实施_课程 c → 课表 k;必须 CAST(c.编号 AS CHAR(36))
|
||||||
|
* 2. 主讲/辅讲字段:真实列名「主教员」(1主/0辅)
|
||||||
|
* 3. 日期节次:统一取主表 s
|
||||||
|
*
|
||||||
|
* 新增 JOIN(同维度1/2):为明细表格聚合"该冲突课次涉及的所有班次/教员/场地"
|
||||||
|
* - INNER JOIN f 是"维度过滤主关联"(只保留该教员的课次做分组)
|
||||||
|
* - 额外 LEFT JOIN xyd/fz/js 三层是"聚合展示用"(把同一冲突组涉及的所有信息都拼出来)
|
||||||
|
*/
|
||||||
|
SELECT
|
||||||
|
f."辅助教员编号",
|
||||||
|
s."日期",
|
||||||
|
s."节次",
|
||||||
|
COUNT(DISTINCT s."编号") AS "占用课次数",
|
||||||
|
/* 原课程名称列表(课程名 + 主/辅标注)*/
|
||||||
|
WM_CONCAT(DISTINCT
|
||||||
|
CASE f."主教员" WHEN 1 THEN '【主】' ELSE '【辅】' END
|
||||||
|
|| COALESCE(k."课名称", '未命名课程')
|
||||||
|
) AS "课程名称列表",
|
||||||
|
WM_CONCAT(DISTINCT s."编号") AS "课次编号列表",
|
||||||
|
/* ========== 新增 4 个聚合字段(明细表格用)========== */
|
||||||
|
WM_CONCAT(DISTINCT xyd_info."学员队名称") AS xyd_names_list,
|
||||||
|
WM_CONCAT(DISTINCT
|
||||||
|
CASE fz_info."主教员" WHEN 1 THEN '【主】' ELSE '【辅】' END
|
||||||
|
|| jy_info."教员姓名"
|
||||||
|
) AS teacher_names_list,
|
||||||
|
WM_CONCAT(DISTINCT js_info."教室名称") AS classroom_names_list,
|
||||||
|
WM_CONCAT(DISTINCT xyd_info."所属单位") AS responsible_dept_list
|
||||||
|
FROM "实施_课程表" s
|
||||||
|
/* 维度过滤主关联:只看该教员的课次(GROUP BY 维度)*/
|
||||||
|
INNER JOIN "实施_课程表_辅助教员" f ON f."实施_课程表编号" = s."编号"
|
||||||
|
/* 课表信息三层关联 */
|
||||||
|
LEFT JOIN "实施_课程" c ON CAST(c."编号" AS CHAR(36)) = s."实施_课程编号"
|
||||||
|
LEFT JOIN "课表" k ON k."科目代码" = c."科目编号"
|
||||||
|
/* ========== 新增 JOIN:聚合该冲突课次的班次/教员/场地所有信息 ========== */
|
||||||
|
LEFT JOIN "实施_课程表_学员队" xyd_link ON xyd_link."实施_课程表编号" = s."编号"
|
||||||
|
LEFT JOIN "学员队表" xyd_info ON xyd_info."学员队编号" = xyd_link."学员队编号"
|
||||||
|
LEFT JOIN "实施_课程表_辅助教员" fz_info ON fz_info."实施_课程表编号" = s."编号"
|
||||||
|
LEFT JOIN "教员表" jy_info ON jy_info."教员编号" = fz_info."辅助教员编号"
|
||||||
|
LEFT JOIN "实施_课程表_教室" js_link ON js_link."实施_课程表编号" = s."编号"
|
||||||
|
LEFT JOIN "教室表" js_info ON js_info."教室编号" = js_link."教室编号"
|
||||||
|
WHERE s."删除状态" = 0
|
||||||
|
AND s."年度" = #{nd}
|
||||||
|
GROUP BY f."辅助教员编号", s."日期", s."节次"
|
||||||
|
HAVING COUNT(DISTINCT s."编号") > 1
|
||||||
|
) g
|
||||||
|
/* 翻译教员姓名:教员表主键「教员编号」,姓名字段「教员姓名」 */
|
||||||
|
LEFT JOIN "教员表" j ON j."教员编号" = g."辅助教员编号"
|
||||||
|
ORDER BY g."日期", g."节次", g."辅助教员编号"
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -2,6 +2,7 @@ package com.roomroot.jwgl.mapper;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.roomroot.jwgl.entity.SSKCBJS;
|
import com.roomroot.jwgl.entity.SSKCBJS;
|
||||||
|
import com.roomroot.jwgl.vo.timetableconflict.TimetableConflictDetailVO;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
@@ -13,4 +14,12 @@ public interface SSKCBJSMapper extends BaseMapper<SSKCBJS> {
|
|||||||
List<SSKCBJS> selectBySskcbh(@Param("sskcbh") String sskcbh);
|
List<SSKCBJS> selectBySskcbh(@Param("sskcbh") String sskcbh);
|
||||||
|
|
||||||
int deleteBySskcbbh(@Param("sskcbbh") String sskcbbh);
|
int deleteBySskcbbh(@Param("sskcbbh") String sskcbbh);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【课表冲突检查】教室时间冲突明细查询。
|
||||||
|
*
|
||||||
|
* @param nd 年度
|
||||||
|
* @return 教室时间冲突明细列表
|
||||||
|
*/
|
||||||
|
List<TimetableConflictDetailVO> selectClassroomConflictDetails(@Param("nd") Integer nd);
|
||||||
}
|
}
|
||||||
@@ -3,11 +3,108 @@
|
|||||||
<mapper namespace="com.roomroot.jwgl.mapper.SSKCBJSMapper">
|
<mapper namespace="com.roomroot.jwgl.mapper.SSKCBJSMapper">
|
||||||
|
|
||||||
<select id="selectBySskcbh" resultType="com.roomroot.jwgl.entity.SSKCBJS">
|
<select id="selectBySskcbh" resultType="com.roomroot.jwgl.entity.SSKCBJS">
|
||||||
SELECT * FROM 实施_课程表_教室 WHERE 实施_课程表编号 = #{sskcbh}
|
SELECT * FROM "实施_课程表_教室" WHERE "实施_课程表编号" = #{sskcbh}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<delete id="deleteBySskcbbh">
|
<delete id="deleteBySskcbbh">
|
||||||
DELETE FROM 实施_课程表_教室 WHERE 实施_课程表编号 = #{sskcbbh}
|
DELETE FROM "实施_课程表_教室" WHERE "实施_课程表编号" = #{sskcbbh}
|
||||||
</delete>
|
</delete>
|
||||||
|
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<!-- 课表冲突检查 - 维度4:教室(教学场地)时间冲突明细查询 -->
|
||||||
|
<!-- 检查逻辑:同一教室同一日期节次,被安排多个不同课次 -->
|
||||||
|
<!-- 用到的表: -->
|
||||||
|
<!-- 1. 实施_课程表 SSKCB → 日期/节次/年度/删除状态 -->
|
||||||
|
<!-- 2. 实施_课程表_教室 SSKCBJS → 教室编号(JSBH) -->
|
||||||
|
<!-- 3. 教室表 JSB → 翻译教室中文名 -->
|
||||||
|
<!-- 4. 课表 KB → 翻译课程名称 -->
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<select id="selectClassroomConflictDetails"
|
||||||
|
resultType="com.roomroot.jwgl.vo.timetableconflict.TimetableConflictDetailVO">
|
||||||
|
SELECT
|
||||||
|
/* 固定维度编码:教室时间冲突 */
|
||||||
|
'CLASSROOM_CONFLICT' AS dimension_code,
|
||||||
|
'教室时间冲突' AS dimension_title,
|
||||||
|
/* 发生冲突的资源(教室)*/
|
||||||
|
g."教室编号" AS resource_id,
|
||||||
|
/* 翻译教室名称(教室表主键「教室编号」,名称「教室名称」)*/
|
||||||
|
COALESCE(r."教室名称", g."教室编号") AS resource_name,
|
||||||
|
/* 冲突的日期节次:统一取自主表 s */
|
||||||
|
g."日期" AS rq,
|
||||||
|
g."节次" AS jc,
|
||||||
|
/* 同一教室同一时间占用课次数 */
|
||||||
|
g."占用课次数" AS occupied_lesson_count,
|
||||||
|
/* 冲突的课程名称列表 */
|
||||||
|
g."课程名称列表" AS course_names,
|
||||||
|
/* 冲突的课次编号列表 */
|
||||||
|
g."课次编号列表" AS conflict_sskcb_bhs,
|
||||||
|
/* 中文原因 */
|
||||||
|
'教室【' || COALESCE(r."教室名称", g."教室编号") || '】在 '
|
||||||
|
|| TO_CHAR(g."日期", 'YYYY-MM-DD') || ' 第' || CAST(g."节次" AS VARCHAR) || '节'
|
||||||
|
|| ' 被同时安排 ' || CAST(g."占用课次数" AS VARCHAR) || ' 门课程:'
|
||||||
|
|| COALESCE(g."课程名称列表", '(课次号:' || g."课次编号列表" || ')')
|
||||||
|
AS reason,
|
||||||
|
/* ========== UI 明细新增 5 字段 ========== */
|
||||||
|
/* 冲突号:日期8位_节次2位_教室编号 */
|
||||||
|
TO_CHAR(g."日期", 'YYYYMMDD')
|
||||||
|
|| '_' || LPAD(CAST(g."节次" AS VARCHAR), 2, '0')
|
||||||
|
|| '_' || g."教室编号" AS conflict_no,
|
||||||
|
/* 班次名称列表(这些课次涉及的所有学员队/班次)*/
|
||||||
|
COALESCE(g.xyd_names_list, '(无)') AS xyd_names,
|
||||||
|
/* 教员姓名列表(含【主/辅】标注)*/
|
||||||
|
COALESCE(g.teacher_names_list, '(无)') AS teacher_names,
|
||||||
|
/* 场地/教室名称(虽然维度是教室,这里仍统一返回所有场地名,便于合班情况)*/
|
||||||
|
COALESCE(g.classroom_names_list, '(无)') AS classroom_names,
|
||||||
|
/* 责任单位(班次所属单位聚合)*/
|
||||||
|
COALESCE(g.responsible_dept_list, '(无)')AS responsible_dept
|
||||||
|
FROM (
|
||||||
|
/*
|
||||||
|
* 内层:按教室编号+日期+节次 GROUP BY,筛选 COUNT(DISTINCT 课次) > 1
|
||||||
|
*
|
||||||
|
* 关键修正:
|
||||||
|
* 1. 三层关联 JOIN:SSKCB → 实施_课程 → 课表;CAST(ss.编号 AS CHAR(36))
|
||||||
|
* 2. 日期节次统一取主表 s
|
||||||
|
*
|
||||||
|
* 新增 JOIN(同其他维度):聚合该冲突课次的班次/教员/场地所有明细信息
|
||||||
|
* - INNER JOIN c 作为"维度过滤主关联"(只保留该教室的课次做分组)
|
||||||
|
* - xyd_link/fz_info/js_link 作为"聚合展示用"JOIN(WM_CONCAT DISTINCT)
|
||||||
|
*/
|
||||||
|
SELECT
|
||||||
|
c."教室编号",
|
||||||
|
s."日期",
|
||||||
|
s."节次",
|
||||||
|
COUNT(DISTINCT s."编号") AS "占用课次数",
|
||||||
|
WM_CONCAT(DISTINCT k."课名称") AS "课程名称列表",
|
||||||
|
WM_CONCAT(DISTINCT s."编号") AS "课次编号列表",
|
||||||
|
/* ========== 新增 4 个聚合字段(明细表格用)========== */
|
||||||
|
WM_CONCAT(DISTINCT xyd_info."学员队名称") AS xyd_names_list,
|
||||||
|
WM_CONCAT(DISTINCT
|
||||||
|
CASE fz_info."主教员" WHEN 1 THEN '【主】' ELSE '【辅】' END
|
||||||
|
|| jy_info."教员姓名"
|
||||||
|
) AS teacher_names_list,
|
||||||
|
WM_CONCAT(DISTINCT js_info."教室名称") AS classroom_names_list,
|
||||||
|
WM_CONCAT(DISTINCT xyd_info."所属单位") AS responsible_dept_list
|
||||||
|
FROM "实施_课程表" s
|
||||||
|
/* 维度过滤主关联:教室维度分组 */
|
||||||
|
INNER JOIN "实施_课程表_教室" c ON c."实施_课程表编号" = s."编号"
|
||||||
|
/* 课表信息三层关联(ss=实施_课程别名,避免与聚合JOIN的别名冲突)*/
|
||||||
|
LEFT JOIN "实施_课程" ss ON CAST(ss."编号" AS CHAR(36)) = s."实施_课程编号"
|
||||||
|
LEFT JOIN "课表" k ON k."科目代码" = ss."科目编号"
|
||||||
|
/* ========== 新增 JOIN:聚合该冲突课次的班次/教员/场地所有信息 ========== */
|
||||||
|
LEFT JOIN "实施_课程表_学员队" xyd_link ON xyd_link."实施_课程表编号" = s."编号"
|
||||||
|
LEFT JOIN "学员队表" xyd_info ON xyd_info."学员队编号" = xyd_link."学员队编号"
|
||||||
|
LEFT JOIN "实施_课程表_辅助教员" fz_info ON fz_info."实施_课程表编号" = s."编号"
|
||||||
|
LEFT JOIN "教员表" jy_info ON jy_info."教员编号" = fz_info."辅助教员编号"
|
||||||
|
LEFT JOIN "实施_课程表_教室" js_link ON js_link."实施_课程表编号" = s."编号"
|
||||||
|
LEFT JOIN "教室表" js_info ON js_info."教室编号" = js_link."教室编号"
|
||||||
|
WHERE s."删除状态" = 0
|
||||||
|
AND s."年度" = #{nd}
|
||||||
|
GROUP BY c."教室编号", s."日期", s."节次"
|
||||||
|
HAVING COUNT(DISTINCT s."编号") > 1
|
||||||
|
) g
|
||||||
|
/* 修正:教室表主键是「教室编号」,不是「编号」 */
|
||||||
|
LEFT JOIN "教室表" r ON r."教室编号" = g."教室编号"
|
||||||
|
ORDER BY g."日期", g."节次", g."教室编号"
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -3,6 +3,7 @@ package com.roomroot.jwgl.mapper;
|
|||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.roomroot.jwgl.entity.SSKCBXYD;
|
import com.roomroot.jwgl.entity.SSKCBXYD;
|
||||||
import com.roomroot.jwgl.vo.elective.ClassScheduleVO;
|
import com.roomroot.jwgl.vo.elective.ClassScheduleVO;
|
||||||
|
import com.roomroot.jwgl.vo.timetableconflict.TimetableConflictDetailVO;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
@@ -17,4 +18,20 @@ public interface SSKCBXYDMapper extends BaseMapper<SSKCBXYD> {
|
|||||||
|
|
||||||
/** 查询班次关联的课程表(联表) */
|
/** 查询班次关联的课程表(联表) */
|
||||||
List<ClassScheduleVO> selectScheduleByXydbh(@Param("xydbh") String xydbh, @Param("nd") Integer nd);
|
List<ClassScheduleVO> selectScheduleByXydbh(@Param("xydbh") String xydbh, @Param("nd") Integer nd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【课表冲突检查】教学班时间冲突明细查询。
|
||||||
|
*
|
||||||
|
* @param nd 年度(学期过滤)
|
||||||
|
* @return 教学班时间冲突明细列表
|
||||||
|
*/
|
||||||
|
List<TimetableConflictDetailVO> selectTeamConflictDetails(@Param("nd") Integer nd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【课表冲突检查】教学班必修与选修时间冲突明细查询。
|
||||||
|
*
|
||||||
|
* @param nd 年度
|
||||||
|
* @return 必修选修冲突明细列表
|
||||||
|
*/
|
||||||
|
List<TimetableConflictDetailVO> selectElectiveRequiredConflictDetails(@Param("nd") Integer nd);
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,13 @@
|
|||||||
k."课名称"
|
k."课名称"
|
||||||
FROM "实施_课程表" s
|
FROM "实施_课程表" s
|
||||||
JOIN "实施_课程表_学员队" t ON t."实施_课程表编号" = s."编号"
|
JOIN "实施_课程表_学员队" t ON t."实施_课程表编号" = s."编号"
|
||||||
LEFT JOIN "课表" k ON k."课编号" = s."课程科目编号"
|
<!--
|
||||||
|
修正:三层关联 SSKCB → 实施_课程 → 课表
|
||||||
|
- 类型转换:c."编号" 是 VARBINARY,s."实施_课程编号" 是 CHAR,必须 CAST 才能匹配上
|
||||||
|
- 科目关联:KB.科目代码 = SSKC.科目编号
|
||||||
|
-->
|
||||||
|
LEFT JOIN "实施_课程" c ON CAST(c."编号" AS CHAR(36)) = s."实施_课程编号"
|
||||||
|
LEFT JOIN "课表" k ON k."科目代码" = c."科目编号"
|
||||||
WHERE t."学员队编号" = #{xydbh}
|
WHERE t."学员队编号" = #{xydbh}
|
||||||
<if test="nd != null">
|
<if test="nd != null">
|
||||||
AND s."年度" = #{nd}
|
AND s."年度" = #{nd}
|
||||||
@@ -38,4 +44,187 @@
|
|||||||
ORDER BY s."日期", s."节次"
|
ORDER BY s."日期", s."节次"
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<!-- 课表冲突检查 - 维度1:教学班时间冲突明细查询 -->
|
||||||
|
<!-- 检查逻辑:同一班次 + 同一日期 + 同一节次,出现 >1 个不同课次 -->
|
||||||
|
<!-- 用到的表: -->
|
||||||
|
<!-- 1. 实施_课程表 SSKCB → 日期(RQ)/节次(JC)/年度/删除状态(SCZT=0正常) -->
|
||||||
|
<!-- 2. 实施_课程表_学员队 SSKCBXYD → 学员队编号(班次编号) -->
|
||||||
|
<!-- 3. 学员队表(或班次表) XYDB → 翻译班次中文名 -->
|
||||||
|
<!-- 4. 课表 KB → 翻译课程名称 -->
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<select id="selectTeamConflictDetails"
|
||||||
|
resultType="com.roomroot.jwgl.vo.timetableconflict.TimetableConflictDetailVO">
|
||||||
|
SELECT
|
||||||
|
/* 固定维度编码:教学班时间冲突 */
|
||||||
|
'TEAM_CONFLICT' AS dimension_code,
|
||||||
|
'教学班时间冲突' AS dimension_title,
|
||||||
|
/* 发生冲突的资源(班次)*/
|
||||||
|
g."学员队编号" AS resource_id,
|
||||||
|
COALESCE(x."学员队名称", g."学员队编号") AS resource_name,
|
||||||
|
/* 冲突日期节次(统一从主表 SSKCB(s) 取)*/
|
||||||
|
g."日期" AS rq,
|
||||||
|
g."节次" AS jc,
|
||||||
|
/* 占用课次数(同一班次同一时间有多少门课) */
|
||||||
|
g."占用课次数" AS occupied_lesson_count,
|
||||||
|
/* 冲突课程名称拼接 */
|
||||||
|
g."课程名称列表" AS course_names,
|
||||||
|
/* 冲突课次编号拼接(前端跳转用)*/
|
||||||
|
g."课次编号列表" AS conflict_sskcb_bhs,
|
||||||
|
/* 冲突原因说明 */
|
||||||
|
'教学班次【' || COALESCE(x."学员队名称", g."学员队编号") || '】在 '
|
||||||
|
|| TO_CHAR(g."日期", 'YYYY-MM-DD') || ' 第' || CAST(g."节次" AS VARCHAR) || '节'
|
||||||
|
|| ' 被同时安排 ' || CAST(g."占用课次数" AS VARCHAR) || ' 门课程:'
|
||||||
|
|| COALESCE(g."课程名称列表", '(课次号:' || g."课次编号列表" || ')')
|
||||||
|
AS reason,
|
||||||
|
/* =========================================================
|
||||||
|
* 以下为 UI 明细表格新增的 5 个字段(对应截图8列)
|
||||||
|
* 列别名统一用"下划线命名" → MyBatis resultType 自动转驼峰匹配 VO 属性
|
||||||
|
* ========================================================= */
|
||||||
|
/* 冲突号:日期8位_节次2位补0_资源编号(学员队编号)*/
|
||||||
|
TO_CHAR(g."日期", 'YYYYMMDD')
|
||||||
|
|| '_' || LPAD(CAST(g."节次" AS VARCHAR), 2, '0')
|
||||||
|
|| '_' || g."学员队编号" AS conflict_no,
|
||||||
|
/* 班次名称列表(该冲突涉及的所有学员队/班次) */
|
||||||
|
COALESCE(g.xyd_names_list, '(无)') AS xyd_names,
|
||||||
|
/* 教员姓名列表(含【主/辅】标注)*/
|
||||||
|
COALESCE(g.teacher_names_list, '(无)') AS teacher_names,
|
||||||
|
/* 场地/教室名称列表 */
|
||||||
|
COALESCE(g.classroom_names_list, '(无)') AS classroom_names,
|
||||||
|
/* 责任单位(班次的所属单位)*/
|
||||||
|
COALESCE(g.responsible_dept_list, '(无)')AS responsible_dept
|
||||||
|
FROM (
|
||||||
|
/*
|
||||||
|
* 内层子查询:按班次+日期+节次 GROUP BY,筛选 COUNT(DISTINCT 课次) > 1 的冲突组
|
||||||
|
*
|
||||||
|
* 新增:除原有课程信息 JOIN 外,再 LEFT JOIN 三类业务信息用于明细展示(均加 DISTINCT,避免 JOIN 膨胀影响聚合)
|
||||||
|
* ① 班次+责任单位:SSKCB 再次 JOIN SSKCBXYD + 学员队表(拿到所有涉及班次的名称和所属单位)
|
||||||
|
* ② 教员:SSKCB → SSKCBFZJY + 教员表(带【主】【辅】标注)
|
||||||
|
* ③ 场地/教室:SSKCB → SSKCBJS + 教室表
|
||||||
|
*/
|
||||||
|
SELECT
|
||||||
|
t."学员队编号",
|
||||||
|
s."日期",
|
||||||
|
s."节次",
|
||||||
|
COUNT(DISTINCT s."编号") AS "占用课次数",
|
||||||
|
/* 原有用字段:课程名、课次号 */
|
||||||
|
WM_CONCAT(DISTINCT k."课名称") AS "课程名称列表",
|
||||||
|
WM_CONCAT(DISTINCT s."编号") AS "课次编号列表",
|
||||||
|
/* ========== 新增 4 个聚合字段(对应 UI 明细 4 列)========== */
|
||||||
|
/* 1. 班次名称列表:一个课次可能多个班上,DISTINCT 去重再拼接 */
|
||||||
|
WM_CONCAT(DISTINCT xyd_info."学员队名称") AS xyd_names_list,
|
||||||
|
/* 2. 教员姓名列表:带【主】【辅】角色区分 */
|
||||||
|
WM_CONCAT(DISTINCT
|
||||||
|
CASE fz_info."主教员" WHEN 1 THEN '【主】' ELSE '【辅】' END
|
||||||
|
|| jy_info."教员姓名"
|
||||||
|
) AS teacher_names_list,
|
||||||
|
/* 3. 场地/教室名称列表 */
|
||||||
|
WM_CONCAT(DISTINCT js_info."教室名称") AS classroom_names_list,
|
||||||
|
/* 4. 责任单位:学员队表所属单位字段聚合(DISTINCT 去重)*/
|
||||||
|
WM_CONCAT(DISTINCT xyd_info."所属单位") AS responsible_dept_list
|
||||||
|
FROM "实施_课程表" s
|
||||||
|
/* 维度过滤用:主关联"教学班(学员队)" —— 教学班冲突就是按这个维度 GROUP BY */
|
||||||
|
INNER JOIN "实施_课程表_学员队" t ON t."实施_课程表编号" = s."编号"
|
||||||
|
/* 课表信息三层关联 */
|
||||||
|
LEFT JOIN "实施_课程" c ON CAST(c."编号" AS CHAR(36)) = s."实施_课程编号"
|
||||||
|
LEFT JOIN "课表" k ON k."科目代码" = c."科目编号"
|
||||||
|
/* ========== 新增 JOIN:聚合该冲突课次涉及的所有班次/教员/教室信息 ========== */
|
||||||
|
LEFT JOIN "实施_课程表_学员队" xyd_link ON xyd_link."实施_课程表编号" = s."编号"
|
||||||
|
LEFT JOIN "学员队表" xyd_info ON xyd_info."学员队编号" = xyd_link."学员队编号"
|
||||||
|
LEFT JOIN "实施_课程表_辅助教员" fz_info ON fz_info."实施_课程表编号" = s."编号"
|
||||||
|
LEFT JOIN "教员表" jy_info ON jy_info."教员编号" = fz_info."辅助教员编号"
|
||||||
|
LEFT JOIN "实施_课程表_教室" js_link ON js_link."实施_课程表编号" = s."编号"
|
||||||
|
LEFT JOIN "教室表" js_info ON js_info."教室编号" = js_link."教室编号"
|
||||||
|
WHERE s."删除状态" = 0
|
||||||
|
AND s."年度" = #{nd}
|
||||||
|
GROUP BY t."学员队编号", s."日期", s."节次"
|
||||||
|
HAVING COUNT(DISTINCT s."编号") > 1
|
||||||
|
) g
|
||||||
|
/* 翻译班次中文名(学员队表) */
|
||||||
|
LEFT JOIN "学员队表" x ON x."学员队编号" = g."学员队编号"
|
||||||
|
ORDER BY g."日期", g."节次", g."学员队编号"
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<!-- 课表冲突检查 - 维度2:教学班必修与选修时间冲突 -->
|
||||||
|
<!-- 检查逻辑:同一班次同一日期节次,同时出现"必修"和"选修"两类课程 -->
|
||||||
|
<!-- 用到的表: -->
|
||||||
|
<!-- 1. 实施_课程表 SSKCB → 日期/节次/年度/删除状态 -->
|
||||||
|
<!-- 2. 实施_课程表_学员队 SSKCBXYD → 班次编号 -->
|
||||||
|
<!-- 3. 课表 KB → 课程类型(必修/选修/考查) -->
|
||||||
|
<!-- 4. 学员队表 XYDB → 翻译班次中文名 -->
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<select id="selectElectiveRequiredConflictDetails"
|
||||||
|
resultType="com.roomroot.jwgl.vo.timetableconflict.TimetableConflictDetailVO">
|
||||||
|
SELECT
|
||||||
|
'ELECTIVE_REQUIRED_CONFLICT' AS dimension_code,
|
||||||
|
'教学班必修与选修时间冲突' AS dimension_title,
|
||||||
|
g."学员队编号" AS resource_id,
|
||||||
|
COALESCE(x."学员队名称", g."学员队编号") AS resource_name,
|
||||||
|
/* 日期节次统一取自主表 s */
|
||||||
|
g."日期" AS rq,
|
||||||
|
g."节次" AS jc,
|
||||||
|
(g."必修课次数" + g."选修课次数") AS occupied_lesson_count,
|
||||||
|
(g."必修课程名" || ';' || g."选修课程名") AS course_names,
|
||||||
|
(g."必修课次编号" || ',' || g."选修课次编号") AS conflict_sskcb_bhs,
|
||||||
|
'教学班次【' || COALESCE(x."学员队名称", g."学员队编号") || '】在 '
|
||||||
|
|| TO_CHAR(g."日期", 'YYYY-MM-DD') || ' 第' || CAST(g."节次" AS VARCHAR) || '节'
|
||||||
|
|| ' 同时安排了必修课:' || COALESCE(g."必修课程名", '(无)')
|
||||||
|
|| ' 与 选修课:' || COALESCE(g."选修课程名", '(无)')
|
||||||
|
AS reason,
|
||||||
|
/* ========== UI 明细新增 5 字段 ========== */
|
||||||
|
/* 冲突号:日期8位_节次2位_学员队编号 */
|
||||||
|
TO_CHAR(g."日期", 'YYYYMMDD')
|
||||||
|
|| '_' || LPAD(CAST(g."节次" AS VARCHAR), 2, '0')
|
||||||
|
|| '_' || g."学员队编号" AS conflict_no,
|
||||||
|
COALESCE(g.xyd_names_list, '(无)') AS xyd_names,
|
||||||
|
COALESCE(g.teacher_names_list, '(无)') AS teacher_names,
|
||||||
|
COALESCE(g.classroom_names_list, '(无)') AS classroom_names,
|
||||||
|
COALESCE(g.responsible_dept_list, '(无)')AS responsible_dept
|
||||||
|
FROM (
|
||||||
|
/*
|
||||||
|
* 按班次+日期+节次分组,分别统计"必修课"和"选修课"
|
||||||
|
* 新增 JOIN:同维度1,LEFT JOIN 班次/教员/教室三类信息(WM_CONCAT DISTINCT 聚合)
|
||||||
|
*/
|
||||||
|
SELECT
|
||||||
|
t."学员队编号",
|
||||||
|
s."日期",
|
||||||
|
s."节次",
|
||||||
|
COUNT(DISTINCT CASE WHEN k."课程类型" = '必修' THEN s."编号" END) AS "必修课次数",
|
||||||
|
COUNT(DISTINCT CASE WHEN k."课程类型" = '选修' THEN s."编号" END) AS "选修课次数",
|
||||||
|
WM_CONCAT(DISTINCT CASE WHEN k."课程类型" = '必修' THEN k."课名称" END) AS "必修课程名",
|
||||||
|
WM_CONCAT(DISTINCT CASE WHEN k."课程类型" = '选修' THEN k."课名称" END) AS "选修课程名",
|
||||||
|
WM_CONCAT(DISTINCT CASE WHEN k."课程类型" = '必修' THEN s."编号" END) AS "必修课次编号",
|
||||||
|
WM_CONCAT(DISTINCT CASE WHEN k."课程类型" = '选修' THEN s."编号" END) AS "选修课次编号",
|
||||||
|
/* ========== 新增 4 个聚合字段 ========== */
|
||||||
|
WM_CONCAT(DISTINCT xyd_info."学员队名称") AS xyd_names_list,
|
||||||
|
WM_CONCAT(DISTINCT
|
||||||
|
CASE fz_info."主教员" WHEN 1 THEN '【主】' ELSE '【辅】' END
|
||||||
|
|| jy_info."教员姓名"
|
||||||
|
) AS teacher_names_list,
|
||||||
|
WM_CONCAT(DISTINCT js_info."教室名称") AS classroom_names_list,
|
||||||
|
WM_CONCAT(DISTINCT xyd_info."所属单位") AS responsible_dept_list
|
||||||
|
FROM "实施_课程表" s
|
||||||
|
INNER JOIN "实施_课程表_学员队" t ON t."实施_课程表编号" = s."编号"
|
||||||
|
LEFT JOIN "实施_课程" c ON CAST(c."编号" AS CHAR(36)) = s."实施_课程编号"
|
||||||
|
LEFT JOIN "课表" k ON k."科目代码" = c."科目编号"
|
||||||
|
/* ========== 新增 JOIN:三类业务信息 ========== */
|
||||||
|
LEFT JOIN "实施_课程表_学员队" xyd_link ON xyd_link."实施_课程表编号" = s."编号"
|
||||||
|
LEFT JOIN "学员队表" xyd_info ON xyd_info."学员队编号" = xyd_link."学员队编号"
|
||||||
|
LEFT JOIN "实施_课程表_辅助教员" fz_info ON fz_info."实施_课程表编号" = s."编号"
|
||||||
|
LEFT JOIN "教员表" jy_info ON jy_info."教员编号" = fz_info."辅助教员编号"
|
||||||
|
LEFT JOIN "实施_课程表_教室" js_link ON js_link."实施_课程表编号" = s."编号"
|
||||||
|
LEFT JOIN "教室表" js_info ON js_info."教室编号" = js_link."教室编号"
|
||||||
|
WHERE s."删除状态" = 0
|
||||||
|
AND s."年度" = #{nd}
|
||||||
|
GROUP BY t."学员队编号", s."日期", s."节次"
|
||||||
|
/* 关键:只有必修 >0 并且 选修 >0 才算冲突 */
|
||||||
|
HAVING COUNT(DISTINCT CASE WHEN k."课程类型" = '必修' THEN s."编号" END) > 0
|
||||||
|
AND COUNT(DISTINCT CASE WHEN k."课程类型" = '选修' THEN s."编号" END) > 0
|
||||||
|
) g
|
||||||
|
/* 修正:学员队表主键是「学员队编号」,不是「编号」 */
|
||||||
|
LEFT JOIN "学员队表" x ON x."学员队编号" = g."学员队编号"
|
||||||
|
ORDER BY g."日期", g."节次"
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
+82
@@ -0,0 +1,82 @@
|
|||||||
|
package com.roomroot.jwgl.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.roomroot.jwgl.entity.TimetableConflictResult;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 课表冲突检查结果 Mapper。
|
||||||
|
* <p>
|
||||||
|
* 对应表:课表_冲突检查结果。
|
||||||
|
* 继承 MyBatis-Plus BaseMapper 自动获得 insert / selectList / delete 等 CRUD 方法;
|
||||||
|
* 额外保留 2 个便捷方法:按年度+批次号清理、按年度+维度编码查最新批次。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author management
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface TimetableConflictResultMapper extends BaseMapper<TimetableConflictResult> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按年度 + 检查批次号清理历史记录(DELETE,非逻辑删除)。
|
||||||
|
* <p>
|
||||||
|
* 等价于 MP 的 delete(new LambdaQueryWrapper<TimetableConflictResult>()
|
||||||
|
* .eq(TimetableConflictResult::getNd, nd)
|
||||||
|
* .eq(TimetableConflictResult::getJcpch, jcpch));
|
||||||
|
* 这里提供 XML 版本只是为了在"批处理前后先清旧批次"场景更直观,
|
||||||
|
* 不实现也行,Service 层用 MP wrapper 即可。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param nd 年度
|
||||||
|
* @param jcpch 检查批次号
|
||||||
|
* @return 删除条数
|
||||||
|
*/
|
||||||
|
int deleteByNdAndJcpch(@Param("nd") Integer nd, @Param("jcpch") String jcpch);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定年度的所有检查批次号(倒序,最新排第一)。
|
||||||
|
* <p>
|
||||||
|
* 给前端"查看某次检查历史"的下拉框用。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param nd 年度
|
||||||
|
* @return 批次号列表
|
||||||
|
*/
|
||||||
|
List<String> selectJcpchListByNd(@Param("nd") Integer nd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【默认走 MP BaseMapper】等价说明:
|
||||||
|
* <ul>
|
||||||
|
* <li>insert(entity) → 单条插入一条冲突结果</li>
|
||||||
|
* <li>insertBatch(entityList) → 需要 Service 层自己循环或用 MP IService 的 saveBatch,
|
||||||
|
* 此处 BaseMapper 没有批量方法。</li>
|
||||||
|
* <li>selectList(wrapper) → 按年度/维度/批次号查询明细列表</li>
|
||||||
|
* <li>delete(wrapper) → 按条件 delete(reset 场景清该年度所有)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* 另:以下 2 个方法只是接口声明,提供语义化 API;
|
||||||
|
* 若不准备写 TimetableConflictResultMapper.xml 实现,
|
||||||
|
* 调用方请直接使用 MP BaseMapper 默认方法即可(推荐,更省事)。
|
||||||
|
*/
|
||||||
|
default int deleteByNd(Integer nd) {
|
||||||
|
return delete(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TimetableConflictResult>()
|
||||||
|
.eq(TimetableConflictResult::getNd, nd));
|
||||||
|
}
|
||||||
|
|
||||||
|
default List<TimetableConflictResult> listByNdAndJcpch(Integer nd, String jcpch) {
|
||||||
|
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TimetableConflictResult> qw
|
||||||
|
= new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
|
||||||
|
qw.eq(TimetableConflictResult::getNd, nd);
|
||||||
|
if (jcpch != null && !jcpch.isEmpty()) {
|
||||||
|
qw.eq(TimetableConflictResult::getJcpch, jcpch);
|
||||||
|
}
|
||||||
|
qw.orderByAsc(TimetableConflictResult::getRq)
|
||||||
|
.orderByAsc(TimetableConflictResult::getJc)
|
||||||
|
.orderByAsc(TimetableConflictResult::getWdbm);
|
||||||
|
return selectList(qw);
|
||||||
|
}
|
||||||
|
}
|
||||||
+144
@@ -0,0 +1,144 @@
|
|||||||
|
package com.roomroot.jwgl.service;
|
||||||
|
|
||||||
|
import com.roomroot.jwgl.entity.TimetableConflictResult;
|
||||||
|
import com.roomroot.jwgl.unit.PageResult;
|
||||||
|
import com.roomroot.jwgl.unit.conflict.TimetableConflictDimension;
|
||||||
|
import com.roomroot.jwgl.vo.timetableconflict.TimetableConflictCardVO;
|
||||||
|
import com.roomroot.jwgl.vo.timetableconflict.TimetableConflictDetailVO;
|
||||||
|
import com.roomroot.jwgl.vo.timetableconflict.TimetableConflictSummaryVO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 课表冲突检查服务接口。
|
||||||
|
* <p>
|
||||||
|
* 现在支持两套"存储/读取"机制,调用方可按需选择:
|
||||||
|
* <ul>
|
||||||
|
* <li><b>内存模式(默认,兼容旧逻辑)</b>:检查结果只存在 ConcurrentHashMap,JVM 重启丢失。
|
||||||
|
* 方法签名不带 jcpch/useDb。</li>
|
||||||
|
* <li><b>落库模式(方案 A)</b>:检查结果同时写"课表_冲突检查结果"表,
|
||||||
|
* 分页可按 useDb=true / jcpch 指定某一批次从 DB 读。</li>
|
||||||
|
* </ul>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author management
|
||||||
|
*/
|
||||||
|
public interface TimetableConflictService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载课表冲突检查页面的初始化汇总(卡片列表)。
|
||||||
|
* <p>
|
||||||
|
* 用于 checkAll / reset 内部生成卡片结构;同时也可以返回"已落库历史检查结果回填"的状态,
|
||||||
|
* 若该年度该维度在 DB 有最新批次也会自动填充 checked=true。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
TimetableConflictSummaryVO loadSummary(Integer nd);
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// 检查相关(兼容旧签名 + 新签名)
|
||||||
|
// =========================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【旧签名,内存模式】单维度检查。
|
||||||
|
* 等价于 {@code checkDimension(nd, dimension, null, false)}。
|
||||||
|
*/
|
||||||
|
default TimetableConflictCardVO checkDimension(Integer nd, TimetableConflictDimension dimension) {
|
||||||
|
return checkDimension(nd, dimension, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【完整签名】单维度检查。
|
||||||
|
*
|
||||||
|
* @param nd 年度
|
||||||
|
* @param dimension 维度枚举
|
||||||
|
* @param jcpch 检查批次号(=null 则内部自动生成;checkAll 调用时所有维度传同一个批次号)
|
||||||
|
* @param writeToDb true=同时写入落库表"课表_冲突检查结果",false=只写内存缓存
|
||||||
|
* @return 卡片结果 VO(内部会保证 conflictCount / checked 的正确性)
|
||||||
|
*/
|
||||||
|
TimetableConflictCardVO checkDimension(Integer nd, TimetableConflictDimension dimension,
|
||||||
|
String jcpch, boolean writeToDb);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【旧签名,内存模式】全部维度检查。等价 checkAll(nd, false)。
|
||||||
|
*/
|
||||||
|
default TimetableConflictSummaryVO checkAll(Integer nd) {
|
||||||
|
return checkAll(nd, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【完整签名】全部维度检查。
|
||||||
|
*
|
||||||
|
* @param nd 年度
|
||||||
|
* @param writeToDb true=写落库表(自动生成统一批次号,所有维度共用;返回 summary 会带上最后生成的批次号),false=只写内存
|
||||||
|
* @return 汇总 VO
|
||||||
|
*/
|
||||||
|
TimetableConflictSummaryVO checkAll(Integer nd, boolean writeToDb);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回 checkAll(writeToDb=true) 生成的批次号(如果 writeToDb=false,返回 null)。
|
||||||
|
* 提供给 Controller 层把"本次检查批次号"放到 Result.message / data 扩展字段给前端用。
|
||||||
|
*/
|
||||||
|
String getLastBatchNo();
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// 重置
|
||||||
|
// =========================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【旧签名】重置:清内存 + 不清 DB。
|
||||||
|
*/
|
||||||
|
default TimetableConflictSummaryVO reset(Integer nd) {
|
||||||
|
return reset(nd, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置检查结果。
|
||||||
|
*
|
||||||
|
* @param nd 年度
|
||||||
|
* @param clearDb true=同时 DELETE 落库表里该年度的所有批次;false=只清内存缓存(保持历史)
|
||||||
|
*/
|
||||||
|
TimetableConflictSummaryVO reset(Integer nd, boolean clearDb);
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// 分页查询明细
|
||||||
|
// =========================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【旧签名,内存模式】分页查询明细。
|
||||||
|
* 等价于 {@code getDetailsPage(nd, dimensionCode, pageNum, pageSize, false, null)}。
|
||||||
|
*/
|
||||||
|
default PageResult<TimetableConflictDetailVO> getDetailsPage(Integer nd, String dimensionCode,
|
||||||
|
Integer pageNum, Integer pageSize) {
|
||||||
|
return getDetailsPage(nd, dimensionCode, pageNum, pageSize, false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【完整签名】分页查询明细。
|
||||||
|
*
|
||||||
|
* @param nd 年度
|
||||||
|
* @param dimensionCode 维度编码,null / "ALL"=合并全部维度
|
||||||
|
* @param pageNum 页码
|
||||||
|
* @param pageSize 每页大小
|
||||||
|
* @param useDb true=从"课表_冲突检查结果"表查;false=从内存缓存查(旧行为)
|
||||||
|
* @param jcpch useDb=true 时可选:具体查哪一个批次号;null 自动取该年度 DB 里"最新批次号"(MAX 创建时间对应批次号)
|
||||||
|
*/
|
||||||
|
PageResult<TimetableConflictDetailVO> getDetailsPage(Integer nd, String dimensionCode,
|
||||||
|
Integer pageNum, Integer pageSize,
|
||||||
|
boolean useDb, String jcpch);
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// 便捷方法:批次号列表查询、实体→VO 转换等
|
||||||
|
// =========================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定年度的所有检查批次号(倒序,最新批次排第一个)。
|
||||||
|
* 给前端下拉框"选择某次历史检查查看详情"。
|
||||||
|
*/
|
||||||
|
List<String> listBatchNos(Integer nd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把一条"课表_冲突检查结果"实体转成前端明细表格需要的 TimetableConflictDetailVO。
|
||||||
|
*/
|
||||||
|
TimetableConflictDetailVO toDetailVO(TimetableConflictResult entity);
|
||||||
|
}
|
||||||
|
|
||||||
+438
@@ -0,0 +1,438 @@
|
|||||||
|
package com.roomroot.jwgl.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.roomroot.jwgl.entity.TimetableConflictResult;
|
||||||
|
import com.roomroot.jwgl.mapper.SSKCBFZJYMapper;
|
||||||
|
import com.roomroot.jwgl.mapper.SSKCBJSMapper;
|
||||||
|
import com.roomroot.jwgl.mapper.SSKCBXYDMapper;
|
||||||
|
import com.roomroot.jwgl.mapper.TimetableConflictResultMapper;
|
||||||
|
import com.roomroot.jwgl.service.TimetableConflictService;
|
||||||
|
import com.roomroot.jwgl.unit.PageResult;
|
||||||
|
import com.roomroot.jwgl.unit.conflict.TimetableConflictDimension;
|
||||||
|
import com.roomroot.jwgl.vo.timetableconflict.TimetableConflictCardVO;
|
||||||
|
import com.roomroot.jwgl.vo.timetableconflict.TimetableConflictDetailVO;
|
||||||
|
import com.roomroot.jwgl.vo.timetableconflict.TimetableConflictSummaryVO;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 课表冲突检查服务实现类。
|
||||||
|
* <p>
|
||||||
|
* 现在支持:
|
||||||
|
* <ul>
|
||||||
|
* <li><b>内存模式(兼容旧前端)</b>:ConcurrentHashMap 缓存(默认行为,不改代码也能跑);</li>
|
||||||
|
* <li><b>落库模式(方案 A)</b>:检查结果写入【课表_冲突检查结果】表,分页可用 useDb=true 查询。</li>
|
||||||
|
* </ul>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author management
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class TimetableConflictServiceImpl implements TimetableConflictService {
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// 依赖注入
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SSKCBXYDMapper sskcbxydMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SSKCBFZJYMapper sskcbfzjyMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SSKCBJSMapper sskcbjsMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 方案 A 落库表 Mapper(继承 MP BaseMapper,开箱自带 insert / selectList / delete)。
|
||||||
|
*/
|
||||||
|
@Resource
|
||||||
|
private TimetableConflictResultMapper conflictResultMapper;
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// 内存缓存(不需要持久化到数据库,临时保存每次检查结果)
|
||||||
|
// Key 格式:"<年度>_<维度编码>" 例:"2026_TEACHER_CONFLICT"
|
||||||
|
// =====================================================================
|
||||||
|
private final ConcurrentHashMap<String, List<TimetableConflictDetailVO>> detailCache
|
||||||
|
= new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private String cacheKey(Integer nd, String dimensionCode) {
|
||||||
|
return nd + "_" + dimensionCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 最近一次 checkAll(writeToDb=true) 生成的批次号 */
|
||||||
|
private volatile String lastBatchNo;
|
||||||
|
|
||||||
|
private static final DateTimeFormatter BATCH_FMT
|
||||||
|
= DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss-SSS");
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// 工具:生成批次号 / VO ↔ Entity 转换
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
|
private String generateBatchNo(Integer nd) {
|
||||||
|
// 格式:{年度}_{yyyyMMdd-HHmmss-SSS},例:2026_20260821-153000-123
|
||||||
|
String ts = LocalDateTime.now().format(BATCH_FMT);
|
||||||
|
return nd + "_" + ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TimetableConflictResult toResultEntity(Integer nd, String jcpch,
|
||||||
|
TimetableConflictDetailVO vo) {
|
||||||
|
TimetableConflictResult e = new TimetableConflictResult();
|
||||||
|
e.setNd(nd);
|
||||||
|
e.setJcpch(jcpch);
|
||||||
|
e.setWdbm(vo.getDimensionCode());
|
||||||
|
e.setWdbt(vo.getDimensionTitle());
|
||||||
|
e.setZybh(vo.getResourceId());
|
||||||
|
e.setZymc(vo.getResourceName());
|
||||||
|
e.setRq(vo.getRq());
|
||||||
|
e.setJc(vo.getJc());
|
||||||
|
e.setZykcsl(vo.getOccupiedLessonCount());
|
||||||
|
e.setKcmclb(vo.getCourseNames());
|
||||||
|
e.setCtkcbhlb(vo.getConflictSskcbBhs());
|
||||||
|
e.setCtyy(vo.getReason());
|
||||||
|
e.setCth(vo.getConflictNo());
|
||||||
|
e.setBcmclb(vo.getXydNames());
|
||||||
|
e.setJyxmlb(vo.getTeacherNames());
|
||||||
|
e.setCdmclb(vo.getClassroomNames());
|
||||||
|
e.setZrdwlb(vo.getResponsibleDept());
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TimetableConflictDetailVO toDetailVO(TimetableConflictResult e) {
|
||||||
|
if (e == null) return null;
|
||||||
|
return TimetableConflictDetailVO.builder()
|
||||||
|
.dimensionCode(e.getWdbm())
|
||||||
|
.dimensionTitle(e.getWdbt())
|
||||||
|
.resourceId(e.getZybh())
|
||||||
|
.resourceName(e.getZymc())
|
||||||
|
.rq(e.getRq())
|
||||||
|
.jc(e.getJc())
|
||||||
|
.occupiedLessonCount(e.getZykcsl())
|
||||||
|
.courseNames(e.getKcmclb())
|
||||||
|
.conflictSskcbBhs(e.getCtkcbhlb())
|
||||||
|
.reason(e.getCtyy())
|
||||||
|
.conflictNo(e.getCth())
|
||||||
|
.xydNames(e.getBcmclb())
|
||||||
|
.teacherNames(e.getJyxmlb())
|
||||||
|
.classroomNames(e.getCdmclb())
|
||||||
|
.responsibleDept(e.getZrdwlb())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// 接口实现:初始化汇总
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TimetableConflictSummaryVO loadSummary(Integer nd) {
|
||||||
|
TimetableConflictSummaryVO summary = new TimetableConflictSummaryVO();
|
||||||
|
summary.setNd(nd);
|
||||||
|
|
||||||
|
for (TimetableConflictDimension dim : TimetableConflictDimension.values()) {
|
||||||
|
TimetableConflictCardVO card = TimetableConflictCardVO.builder()
|
||||||
|
.dimensionCode(dim.getCode())
|
||||||
|
.title(dim.getTitle())
|
||||||
|
.description(dim.getDescription())
|
||||||
|
.checked(false)
|
||||||
|
.conflictCount(0)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 1) 优先:内存缓存有就回填(最高优先级:刚点过检查的结果)
|
||||||
|
List<TimetableConflictDetailVO> cached = detailCache.get(cacheKey(nd, dim.getCode()));
|
||||||
|
if (cached != null) {
|
||||||
|
card.setChecked(true);
|
||||||
|
card.setConflictCount(cached.size());
|
||||||
|
} else {
|
||||||
|
// 2) 兜底:DB 里该年度最新批次有数据就回填(避免"重启完 JVM 卡片全变未检查")
|
||||||
|
Long dbCnt = countLatestInDb(nd, dim.getCode());
|
||||||
|
if (dbCnt != null && dbCnt > 0) {
|
||||||
|
card.setChecked(true);
|
||||||
|
card.setConflictCount(dbCnt.intValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
summary.getCards().add(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.recalculateTotal();
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DB 辅助:返回某年度某维度在"最新批次"中的冲突条数。
|
||||||
|
*/
|
||||||
|
private Long countLatestInDb(Integer nd, String wdbm) {
|
||||||
|
// 1. 查该年度在 DB 中"最新批次号"
|
||||||
|
List<String> batchNos = listBatchNos(nd);
|
||||||
|
if (batchNos.isEmpty()) return null;
|
||||||
|
String latest = batchNos.get(0);
|
||||||
|
|
||||||
|
// 2. 按 nd+jcpch+wdbm count
|
||||||
|
LambdaQueryWrapper<TimetableConflictResult> qw = new LambdaQueryWrapper<>();
|
||||||
|
qw.eq(TimetableConflictResult::getNd, nd)
|
||||||
|
.eq(TimetableConflictResult::getJcpch, latest)
|
||||||
|
.eq(TimetableConflictResult::getWdbm, wdbm);
|
||||||
|
try {
|
||||||
|
return conflictResultMapper.selectCount(qw);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn("[课表冲突检查] 回填DB历史冲突数失败(nd={},wdbm={}): {}", nd, wdbm, ex.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// 接口实现:单维度检查(完整签名 = 内存 + 可选落库)
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TimetableConflictCardVO checkDimension(Integer nd, TimetableConflictDimension dimension,
|
||||||
|
String jcpch, boolean writeToDb) {
|
||||||
|
if (dimension == null) {
|
||||||
|
throw new IllegalArgumentException("维度不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 跑聚合 SQL(和旧版完全一样,保证结果一致性)
|
||||||
|
List<TimetableConflictDetailVO> details;
|
||||||
|
long startMs = System.currentTimeMillis();
|
||||||
|
try {
|
||||||
|
details = switch (dimension) {
|
||||||
|
case TEAM_CONFLICT -> sskcbxydMapper.selectTeamConflictDetails(nd);
|
||||||
|
case ELECTIVE_REQUIRED_CONFLICT -> sskcbxydMapper.selectElectiveRequiredConflictDetails(nd);
|
||||||
|
case TEACHER_CONFLICT -> sskcbfzjyMapper.selectTeacherConflictDetails(nd);
|
||||||
|
case CLASSROOM_CONFLICT -> sskcbjsMapper.selectClassroomConflictDetails(nd);
|
||||||
|
case GUARANTEE_CONFLICT, EVENT_CONFLICT -> Collections.emptyList();
|
||||||
|
};
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[课表冲突检查][维度:{}][年度:{}] 执行SQL失败: {}",
|
||||||
|
dimension.getCode(), nd, e.getMessage(), e);
|
||||||
|
details = Collections.emptyList();
|
||||||
|
}
|
||||||
|
if (details == null) details = Collections.emptyList();
|
||||||
|
long costMs = System.currentTimeMillis() - startMs;
|
||||||
|
log.info("[课表冲突检查][维度:{}][年度:{}] SQL完成, {} 条冲突, 耗时 {} ms",
|
||||||
|
dimension.getCode(), nd, details.size(), costMs);
|
||||||
|
|
||||||
|
// 2. 写内存缓存(保持原逻辑,旧前端不动也能看)
|
||||||
|
detailCache.put(cacheKey(nd, dimension.getCode()), details);
|
||||||
|
|
||||||
|
// 3. 可选:写落库表(方案 A)
|
||||||
|
if (writeToDb) {
|
||||||
|
String batch = jcpch;
|
||||||
|
if (batch == null || batch.isEmpty()) {
|
||||||
|
batch = generateBatchNo(nd);
|
||||||
|
}
|
||||||
|
persistOneDimension(nd, batch, dimension.getCode(), details);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 返回卡片
|
||||||
|
return TimetableConflictCardVO.builder()
|
||||||
|
.dimensionCode(dimension.getCode())
|
||||||
|
.title(dimension.getTitle())
|
||||||
|
.description(dimension.getDescription())
|
||||||
|
.checked(true)
|
||||||
|
.conflictCount(details.size())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把一个维度的明细持久化到【课表_冲突检查结果】。
|
||||||
|
* 幂等策略:先 DELETE (nd, jcpch, wdbm) 再 INSERT 明细,保证同一批次重跑不重复。
|
||||||
|
*/
|
||||||
|
private void persistOneDimension(Integer nd, String jcpch, String wdbm,
|
||||||
|
List<TimetableConflictDetailVO> details) {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
// 1) 幂等清理
|
||||||
|
LambdaQueryWrapper<TimetableConflictResult> qw = new LambdaQueryWrapper<>();
|
||||||
|
qw.eq(TimetableConflictResult::getNd, nd)
|
||||||
|
.eq(TimetableConflictResult::getJcpch, jcpch)
|
||||||
|
.eq(TimetableConflictResult::getWdbm, wdbm);
|
||||||
|
conflictResultMapper.delete(qw);
|
||||||
|
|
||||||
|
// 2) 逐条 INSERT(一次检查一个维度最多几千条,直接循环够用;要更高性能改成 SqlSession Batch)
|
||||||
|
int ok = 0, fail = 0;
|
||||||
|
for (TimetableConflictDetailVO vo : details) {
|
||||||
|
try {
|
||||||
|
conflictResultMapper.insert(toResultEntity(nd, jcpch, vo));
|
||||||
|
ok++;
|
||||||
|
} catch (Exception e) {
|
||||||
|
fail++;
|
||||||
|
if (fail <= 3) {
|
||||||
|
log.warn("[课表冲突检查][落库] nd={},jcpch={},wdbm={},插入冲突号={}失败: {}",
|
||||||
|
nd, jcpch, wdbm, vo.getConflictNo(), e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info("[课表冲突检查][落库][维度:{}] nd={},jcpch={},成功{}条/失败{}条,耗时{}ms",
|
||||||
|
wdbm, nd, jcpch, ok, fail, (System.currentTimeMillis() - start));
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// 接口实现:全部维度检查(完整签名)
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TimetableConflictSummaryVO checkAll(Integer nd, boolean writeToDb) {
|
||||||
|
TimetableConflictSummaryVO summary = loadSummary(nd);
|
||||||
|
|
||||||
|
// 1) writeToDb 模式:统一生成一个批次号,6 个维度共用
|
||||||
|
String batchNo = null;
|
||||||
|
if (writeToDb) {
|
||||||
|
batchNo = generateBatchNo(nd);
|
||||||
|
lastBatchNo = batchNo;
|
||||||
|
log.info("[课表冲突检查][checkAll] nd={}, 生成批次号={}", nd, batchNo);
|
||||||
|
} else {
|
||||||
|
lastBatchNo = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) 按卡片顺序串行检查
|
||||||
|
for (int i = 0; i < summary.getCards().size(); i++) {
|
||||||
|
TimetableConflictCardVO old = summary.getCards().get(i);
|
||||||
|
TimetableConflictDimension dim = TimetableConflictDimension.of(old.getDimensionCode());
|
||||||
|
if (dim == null) continue;
|
||||||
|
TimetableConflictCardVO newCard = checkDimension(nd, dim, batchNo, writeToDb);
|
||||||
|
summary.getCards().set(i, newCard);
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.recalculateTotal();
|
||||||
|
summary.setFullyChecked(true);
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLastBatchNo() {
|
||||||
|
return lastBatchNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// 接口实现:重置
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TimetableConflictSummaryVO reset(Integer nd, boolean clearDb) {
|
||||||
|
// 1) 清内存缓存
|
||||||
|
Iterator<Map.Entry<String, List<TimetableConflictDetailVO>>> it
|
||||||
|
= detailCache.entrySet().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
Map.Entry<String, List<TimetableConflictDetailVO>> e = it.next();
|
||||||
|
if (e.getKey().startsWith(nd + "_")) {
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastBatchNo = null;
|
||||||
|
log.info("[课表冲突检查][年度:{}] 已重置内存缓存", nd);
|
||||||
|
|
||||||
|
// 2) 可选:清落库表(方案A 硬删除,不是逻辑删除——重置意为"不留任何检查痕迹")
|
||||||
|
if (clearDb) {
|
||||||
|
int del = conflictResultMapper.deleteByNd(nd);
|
||||||
|
log.info("[课表冲突检查][年度:{}] 已清空落库表记录 {} 条", nd, del);
|
||||||
|
}
|
||||||
|
return loadSummary(nd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// 接口实现:明细分页(支持 useDb)
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageResult<TimetableConflictDetailVO> getDetailsPage(Integer nd, String dimensionCode,
|
||||||
|
Integer pageNum, Integer pageSize,
|
||||||
|
boolean useDb, String jcpch) {
|
||||||
|
// 兜底分页参数
|
||||||
|
if (pageNum == null || pageNum < 1) pageNum = 1;
|
||||||
|
if (pageSize == null || pageSize < 1) pageSize = 20;
|
||||||
|
|
||||||
|
List<TimetableConflictDetailVO> all = useDb
|
||||||
|
? buildListFromDb(nd, dimensionCode, jcpch)
|
||||||
|
: buildListFromCache(nd, dimensionCode);
|
||||||
|
|
||||||
|
// 排序(不管缓存/DB,统一按 日期 → 节次 → 维度 排)
|
||||||
|
all.sort(Comparator
|
||||||
|
.comparing((TimetableConflictDetailVO v) -> v.getRq() == null ? "" : v.getRq().toString())
|
||||||
|
.thenComparing(v -> v.getJc() == null ? 0 : v.getJc())
|
||||||
|
.thenComparing(v -> v.getDimensionCode() == null ? "" : v.getDimensionCode()));
|
||||||
|
|
||||||
|
// 手动分页
|
||||||
|
long total = all.size();
|
||||||
|
int fromIndex = Math.min((pageNum - 1) * pageSize, (int) total);
|
||||||
|
int toIndex = Math.min(fromIndex + pageSize, (int) total);
|
||||||
|
List<TimetableConflictDetailVO> pageData = total == 0
|
||||||
|
? Collections.emptyList()
|
||||||
|
: all.subList(fromIndex, toIndex);
|
||||||
|
return new PageResult<>(pageData, total, pageNum, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TimetableConflictDetailVO> buildListFromCache(Integer nd, String dimensionCode) {
|
||||||
|
List<TimetableConflictDetailVO> all = new ArrayList<>();
|
||||||
|
for (TimetableConflictDimension dim : TimetableConflictDimension.values()) {
|
||||||
|
if (dimensionCode != null && !"ALL".equalsIgnoreCase(dimensionCode)
|
||||||
|
&& !dim.getCode().equalsIgnoreCase(dimensionCode)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List<TimetableConflictDetailVO> part = detailCache.get(cacheKey(nd, dim.getCode()));
|
||||||
|
if (part != null && !part.isEmpty()) all.addAll(part);
|
||||||
|
}
|
||||||
|
return all;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TimetableConflictDetailVO> buildListFromDb(Integer nd, String dimensionCode,
|
||||||
|
String jcpch) {
|
||||||
|
// 批次号没传 → 取该年度最新批次
|
||||||
|
String batch = jcpch;
|
||||||
|
if (batch == null || batch.isEmpty()) {
|
||||||
|
List<String> batches = listBatchNos(nd);
|
||||||
|
if (batches.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
batch = batches.get(0);
|
||||||
|
}
|
||||||
|
// 从 DB 拉该 nd+jcpch 的所有结果行
|
||||||
|
List<TimetableConflictResult> rows = conflictResultMapper.listByNdAndJcpch(nd, batch);
|
||||||
|
if (rows == null || rows.isEmpty()) return Collections.emptyList();
|
||||||
|
|
||||||
|
// 按 dimensionCode 过滤(null/ALL=保留全部),再实体→VO
|
||||||
|
return rows.stream()
|
||||||
|
.filter(r -> {
|
||||||
|
if (dimensionCode == null || "ALL".equalsIgnoreCase(dimensionCode)) return true;
|
||||||
|
return dimensionCode.equalsIgnoreCase(r.getWdbm());
|
||||||
|
})
|
||||||
|
.map(this::toDetailVO)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// 便捷方法:批次号列表(倒序,最新排第一)
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> listBatchNos(Integer nd) {
|
||||||
|
// 用 MP QueryWrapper:SELECT DISTINCT "检查批次号" FROM 表 WHERE "年度" = ? ORDER BY MAX("创建时间") DESC
|
||||||
|
QueryWrapper<TimetableConflictResult> qw = new QueryWrapper<>();
|
||||||
|
qw.select("检查批次号 AS jcpch")
|
||||||
|
.eq("年度", nd)
|
||||||
|
.groupBy("检查批次号")
|
||||||
|
.orderByDesc("MAX(创建时间)");
|
||||||
|
try {
|
||||||
|
List<Object> objs = conflictResultMapper.selectObjs(qw);
|
||||||
|
List<String> res = new ArrayList<>(objs.size());
|
||||||
|
for (Object o : objs) {
|
||||||
|
if (o != null) res.add(String.valueOf(o));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 表若不存在 / 列名报错 → 不影响主流程,返回空
|
||||||
|
log.warn("[课表冲突检查] 查询批次号列表异常(nd={}): {}", nd, e.getMessage());
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+149
@@ -0,0 +1,149 @@
|
|||||||
|
package com.roomroot.jwgl.unit.conflict;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 课表冲突检查维度枚举。
|
||||||
|
* <p>
|
||||||
|
* 对应页面"教学资源冲突检查"的6个卡片,每个枚举值代表一种检查维度。
|
||||||
|
* 用于 Controller → Service → Mapper 之间传递"检查哪一类"的标识。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 设计说明:
|
||||||
|
* <ul>
|
||||||
|
* <li>前4项是已明确的页面卡片,后2项是预留(保障资源/活动事件),可按需启用</li>
|
||||||
|
* <li>每个维度都对应一张或多张数据表的 GROUP BY + HAVING COUNT 聚合查询</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author management
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum TimetableConflictDimension {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 教学班时间冲突检查。
|
||||||
|
* <p>检查逻辑:同一教学班次(学员队)在同一日期同一节次被安排多门课程 → 冲突</p>
|
||||||
|
* <p>涉及数据表:
|
||||||
|
* <ol>
|
||||||
|
* <li>实施_课程表 SSKCB —— 主表,获取日期(RQ)、节次(JC)、年度(ND)、删除状态(SCZT)</li>
|
||||||
|
* <li>实施_课程表_学员队 SSKCBXYD —— 课次与教学班次(学员队)的关联表,获取教学班次编号(XYDBH)</li>
|
||||||
|
* <li>学员队表 XYDB(辅助关联,用于翻译中文名称展示)</li>
|
||||||
|
* </ol>
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
TEAM_CONFLICT("TEAM_CONFLICT",
|
||||||
|
"教学班时间冲突检查",
|
||||||
|
"同一教学班次在同一时间段被安排多门课程"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 教学班必修与选修时间冲突检查。
|
||||||
|
* <p>检查逻辑:同一教学班次在同一日期同一节次,既有"必修课"又有"选修课" → 冲突</p>
|
||||||
|
* <p>涉及数据表:
|
||||||
|
* <ol>
|
||||||
|
* <li>实施_课程表 SSKCB —— 主表,日期/节次/年度/删除状态</li>
|
||||||
|
* <li>实施_课程表_学员队 SSKCBXYD —— 课次与教学班次的关联</li>
|
||||||
|
* <li>课程表科目 KB / 课程科目表 KC —— 获取课程的"课程类型"字段(必修/选修/考查等)</li>
|
||||||
|
* </ol>
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
ELECTIVE_REQUIRED_CONFLICT("ELECTIVE_REQUIRED_CONFLICT",
|
||||||
|
"教学班必修与选修时间冲突检查",
|
||||||
|
"教学班次必修课与选修课时间重叠"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 教员时间冲突检查。
|
||||||
|
* <p>检查逻辑:同一教员(主讲+辅讲统一在此维度)在同一日期同一节次被安排多门课程 → 冲突</p>
|
||||||
|
* <p>涉及数据表:
|
||||||
|
* <ol>
|
||||||
|
* <li>实施_课程表 SSKCB —— 主表,日期/节次/年度/删除状态</li>
|
||||||
|
* <li>实施_课程表_辅助教员 SSKCBFZJY —— 课次与教员的关联表(含主讲ZJY=1 与辅讲ZJY=0),获取教员编号(FZJYBH)</li>
|
||||||
|
* <li>教员表 JYB / 教研室人员 JYSX —— 翻译中文姓名展示</li>
|
||||||
|
* </ol>
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
TEACHER_CONFLICT("TEACHER_CONFLICT",
|
||||||
|
"教员时间冲突检查",
|
||||||
|
"同一教员在同一时间段被安排多门课程"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 教室时间冲突检查。
|
||||||
|
* <p>检查逻辑:同一教室(场地)在同一日期同一节次被多门课程占用 → 冲突</p>
|
||||||
|
* <p>涉及数据表:
|
||||||
|
* <ol>
|
||||||
|
* <li>实施_课程表 SSKCB —— 主表,日期/节次/年度/删除状态</li>
|
||||||
|
* <li>实施_课程表_教室 SSKCBJS —— 课次与教室的关联表,获取教室编号(JSBH)</li>
|
||||||
|
* <li>教室表 JSB —— 翻译中文名称(含容纳人数/设施等信息)</li>
|
||||||
|
* </ol>
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
CLASSROOM_CONFLICT("CLASSROOM_CONFLICT",
|
||||||
|
"教室时间冲突检查",
|
||||||
|
"同一教室在同一时间段被多门课程占用"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 教学保障资源冲突检查(预留/按需开启)。
|
||||||
|
* <p>检查逻辑:同一保障项目在同一日期同一节次,申请总数量 > 可提供能力阈值 → 冲突</p>
|
||||||
|
* <p>涉及数据表:
|
||||||
|
* <ol>
|
||||||
|
* <li>课程表保障明细表 KCBBZMX / 实施保障表 —— 课次申请的保障项目及数量</li>
|
||||||
|
* <li>保障项目表 BZLB / BZMX —— 项目可提供能力阈值</li>
|
||||||
|
* </ol>
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
GUARANTEE_CONFLICT("GUARANTEE_CONFLICT",
|
||||||
|
"教学保障资源冲突检查",
|
||||||
|
"同一保障项目同一时间申请数量超过可用能力"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 院历/班历活动事件冲突检查(预留/按需开启)。
|
||||||
|
* <p>检查逻辑:被"不可排课"活动事件覆盖的日期节次范围,却依然存在已落地课次 → 冲突</p>
|
||||||
|
* <p>涉及数据表:
|
||||||
|
* <ol>
|
||||||
|
* <li>学期校历表 XQ / 学员队期班历 XYJXQJB —— 标记"是否可排课"的活动事件集合</li>
|
||||||
|
* <li>实施_课程表 SSKCB + 实施_课程表_学员队 SSKCBXYD —— 当前已排课的日期节次</li>
|
||||||
|
* </ol>
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
EVENT_CONFLICT("EVENT_CONFLICT",
|
||||||
|
"院历/班历活动事件冲突检查",
|
||||||
|
"不可排课的活动事件时段存在已排课程");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 维度编码(前端传参使用此值)
|
||||||
|
*/
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卡片标题(对应页面卡片名称)
|
||||||
|
*/
|
||||||
|
private final String title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卡片描述(对应页面卡片下的说明文字)
|
||||||
|
*/
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
TimetableConflictDimension(String code, String title, String description) {
|
||||||
|
this.code = code;
|
||||||
|
this.title = title;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据编码获取枚举值(前端传 code → 后端反查枚举)。
|
||||||
|
*
|
||||||
|
* @param code 维度编码
|
||||||
|
* @return 对应枚举,找不到返回 null
|
||||||
|
*/
|
||||||
|
public static TimetableConflictDimension of(String code) {
|
||||||
|
if (code == null || code.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (TimetableConflictDimension d : values()) {
|
||||||
|
if (d.getCode().equalsIgnoreCase(code)) {
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+101
@@ -0,0 +1,101 @@
|
|||||||
|
package com.roomroot.jwgl.vo.courserunning;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分组教学任务列表展示 VO。
|
||||||
|
* <p>
|
||||||
|
* 对应"分组教学任务"页面表格展示的所有字段,
|
||||||
|
* 通过多表关联查询(教学任务 + 学员队 + 课表 + 教材 + 教员 + 教室 + 实施课程)聚合展示。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author management
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class GroupTeachingTaskVO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序号,前端展示用的行号。
|
||||||
|
*/
|
||||||
|
private Integer xh;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 课程名称,关联课表的"课名称"。
|
||||||
|
*/
|
||||||
|
private String kcmc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 教材名称,关联教材信息表的"名称"。
|
||||||
|
*/
|
||||||
|
private String jcmc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实施教员姓名,关联教员表的"教员姓名"。
|
||||||
|
*/
|
||||||
|
private String jyxm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 教学场地(教室名称),关联教室表的"教室名称"。
|
||||||
|
*/
|
||||||
|
private String jsmc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可选队别班次名称(学员队名称),多个班次用分号分隔。
|
||||||
|
* 例:"2025级计算机1班;2025级计算机2班"。
|
||||||
|
*/
|
||||||
|
private String xydmc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计划学时,来自实施_课程表的"学时"字段。
|
||||||
|
*/
|
||||||
|
private Integer jhxs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行学时,来自实施_课程表的"运行学时"字段。
|
||||||
|
*/
|
||||||
|
private Integer yxxs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学分,来自实施_课程表的"学分"字段。
|
||||||
|
*/
|
||||||
|
private Float xf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开课日期,班次的开始教学日期。
|
||||||
|
*/
|
||||||
|
private LocalDateTime kkrq;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 结课日期,班次的结束教学日期。
|
||||||
|
*/
|
||||||
|
private LocalDateTime jkrq;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 报名日期,学员报名截止日期。
|
||||||
|
*/
|
||||||
|
private LocalDateTime bmrq;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计划人数,来自学员队表的"学员队人数"。
|
||||||
|
*/
|
||||||
|
private Integer jhrs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 要求说明,来自学员队拼班信息的"要求说明"。
|
||||||
|
*/
|
||||||
|
private String yqsm;
|
||||||
|
|
||||||
|
// ====== 查询条件字段(仅用于筛选,不回显) ======
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 年度(查询条件)。
|
||||||
|
*/
|
||||||
|
private Integer nd;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务状态(查询条件)。
|
||||||
|
*/
|
||||||
|
private String zt;
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
package com.roomroot.jwgl.vo.timetableconflict;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单张"冲突检查卡片"结果 VO。
|
||||||
|
* <p>
|
||||||
|
* 对应页面上方 6 个检查卡片,每个卡片有:标题、描述、检查状态、冲突数量、维度编码。
|
||||||
|
* 前端根据 conflictCount 的值展示为"0(绿色通过)/ >0(红色警告)"。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author management
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class TimetableConflictCardVO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 冲突维度编码(见 {@link com.roomroot.jwgl.unit.conflict.TimetableConflictDimension})。
|
||||||
|
* 例:TEAM_CONFLICT、TEACHER_CONFLICT。
|
||||||
|
*/
|
||||||
|
private String dimensionCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卡片标题。例:"教学班时间冲突检查"。
|
||||||
|
*/
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卡片描述。例:"同一教学班次在同一时间段被安排多门课程"。
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已执行检查。
|
||||||
|
* true = 已检查(页面去掉"未检查"标签,显示冲突数)
|
||||||
|
* false = 未检查(页面显示灰色"未检查"标签,冲突数视为 0)
|
||||||
|
*/
|
||||||
|
private boolean checked;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测出的冲突总数(= 冲突明细列表的总行数,非聚合数)。
|
||||||
|
* 例:同一班次在 3 个日期节次有冲突 → conflictCount = 3。
|
||||||
|
*/
|
||||||
|
private int conflictCount;
|
||||||
|
}
|
||||||
+135
@@ -0,0 +1,135 @@
|
|||||||
|
package com.roomroot.jwgl.vo.timetableconflict;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单条冲突明细 VO。
|
||||||
|
* <p>
|
||||||
|
* 对应页面下方"冲突明细"表格的每一行数据。
|
||||||
|
* 所有维度的冲突统一用此结构返回,前端根据 dimensionCode 做颜色区分或图标区分。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author management
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class TimetableConflictDetailVO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 冲突维度编码(见枚举 TimetableConflictDimension#code)。
|
||||||
|
* 例:TEACHER_CONFLICT → 前端用红色标签标注"教员冲突"。
|
||||||
|
*/
|
||||||
|
private String dimensionCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 冲突维度标题(中文)。例:"教员时间冲突"。
|
||||||
|
*/
|
||||||
|
private String dimensionTitle;
|
||||||
|
|
||||||
|
// ==================== 发生冲突的资源 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发生冲突的资源编号。
|
||||||
|
* 根据维度不同,含义不同:
|
||||||
|
* TEAM_CONFLICT → 教学班次(学员队)编号
|
||||||
|
* ELECTIVE_REQUIRED_CONFLICT → 教学班次(学员队)编号
|
||||||
|
* TEACHER_CONFLICT → 教员编号
|
||||||
|
* CLASSROOM_CONFLICT → 教室编号
|
||||||
|
* GUARANTEE_CONFLICT → 保障项目编号
|
||||||
|
* EVENT_CONFLICT → 活动事件编号
|
||||||
|
*/
|
||||||
|
private String resourceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发生冲突的资源中文名(直接展示给用户,无需再次翻译)。
|
||||||
|
* 例:"张教授"、"101多媒体教室"、"2025级计算机1班"。
|
||||||
|
*/
|
||||||
|
private String resourceName;
|
||||||
|
|
||||||
|
// ==================== 冲突的日期节次 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发生冲突的日期。
|
||||||
|
* 例:2026-09-10。
|
||||||
|
*/
|
||||||
|
private LocalDateTime rq;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发生冲突的节次。
|
||||||
|
* 例:3(表示当日第 3 节有冲突)。
|
||||||
|
*/
|
||||||
|
private Integer jc;
|
||||||
|
|
||||||
|
// ==================== 冲突涉及的课次/课程信息 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同一"资源+日期节次"上发生冲突的课次数量。
|
||||||
|
* 一般 >= 2(1 个资源同一时间被安排多门课)。
|
||||||
|
* 用于明细列"占用课次数"展示。
|
||||||
|
*/
|
||||||
|
private Integer occupiedLessonCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 冲突涉及的课程名称列表,用";"分隔。
|
||||||
|
* 例:"高等数学;大学英语"。
|
||||||
|
*/
|
||||||
|
private String courseNames;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 冲突涉及的课次编号列表,用","分隔。
|
||||||
|
* 对应表【实施_课程表 SSKCB】的主键 编号。
|
||||||
|
* 用于前端做"点击跳转到具体课次详情"的超链接。
|
||||||
|
*/
|
||||||
|
private String conflictSskcbBhs;
|
||||||
|
|
||||||
|
// ==================== 备注 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 冲突原因的中文描述。
|
||||||
|
* 例:"教学班次【2025级计算机1班】在 2026-09-10 第3节同时被安排《高等数学》和《大学英语》两门课程"。
|
||||||
|
* 用于"查看详情"弹窗,或表格的备注列。
|
||||||
|
*/
|
||||||
|
private String reason;
|
||||||
|
|
||||||
|
// ==================== UI 明细表格新增业务字段(对应截图8列:冲突号/班次/教员/场地/责任单位)====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 冲突组唯一编号(格式:日期_节次_资源编号,例:20260821_03_XYD001)。
|
||||||
|
* 由内层聚合出的"日期+节次+资源号"在外层直接拼接,便于前端定位和排查。
|
||||||
|
*/
|
||||||
|
private String conflictNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班次名称(学员队名称)。
|
||||||
|
* 该冲突涉及的所有教学班次/学员队,多值用 WM_CONCAT(DISTINCT ...) 去重拼接,展示给用户。
|
||||||
|
* 例:"2025级计算机1班;2025级计算机2班"。
|
||||||
|
*/
|
||||||
|
private String xydNames;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 教员姓名。
|
||||||
|
* 该冲突涉及的所有主讲/辅讲教员,多个时拼接;可带【主】/【辅】前缀便于区分角色。
|
||||||
|
* 例:"【主】张教授;【辅】李助教"。
|
||||||
|
*/
|
||||||
|
private String teacherNames;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场地/教室名称。
|
||||||
|
* 该冲突涉及的所有教学场地(教室)名称,多值去重拼接。
|
||||||
|
* 例:"1号教学楼101多媒体教室;2号教学楼实验机房"。
|
||||||
|
*/
|
||||||
|
private String classroomNames;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 责任单位(班次的所属单位 / 教研室名称)。
|
||||||
|
* 统一从学员队表的「所属单位」列取值(WM_CONCAT 去重)。
|
||||||
|
*/
|
||||||
|
private String responsibleDept;
|
||||||
|
}
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
package com.roomroot.jwgl.vo.timetableconflict;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 课表冲突检查 —— 全维度汇总结果 VO。
|
||||||
|
* <p>
|
||||||
|
* 对应页面"执行全部检查"按钮:返回 6 张卡片的检查结果数组。
|
||||||
|
* 页面初次加载时也返回此对象(所有卡片的 checked=false),供前端渲染卡片布局。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author management
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class TimetableConflictSummaryVO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前检查所属年度(学期)。
|
||||||
|
* 例:2026(对应页面顶部 2026年秋季学期)。
|
||||||
|
*/
|
||||||
|
private Integer nd;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 6 张(当前已启用 4 张)冲突检查卡片的结果列表。
|
||||||
|
* 列表顺序与 UI 卡片顺序保持一致:
|
||||||
|
* 1.教学班时间冲突 → 2.必修选修冲突 → 3.教员时间冲突 → 4.教室时间冲突 → 5.保障 → 6.活动事件
|
||||||
|
*/
|
||||||
|
private List<TimetableConflictCardVO> cards;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全部冲突总数(= 所有卡片 conflictCount 之和)。
|
||||||
|
* 用于页面顶部快速展示"本学期共有多少条冲突待处理"。
|
||||||
|
*/
|
||||||
|
private int totalConflictCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已完成完整的全量检查(即所有卡片都执行过一次)。
|
||||||
|
* true 表示用户至少点过一次"执行全部检查"
|
||||||
|
* false 表示还未全部检查(可能只单独检查过部分卡片)
|
||||||
|
*/
|
||||||
|
private boolean fullyChecked;
|
||||||
|
|
||||||
|
public TimetableConflictSummaryVO() {
|
||||||
|
this.cards = new ArrayList<>();
|
||||||
|
this.totalConflictCount = 0;
|
||||||
|
this.fullyChecked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重计算总冲突数。每次单卡片检查结果更新后,上层汇总时调用此方法。
|
||||||
|
*/
|
||||||
|
public void recalculateTotal() {
|
||||||
|
int total = 0;
|
||||||
|
boolean all = !cards.isEmpty();
|
||||||
|
for (TimetableConflictCardVO c : cards) {
|
||||||
|
total += c.getConflictCount();
|
||||||
|
if (!c.isChecked()) {
|
||||||
|
all = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.totalConflictCount = total;
|
||||||
|
this.fullyChecked = all;
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user