课表申请功能重写 bug修复

This commit is contained in:
liumengyu
2026-08-25 18:47:22 +08:00
parent aa4145fd33
commit a30b963dea
58 changed files with 1203 additions and 1632 deletions
@@ -1,88 +1,25 @@
package com.roomroot.jwgl.config;
import com.roomroot.jwgl.interceptor.AccountManagementAuthorizationInterceptor;
import com.roomroot.jwgl.interceptor.JwglApiAccessInterceptor;
import com.roomroot.jwgl.interceptor.OperationLogInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 在线账户状态和账户管理权限 Web MVC 配置。
*
* <p>该配置对已有在线会话执行账户状态校验,同时保持普通匿名请求的原有访问规则;
* 账户管理路径继续执行超级管理员权限校验,并在校验通过后采集操作日志上下文。</p>
* 操作日志采集 Web MVC 配置。
* <p>页面菜单权限由前端控制;后台只校验登录态,查询时再按角色和用户ID过滤数据。</p>
*/
@Configuration
public class AccountManagementWebMvcConfig implements WebMvcConfigurer {
/**
* 账户管理身份和权限拦截器。
*/
private final AccountManagementAuthorizationInterceptor
authorizationInterceptor;
/**
* 操作日志统一请求采集拦截器。
*/
private final OperationLogInterceptor operationLogInterceptor;
private final JwglApiAccessInterceptor jwglApiAccessInterceptor;
/**
* 构造账户管理和操作日志统一 Web MVC 配置。
*
* @param authorizationInterceptor 账户管理身份和权限拦截器
* @param operationLogInterceptor 操作日志统一请求采集拦截器
* @param jwglApiAccessInterceptor 教务角色接口访问拦截器
*/
public AccountManagementWebMvcConfig(
AccountManagementAuthorizationInterceptor
authorizationInterceptor,
OperationLogInterceptor operationLogInterceptor,
JwglApiAccessInterceptor jwglApiAccessInterceptor) {
// 第一步:保存账户管理专用拦截器,供 MVC 注册阶段使用。
this.authorizationInterceptor = authorizationInterceptor;
// 第二步:保存操作日志采集拦截器,供 MVC 注册阶段使用。
public AccountManagementWebMvcConfig(OperationLogInterceptor operationLogInterceptor) {
this.operationLogInterceptor = operationLogInterceptor;
this.jwglApiAccessInterceptor = jwglApiAccessInterceptor;
}
/**
* 注册账户状态校验、账户管理权限和操作日志采集规则。
*
* @param registry Spring MVC 拦截器注册器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
/*
* 第一步:注册账户管理鉴权拦截器。
* 第二步:覆盖全部 Controller 请求,使停用或已合并账户不能绕过
* 账户管理路径继续访问其他业务接口。
* 第三步:拦截器内部会放行没有 Servlet 会话的普通匿名请求,
* 因此不会改变项目现有公开接口的访问方式。
*/
// registry.addInterceptor(authorizationInterceptor)
// .addPathPatterns("/**");
/*
* 第四步:在账户状态和账户管理权限校验通过后采集日志上下文。
* 第五步:覆盖全部 Controller 请求,由采集拦截器自行跳过
* 非 Controller 处理器和没有有效登录记录的请求。
*/
registry.addInterceptor(jwglApiAccessInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(
"/login",
"/register",
"/captchaImage",
"/jys/teacher/list",
"/swagger-ui/**",
"/v3/api-docs/**",
"/druid/**");
registry.addInterceptor(operationLogInterceptor)
.addPathPatterns("/**");
}
@@ -24,10 +24,10 @@ public class ScheduleAdjustApplyDTO {
private String sskcbbh;
/**
* 调课事由,必填,描述本次调课的原因和依据。
* 调课事由,对应 实施_调课申请.事由 VARCHAR(200)。
*/
@NotBlank(message = "调课事由不能为空")
@Size(max = 500, message = "调课事由不能超过500个字符")
@Size(max = 200, message = "调课事由不能超过200个字符")
private String sy;
/**
@@ -79,9 +79,9 @@ public class ScheduleAdjustApplyDTO {
private String xjsbh;
/**
* 调课后教学内容,调整后的本节课教学主题或知识点。
* 调课后教学内容,对应 实施_调课申请.教学内容 VARCHAR(500)。
*/
@Size(max = 2000, message = "教学内容不能超过2000个字符")
@Size(max = 500, message = "教学内容不能超过500个字符")
private String jxnr;
/**
@@ -91,9 +91,9 @@ public class ScheduleAdjustApplyDTO {
private String jxyd;
/**
* 调课后教学方法,调整后的教学方法。
* 调课后教学方法,对应 实施_调课申请.教学方法 VARCHAR(50)。
*/
@Size(max = 200, message = "教学方法不能超过200个字符")
@Size(max = 50, message = "教学方法不能超过50个字符")
private String jxff;
/**
@@ -109,9 +109,9 @@ public class ScheduleAdjustApplyDTO {
private String ycxx;
/**
* 备注,补充说明信息。
* 备注,对应 实施_调课申请.备注 VARCHAR(50)。
*/
@Size(max = 500, message = "备注不能超过500个字符")
@Size(max = 50, message = "备注不能超过50个字符")
private String bz;
/**
@@ -2,53 +2,42 @@ package com.roomroot.jwgl.dto.courserunning;
import lombok.Data;
import jakarta.validation.constraints.*;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
/**
* 调课申请审批参数。
* <p>
* 用于教研室或机关审批调课申请,包含审批结果、审批意见和审批人编号。
* </p>
* 教研室审批调课申请参数。
*/
@Data
public class ScheduleAdjustAuditDTO {
/**
* 调课申请编号,必填,标识需要审批的调课申请。
* 调课申请编号。
*/
@NotBlank(message = "调课申请编号不能为空")
@Size(max = 50, message = "调课申请编号不能超过50个字符")
private String ssdksqbh;
/**
* 审批角色,必填:jys(教研室审批)、jg(机关审批)。
*/
@NotBlank(message = "审批角色不能为空")
@Size(max = 20, message = "审批角色不能超过20个字符")
private String auditRole;
/**
* 审批结果,必填:1-同意,2-发回修改,3-拒绝。
* 审批结果:1-同意,2-发回,3-拒绝。点审批即已查收。
*/
@NotNull(message = "审批结果不能为空")
@Min(value = 1, message = "审批结果不正确")
@Max(value = 3, message = "审批结果不正确")
private Integer spzt;
/**
* 审批意见,可选,记录审批的具体意见或发回原因。
* 审批意见,发回时填写原因。
*/
@Size(max = 500, message = "审批意见不能超过500个字符")
private String fhyj;
/**
* 审批人编号,不传时由服务端取当前登录人。
* 审批人编号,不传时取当前登录人;同时记为查收人。
*/
@Size(max = 50, message = "审批人编号不能超过50个字符")
private String sprbh;
/**
* 机关编号,机关审批时可选。
*/
@Size(max = 50, message = "机关编号不能超过50个字符")
private String jgbh;
}
@@ -38,15 +38,10 @@ public class ScheduleAdjustQueryDTO extends PageQuery {
private Integer jyspzzt;
/**
* 教研室查收状态:0-未查收,1-已查收。
* 教研室查收状态:0-未查收,1-已查收。点了审批即为已查收。
*/
private Integer jyscszt;
/**
* 机关审批状态:0-未审批,1-已同意,2-已发回,3-已拒绝。
*/
private Integer jgspzt;
/**
* 删除状态:0-正常,1-已删除。
*/
@@ -1,52 +0,0 @@
package com.roomroot.jwgl.dto.courserunning;
import lombok.Data;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
/**
* 调课策略新增/修改参数。
* <p>
* 统一处理新增和修改:策略编号为空时执行新增,非空时执行修改。
* 策略类型为 TIME_LIMIT 时需填写截止星期和截止节次;
* 策略类型为 AUTO_APPROVE 时截止星期和截止节次不生效。
* </p>
*/
@Data
public class ScheduleAdjustStrategyDTO {
/** 策略编号(修改时必填,新增时为空) */
@Size(max = 50, message = "策略编号不能超过50个字符")
private String clbh;
/** 策略名称 */
@NotBlank(message = "策略名称不能为空")
@Size(max = 100, message = "策略名称不能超过100个字符")
private String clmc;
/** 策略类型:TIME_LIMIT=时间段限制,AUTO_APPROVE=免审批 */
@NotBlank(message = "策略类型不能为空")
@Size(max = 20, message = "策略类型不能超过20个字符")
private String cllx;
/** 是否启用:0=停用,1=启用(为空时默认1) */
private Integer sfqy;
/** 截止星期:0-6(仅 TIME_LIMIT 类型使用) */
private Integer jzxq;
/** 截止节次(仅 TIME_LIMIT 类型使用) */
private Integer jzjc;
/** 生效开始时间 */
private LocalDateTime sxkssj;
/** 生效结束时间 */
private LocalDateTime sxjssj;
/** 备注 */
@Size(max = 500, message = "备注不能超过500个字符")
private String bz;
}
@@ -1,8 +1,9 @@
package com.roomroot.jwgl.entity;
import com.baomidou.mybatisplus.annotation.TableName;
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 com.roomroot.jwgl.unit.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -17,55 +18,48 @@ import java.time.LocalDateTime;
@TableName("调课申请_保障明细")
public class DKSQBZMX extends BaseEntity {
/**
* 编号
*/
@TableId(value = "编号", type = IdType.INPUT)
private String bh;
/**
* 申请编号
*/
@TableField("申请编号")
private String sqbh;
/**
* 保障提供明细编号
*/
@TableField("保障提供明细编号")
private String bztgmxbh;
/**
* 数量
*/
@TableField("数量")
private Integer sl;
/**
* 备注
*/
@TableField("备注")
private String bz;
/**
* 查收状态
*/
@TableField("查收状态")
private Integer cszt;
/**
* 查收时间
*/
@TableField("查收时间")
private LocalDateTime cssj;
/**
* 年度
*/
@TableField("年度")
private Integer nd;
/**
* 日期
*/
@TableField("日期")
private LocalDateTime rq;
/**
* 节次
*/
@TableField("节次")
private Integer jc;
@TableField(exist = false)
private LocalDateTime createTime;
@TableField(exist = false)
private LocalDateTime updateTime;
@TableField(exist = false)
private String createBy;
@TableField(exist = false)
private String updateBy;
@TableField(exist = false)
private Integer delFlag;
}
@@ -1,8 +1,9 @@
package com.roomroot.jwgl.entity;
import com.baomidou.mybatisplus.annotation.TableName;
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 com.roomroot.jwgl.unit.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -17,60 +18,51 @@ import java.time.LocalDateTime;
@TableName("调课申请_辅助教员")
public class DKSQFZJY extends BaseEntity {
/**
* 编号
*/
@TableId(value = "编号", type = IdType.INPUT)
private String bh;
/**
* 调课申请编号
*/
@TableField("调课申请编号")
private String dksqbh;
/**
* 辅助教员编号
*/
@TableField("辅助教员编号")
private String fzjybh;
/**
* 查收状态
*/
@TableField("查收状态")
private Integer cszt;
/**
* 查收时间
*/
@TableField("查收时间")
private LocalDateTime cssj;
/**
* 主教员
*/
@TableField("主教员")
private Integer zjy;
/**
* 移除状态
*/
@TableField("移除状态")
private Integer yczt;
/**
* 年度
*/
@TableField("年度")
private Integer nd;
/**
* 备注
*/
@TableField("备注")
private String bz;
/**
* 日期
*/
@TableField("日期")
private LocalDateTime rq;
/**
* 节次
*/
@TableField("节次")
private Integer jc;
@TableField(exist = false)
private LocalDateTime createTime;
@TableField(exist = false)
private LocalDateTime updateTime;
@TableField(exist = false)
private String createBy;
@TableField(exist = false)
private String updateBy;
@TableField(exist = false)
private Integer delFlag;
}
@@ -1,8 +1,9 @@
package com.roomroot.jwgl.entity;
import com.baomidou.mybatisplus.annotation.TableName;
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 com.roomroot.jwgl.unit.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -17,40 +18,39 @@ import java.time.LocalDateTime;
@TableName("调课申请_教室")
public class DKSQJS extends BaseEntity {
/**
* 编号
*/
@TableId(value = "编号", type = IdType.INPUT)
private String bh;
/**
* 调课申请编号
*/
@TableField("调课申请编号")
private String dksqbh;
/**
* 教室编号
*/
@TableField("教室编号")
private String jsbh;
/**
* 备注
*/
@TableField("备注")
private String bz;
/**
* 年度
*/
@TableField("年度")
private Integer nd;
/**
* 日期
*/
@TableField("日期")
private LocalDateTime rq;
/**
* 节次
*/
@TableField("节次")
private Integer jc;
@TableField(exist = false)
private LocalDateTime createTime;
@TableField(exist = false)
private LocalDateTime updateTime;
@TableField(exist = false)
private String createBy;
@TableField(exist = false)
private String updateBy;
@TableField(exist = false)
private Integer delFlag;
}
@@ -1,8 +1,9 @@
package com.roomroot.jwgl.entity;
import com.baomidou.mybatisplus.annotation.TableName;
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 com.roomroot.jwgl.unit.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -17,50 +18,45 @@ import java.time.LocalDateTime;
@TableName("调课申请_学员队")
public class DKSQXYD extends BaseEntity {
/**
* 编号
*/
@TableId(value = "编号", type = IdType.INPUT)
private String bh;
/**
* 调课申请编号
*/
@TableField("调课申请编号")
private String dksqbh;
/**
* 学员队编号
*/
@TableField("学员队编号")
private String xydbh;
/**
* 查收状态
*/
@TableField("查收状态")
private Integer cszt;
/**
* 查收时间
*/
@TableField("查收时间")
private LocalDateTime cssj;
/**
* 移除状态
*/
@TableField("移除状态")
private Integer yczt;
/**
* 年度
*/
@TableField("年度")
private Integer nd;
/**
* 日期
*/
@TableField("日期")
private LocalDateTime rq;
/**
* 节次
*/
@TableField("节次")
private Integer jc;
@TableField(exist = false)
private LocalDateTime createTime;
@TableField(exist = false)
private LocalDateTime updateTime;
@TableField(exist = false)
private String createBy;
@TableField(exist = false)
private String updateBy;
@TableField(exist = false)
private Integer delFlag;
}
@@ -57,16 +57,6 @@ public class SSDKSQ extends BaseEntity {
@TableField("教研室批准人编号")
private String jyspzrbh;
/** 原表无此列 */
@TableField(exist = false)
private Integer jxxspzzt;
@TableField(exist = false)
private LocalDateTime jxxspzsj;
@TableField(exist = false)
private String jxxspzrbh;
@TableField("备注")
private String bz;
@@ -1,81 +0,0 @@
package com.roomroot.jwgl.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.roomroot.jwgl.unit.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* 实施_调课申请审批
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("实施_调课申请审批")
public class SSDKSQSP extends BaseEntity {
/**
* 编号
*/
@TableId(value = "编号", type = IdType.INPUT)
private String bh;
/**
* 实施_调课申请编号
*/
private String ssdksqbh;
/**
* 机关编号
*/
private String jgbh;
/**
* 创建时间
*/
private LocalDateTime cjsj;
/**
* 审批时间
*/
private LocalDateTime spsj;
/**
* 审批状态
*/
private Integer spzt;
/**
* 审批人编号
*/
private String sprbh;
/**
* 查收状态
*/
private Integer cszt;
/**
* 查收时间
*/
private LocalDateTime cssj;
/**
* 查收人编号
*/
private String csrbh;
/**
* 发回意见
*/
private String fhyj;
/**
* 年度
*/
private Integer nd;
}
@@ -5,7 +5,9 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.roomroot.jwgl.handler.DmGuidTypeHandler;
import lombok.Data;
import org.apache.ibatis.type.JdbcType;
import java.time.LocalDateTime;
@@ -13,13 +15,14 @@ import java.time.LocalDateTime;
* 实施_课程
*/
@Data
@TableName(value = "实施_课程")
@TableName(value = "实施_课程", autoResultMap = true)
public class SSKC {
/**
* 编号
* 编号(库内 VARBINARY(16),Java 侧按 36 位 UUID 字符串使用)
*/
@TableId(value = "编号", type = IdType.INPUT)
@TableField(value = "编号", jdbcType = JdbcType.VARBINARY, typeHandler = DmGuidTypeHandler.class)
private String bh;
/**
@@ -1,74 +0,0 @@
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>
* 管理调课的时间段限制策略与免审批策略。
* 策略类型为 TIME_LIMIT 时,用于限制教员在规定时间后不得调整下周课程;
* 策略类型为 AUTO_APPROVE 时,用于在生效范围内自动通过调课申请,跳过三级审批。
* </p>
*/
@Data
@TableName("实施_调课策略")
public class ScheduleAdjustStrategy {
/** 策略编号 */
@TableId(value = "策略编号", type = IdType.INPUT)
private String clbh;
/** 策略名称 */
@TableField("策略名称")
private String clmc;
/**
* 策略类型:TIME_LIMIT=时间段限制,AUTO_APPROVE=免审批
*/
@TableField("策略类型")
private String cllx;
/** 是否启用:0=停用,1=启用 */
@TableField("是否启用")
private Integer sfqy;
/**
* 截止星期:0-6(周日到周六),仅 TIME_LIMIT 类型使用。
* 含义:当前时间超过该星期时,不允许调整下周课程。为空表示不限制星期。
*/
@TableField("截止星期")
private Integer jzxq;
/**
* 截止节次:仅 TIME_LIMIT 类型使用。
* 含义:到达截止星期当天的该节次后不允许调整下周课程。为空表示不限制节次。
*/
@TableField("截止节次")
private Integer jzjc;
/** 生效开始时间(包含),为空表示不限制开始时间 */
@TableField("生效开始时间")
private LocalDateTime sxkssj;
/** 生效结束时间(包含),为空表示不限制结束时间 */
@TableField("生效结束时间")
private LocalDateTime sxjssj;
/** 备注 */
@TableField("备注")
private String bz;
/** 创建时间 */
@TableField("创建时间")
private LocalDateTime cjsj;
/** 更新时间 */
@TableField("更新时间")
private LocalDateTime gxsj;
}
@@ -0,0 +1,41 @@
package com.roomroot.jwgl.handler;
import com.roomroot.jwgl.utils.UuidUtil;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* 达梦 VARBINARY(16) GUID 与 Java 字符串互转。
* <p>
* 仅通过实体 / resultMap 显式引用,避免把其它 VARBINARY 字段都当成 UUID。
* 禁止把带横线的 CHAR UUID 直接绑定到 VARBINARY,达梦会按十六进制解析并报 -6147。
* </p>
*/
public class DmGuidTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
ps.setBytes(i, UuidUtil.toBytes(parameter));
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return UuidUtil.fromBytes(rs.getBytes(columnName));
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return UuidUtil.fromBytes(rs.getBytes(columnIndex));
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return UuidUtil.fromBytes(cs.getBytes(columnIndex));
}
}
@@ -62,27 +62,18 @@ public class JwglApiAccessInterceptor implements HandlerInterceptor {
if (isTeacherDenied(path)) {
return deny(response, "教员无权访问该功能");
}
if (isTeacherAdjustDenied(path, request.getMethod())) {
return deny(response, "教员只能提交、查看和撤销本人的调课申请");
}
return true;
}
if (roleHelper.isResearchOffice()) {
if (isOfficeDenied(path)) {
return deny(response, "教研室无权访问系统管理功能");
}
if (isResearchOfficeAdjustDenied(path)) {
return deny(response, "教研室仅可查看和审批本室调课申请");
}
return true;
}
if (roleHelper.isDepartmentPersonnel()) {
if (isOfficeDenied(path)) {
return deny(response, "机关人员无权访问系统管理功能");
}
if (isOfficeAdjustDenied(path, request.getMethod())) {
return deny(response, "机关人员仅可查看和审批调课申请");
}
return true;
}
return true;
@@ -126,47 +117,6 @@ public class JwglApiAccessInterceptor implements HandlerInterceptor {
"/system/notice");
}
private boolean isTeacherAdjustDenied(String path, String method) {
if (!path.startsWith("/course-running/adjust")) {
return false;
}
if (path.startsWith("/course-running/adjust/audit")
|| path.startsWith("/course-running/adjust/receive")) {
return true;
}
if (path.equals("/course-running/adjust/submit")) {
return !HttpMethod.POST.matches(method);
}
return false;
}
private boolean isResearchOfficeAdjustDenied(String path) {
if (!path.startsWith("/course-running/adjust")) {
return false;
}
if (path.equals("/course-running/adjust/submit")
|| path.equals("/course-running/adjust/cancel")
|| path.startsWith("/course-running/adjust/lessons")
|| path.startsWith("/course-running/adjust/audit/jg")
|| path.startsWith("/course-running/adjust/audit/jxx")
|| path.startsWith("/course-running/adjust/receive/jg")) {
return true;
}
return false;
}
private boolean isOfficeAdjustDenied(String path, String method) {
if (!path.startsWith("/course-running/adjust")) {
return false;
}
if (HttpMethod.GET.matches(method)) {
return path.startsWith("/course-running/adjust/lessons")
|| path.startsWith("/course-running/adjust/strategy");
}
return !(path.startsWith("/course-running/adjust/audit/jg")
|| path.startsWith("/course-running/adjust/receive/jg"));
}
private boolean isTeacherDenied(String path) {
return startsWithAny(path,
"/faculty",
@@ -19,7 +19,7 @@
a.数量,
a.备注
FROM "调课申请_保障明细" a
LEFT JOIN "保障提供明细表" m ON a.保障提供明细编号 = m.编号
LEFT JOIN "保障提供明细" m ON TRIM(m.编号) = TRIM(a.保障提供明细编号)
WHERE a.申请编号 = #{dksqbh}
ORDER BY a.编号
</select>
@@ -15,11 +15,11 @@
SELECT
a.编号,
a.辅助教员编号,
t.姓名 AS 辅助教员姓名,
t.教员姓名 AS 辅助教员姓名,
a.主教员,
a.备注
FROM "调课申请_辅助教员" a
LEFT JOIN "教员表" t ON a.辅助教员编号 = t.编号
LEFT JOIN "教员表" t ON TRIM(t.教员编号) = TRIM(a.辅助教员编号)
WHERE a.调课申请编号 = #{dksqbh}
ORDER BY a.编号
</select>
@@ -17,7 +17,7 @@
r.教室名称,
a.备注
FROM "调课申请_教室" a
LEFT JOIN "教室表" r ON a.教室编号 = r.编号
LEFT JOIN "教室表" r ON TRIM(r.教室编号) = TRIM(a.教室编号)
WHERE a.调课申请编号 = #{dksqbh}
ORDER BY a.编号
</select>
@@ -15,7 +15,7 @@
a.学员队编号,
d.学员队名称
FROM "调课申请_学员队" a
LEFT JOIN "学员队表" d ON a.学员队编号 = d.编号
LEFT JOIN "学员队表" d ON TRIM(d.学员队编号) = TRIM(a.学员队编号)
WHERE a.调课申请编号 = #{dksqbh}
ORDER BY a.编号
</select>
@@ -21,7 +21,7 @@
<if test="cond != null and cond.xqdc != null">
AND EXISTS (
SELECT 1 FROM "实施_课程学员队" cxf
WHERE cxf."实施_课程编号" = c."编号"
WHERE LOWER(RAWTOHEX(c."编号")) = LOWER(REPLACE(TRIM(cxf."实施_课程编号"), '-', ''))
AND cxf."学期第次" = #{cond.xqdc}
)
</if>
@@ -49,7 +49,7 @@
AND kcb."课次序号" = c."教员课次序号")
)) AS bz
FROM "实施_课程表" s
LEFT JOIN "实施_课程" c ON CAST(c."编号" AS CHAR(36)) = TRIM(s."实施_课程编号")
LEFT JOIN "实施_课程" c ON LOWER(RAWTOHEX(c."编号")) = LOWER(REPLACE(TRIM(s."实施_课程编号"), '-', ''))
LEFT JOIN "课表" k ON (k."课编号" = c."科目编号" OR k."科目代码" = c."科目编号")
LEFT JOIN "教研室表" jys ON jys."教研室代号" = k."教研室代号"
LEFT JOIN "教员表" jy ON jy."教员编号" = c."教员编号"
@@ -93,7 +93,13 @@
<sql id="ClassTimetableSelect">
SELECT
c."编号" AS sskcbh,
LOWER(
SUBSTR(RAWTOHEX(c."编号"), 1, 8) || '-' ||
SUBSTR(RAWTOHEX(c."编号"), 9, 4) || '-' ||
SUBSTR(RAWTOHEX(c."编号"), 13, 4) || '-' ||
SUBSTR(RAWTOHEX(c."编号"), 17, 4) || '-' ||
SUBSTR(RAWTOHEX(c."编号"), 21, 12)
) AS sskcbh,
(NVL(k."课名称", c."科目编号")
|| ' / '
|| NVL(TO_CHAR(c."课程课时系数"), '-')) AS kcmcksxs,
@@ -101,7 +107,7 @@
|| ' / '
|| NVL(TO_CHAR(c."教员课次序号"),
TO_CHAR((SELECT COUNT(1) FROM "实施_课程表" t
WHERE CAST(c."编号" AS CHAR(36)) = TRIM(t."实施_课程编号")
WHERE LOWER(RAWTOHEX(c."编号")) = LOWER(REPLACE(TRIM(t."实施_课程编号"), '-', ''))
AND COALESCE(t."删除状态", 0) = 0)))) AS zrjykc,
c."学时" AS jhxs,
c."运行学时" AS yxxs,
@@ -113,15 +119,15 @@
FROM "实施_课程表" t
LEFT JOIN "实施_课程表_教室" jsx ON jsx."实施_课程表编号" = t."编号"
LEFT JOIN "教室表" js ON js."教室编号" = jsx."教室编号"
WHERE CAST(c."编号" AS CHAR(36)) = TRIM(t."实施_课程编号")
WHERE LOWER(RAWTOHEX(c."编号")) = LOWER(REPLACE(TRIM(t."实施_课程编号"), '-', ''))
AND COALESCE(t."删除状态", 0) = 0),
NVL(js0."教室名称", '-'))) AS rscd,
(SELECT WM_CONCAT(DISTINCT sq."事由")
FROM "实施_调课申请" sq
INNER JOIN "实施_课程表" t ON t."编号" = sq."实施_课程表编号"
WHERE CAST(c."编号" AS CHAR(36)) = TRIM(t."实施_课程编号")) AS ssjhbg
WHERE LOWER(RAWTOHEX(c."编号")) = LOWER(REPLACE(TRIM(t."实施_课程编号"), '-', ''))) AS ssjhbg
FROM "实施_课程" c
INNER JOIN "实施_课程学员队" cx ON cx."实施_课程编号" = c."编号"
INNER JOIN "实施_课程学员队" cx ON LOWER(RAWTOHEX(c."编号")) = LOWER(REPLACE(TRIM(cx."实施_课程编号"), '-', ''))
LEFT JOIN "学员队表" xyd ON xyd."学员队编号" = cx."学员队编号"
LEFT JOIN "课表" k ON (k."课编号" = c."科目编号" OR k."科目代码" = c."科目编号")
LEFT JOIN "教员表" zjy ON zjy."教员编号" = c."责任教员编号"
@@ -30,7 +30,6 @@ public interface SSDKSQMapper extends BaseMapper<SSDKSQ> {
@Param("nd") Integer nd,
@Param("jyspzzt") Integer jyspzzt,
@Param("jyscszt") Integer jyscszt,
@Param("jgspzt") Integer jgspzt,
@Param("sczt") Integer sczt,
@Param("cjsjStart") String cjsjStart,
@Param("cjsjEnd") String cjsjEnd);
@@ -22,7 +22,6 @@
<result column="创建时间" property="cjsj"/>
<result column="教研室批准状态" property="jyspzzt"/>
<result column="教研室查收状态" property="jyscszt"/>
<result column="机关审批状态" property="jgspzt"/>
<result column="发回意见" property="fhyj"/>
<result column="删除状态" property="sczt"/>
</resultMap>
@@ -57,6 +56,7 @@
<result column="教研室查收状态" property="jyscszt"/>
<result column="教研室查收时间" property="jyscssj"/>
<result column="教研室查收人编号" property="jyscsrbh"/>
<result column="发回意见" property="fhyj"/>
</resultMap>
<resultMap id="AdjustableLessonMap" type="com.roomroot.jwgl.vo.courserunning.AdjustableLessonVO">
@@ -103,17 +103,11 @@
a.创建时间,
a.教研室批准状态,
a.教研室查收状态,
(
SELECT MAX(sp.审批状态)
FROM "实施_调课申请审批" sp
WHERE sp.实施_调课申请编号 = a.编号
AND sp.审批状态 IS NOT NULL
) AS 机关审批状态,
a.发回意见,
a.删除状态
FROM "实施_调课申请" a
LEFT JOIN "实施_课程表" b ON TRIM(b.编号) = TRIM(a.实施_课程表编号)
LEFT JOIN "实施_课程" e ON CAST(e.编号 AS CHAR(36)) = TRIM(b.实施_课程编号)
LEFT JOIN "实施_课程" e ON LOWER(RAWTOHEX(e."编号")) = LOWER(REPLACE(TRIM(b.实施_课程编号), '-', ''))
LEFT JOIN "课表" kb ON kb.课编号 = COALESCE(NULLIF(TRIM(a.课编号), ''), e.科目编号)
LEFT JOIN "教员表" j ON TRIM(j.教员编号) = TRIM(a.申请教员编号)
LEFT JOIN "课程表" kcb ON TRIM(kcb.编号) = TRIM(a.课程表编号)
@@ -142,14 +136,6 @@
<if test="jyscszt != null">
AND a.教研室查收状态 = #{jyscszt}
</if>
<if test="jgspzt != null">
AND (
SELECT MAX(sp.审批状态)
FROM "实施_调课申请审批" sp
WHERE sp.实施_调课申请编号 = a.编号
AND sp.审批状态 IS NOT NULL
) = #{jgspzt}
</if>
<if test="sczt != null">
AND a.删除状态 = #{sczt}
</if>
@@ -197,10 +183,11 @@
a.教研室批准人编号,
a.教研室查收状态,
a.教研室查收时间,
a.教研室查收人编号
a.教研室查收人编号,
a.发回意见
FROM "实施_调课申请" a
LEFT JOIN "实施_课程表" b ON TRIM(b.编号) = TRIM(a.实施_课程表编号)
LEFT JOIN "实施_课程" e ON CAST(e.编号 AS CHAR(36)) = TRIM(b.实施_课程编号)
LEFT JOIN "实施_课程" e ON LOWER(RAWTOHEX(e."编号")) = LOWER(REPLACE(TRIM(b.实施_课程编号), '-', ''))
LEFT JOIN "课表" kb ON kb.课编号 = COALESCE(NULLIF(TRIM(a.课编号), ''), e.科目编号)
LEFT JOIN "教员表" j ON TRIM(j.教员编号) = TRIM(a.申请教员编号)
WHERE a.编号 = #{ssdksqbh}
@@ -252,7 +239,7 @@
AND kcb.年度 = e.年度
) AS 课程表编号
FROM "实施_课程表" b
JOIN "实施_课程" e ON CAST(e.编号 AS CHAR(36)) = TRIM(b.实施_课程编号)
JOIN "实施_课程" e ON LOWER(RAWTOHEX(e."编号")) = LOWER(REPLACE(TRIM(b.实施_课程编号), '-', ''))
LEFT JOIN "课表" kb ON kb.课编号 = e.科目编号
LEFT JOIN "教员表" jy ON TRIM(jy.教员编号) = TRIM(e.教员编号)
WHERE COALESCE(b.删除状态, 0) = 0
@@ -1,30 +0,0 @@
package com.roomroot.jwgl.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.roomroot.jwgl.entity.SSDKSQSP;
import com.roomroot.jwgl.vo.courserunning.ScheduleAdjustAuditVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 实施_调课申请审批 Mapper。
* <p>
* 提供调课申请审批记录数据的CRUD操作及自定义查询。
* </p>
*/
@Mapper
public interface SSDKSQSPMapper extends BaseMapper<SSDKSQSP> {
/**
* 根据调课申请编号查询审批记录列表。
* <p>
* 关联机构表获取机关名称,关联教员表获取审批人姓名。
* </p>
*
* @param ssdksqbh 调课申请编号
* @return 审批记录列表
*/
List<ScheduleAdjustAuditVO> selectBySsdksqbh(@Param("ssdksqbh") String ssdksqbh);
}
@@ -1,45 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.roomroot.jwgl.mapper.SSDKSQSPMapper">
<!-- 调课申请审批记录结果映射 -->
<resultMap id="ScheduleAdjustAuditMap" type="com.roomroot.jwgl.vo.courserunning.ScheduleAdjustAuditVO">
<result column="编号" property="bh"/>
<result column="实施_调课申请编号" property="ssdksqbh"/>
<result column="机关编号" property="jgbh"/>
<result column="机关名称" property="jgmc"/>
<result column="审批时间" property="spsj"/>
<result column="审批状态" property="spzt"/>
<result column="审批人编号" property="sprbh"/>
<result column="审批人姓名" property="sprxm"/>
<result column="查收状态" property="cszt"/>
<result column="查收时间" property="cssj"/>
<result column="查收人编号" property="csrbh"/>
<result column="发回意见" property="fhyj"/>
<result column="年度" property="nd"/>
</resultMap>
<!-- 根据调课申请编号查询审批记录列表 -->
<select id="selectBySsdksqbh" resultMap="ScheduleAdjustAuditMap">
SELECT
a.编号,
a.实施_调课申请编号,
a.机关编号,
j.名称 AS 机关名称,
a.审批时间,
a.审批状态,
a.审批人编号,
t.姓名 AS 审批人姓名,
a.查收状态,
a.查收时间,
a.查收人编号,
a.发回意见,
a.年度
FROM "实施_调课申请审批" a
LEFT JOIN "机构表" j ON a.机关编号 = j.编号
LEFT JOIN "教员表" t ON a.审批人编号 = t.编号
WHERE a.实施_调课申请编号 = #{ssdksqbh}
ORDER BY a.创建时间 DESC
</select>
</mapper>
@@ -61,7 +61,7 @@
* 内层:按教员编号+日期+节次 GROUP BY,筛选出现>1课次的冲突
*
* 关键修正:
* 1. 关联链路:SSKCB → 实施_课程 c → 课表 k;必须 CAST(c.编号 AS CHAR(36))
* 1. 关联链路:SSKCB → 实施_课程 c → 课表 k;VARBINARY 主键用 RAWTOHEX 对齐 CHAR 外键
* 2. 主讲/辅讲字段:真实列名「主教员」(1主/0辅)
* 3. 日期节次:统一取主表 s
*
@@ -92,7 +92,7 @@
/* 维度过滤主关联:只看该教员的课次(GROUP BY 维度)*/
INNER JOIN "实施_课程表_辅助教员" f ON f."实施_课程表编号" = s."编号"
/* 课表信息三层关联 */
LEFT JOIN "实施_课程" c ON CAST(c."编号" AS CHAR(36)) = s."实施_课程编号"
LEFT JOIN "实施_课程" c ON LOWER(RAWTOHEX(c."编号")) = LOWER(REPLACE(TRIM(s."实施_课程编号"), '-', ''))
LEFT JOIN "课表" k ON k."科目代码" = c."科目编号"
/* ========== 新增 JOIN:聚合该冲突课次的班次/教员/场地所有信息 ========== */
LEFT JOIN "实施_课程表_学员队" xyd_link ON xyd_link."实施_课程表编号" = s."编号"
@@ -62,7 +62,7 @@
* 内层:按教室编号+日期+节次 GROUP BY,筛选 COUNT(DISTINCT 课次) > 1
*
* 关键修正:
* 1. 三层关联 JOIN:SSKCB → 实施_课程 → 课表;CAST(ss.编号 AS CHAR(36))
* 1. 三层关联 JOIN:SSKCB → 实施_课程 → 课表;VARBINARY 主键用 RAWTOHEX 对齐 CHAR 外键
* 2. 日期节次统一取主表 s
*
* 新增 JOIN(同其他维度):聚合该冲突课次的班次/教员/场地所有明细信息
@@ -88,7 +88,7 @@
/* 维度过滤主关联:教室维度分组 */
INNER JOIN "实施_课程表_教室" c ON c."实施_课程表编号" = s."编号"
/* 课表信息三层关联(ss=实施_课程别名,避免与聚合JOIN的别名冲突)*/
LEFT JOIN "实施_课程" ss ON CAST(ss."编号" AS CHAR(36)) = s."实施_课程编号"
LEFT JOIN "实施_课程" ss ON LOWER(RAWTOHEX(ss."编号")) = LOWER(REPLACE(TRIM(s."实施_课程编号"), '-', ''))
LEFT JOIN "课表" k ON k."科目代码" = ss."科目编号"
/* ========== 新增 JOIN:聚合该冲突课次的班次/教员/场地所有信息 ========== */
LEFT JOIN "实施_课程表_学员队" xyd_link ON xyd_link."实施_课程表编号" = s."编号"
@@ -60,7 +60,7 @@
b.创建时间,
b.变动时间
FROM "实施_课程表" b
LEFT JOIN "实施_课程" a ON b.实施_课程编号 = a.编号
LEFT JOIN "实施_课程" a ON LOWER(RAWTOHEX(a."编号")) = LOWER(REPLACE(TRIM(b.实施_课程编号), '-', ''))
LEFT JOIN "课程表" c ON a.课编号 = c.课编号
WHERE b.删除状态 = 0
<if test="sskcbh != null and sskcbh != ''">
@@ -116,7 +116,7 @@
b.创建时间,
b.变动时间
FROM "实施_课程表" b
LEFT JOIN "实施_课程" a ON b.实施_课程编号 = a.编号
LEFT JOIN "实施_课程" a ON LOWER(RAWTOHEX(a."编号")) = LOWER(REPLACE(TRIM(b.实施_课程编号), '-', ''))
LEFT JOIN "课程表" c ON a.课编号 = c.课编号
WHERE b.编号 = #{sskcbbh}
AND b.删除状态 = 0
@@ -32,10 +32,11 @@
JOIN "实施_课程表_学员队" t ON t."实施_课程表编号" = s."编号"
<!--
修正:三层关联 SSKCB → 实施_课程 → 课表
- 类型转换:c."编号" 是 VARBINARY,s."实施_课程编号" 是 CHAR,必须 CAST 才能匹配上
- 类型转换:c."编号" 是 VARBINARY(16),s."实施_课程编号" 是 CHAR(36);
禁止直接等号(-6147),须 RAWTOHEX 去横线后比较
- 科目关联:KB.科目代码 = SSKC.科目编号
-->
LEFT JOIN "实施_课程" c ON CAST(c."编号" AS CHAR(36)) = s."实施_课程编号"
LEFT JOIN "实施_课程" c ON LOWER(RAWTOHEX(c."编号")) = LOWER(REPLACE(TRIM(s."实施_课程编号"), '-', ''))
LEFT JOIN "课表" k ON k."科目代码" = c."科目编号"
WHERE t."学员队编号" = #{xydbh}
<if test="nd != null">
@@ -126,7 +127,7 @@
/* 维度过滤用:主关联"教学班(学员队)" —— 教学班冲突就是按这个维度 GROUP BY */
INNER JOIN "实施_课程表_学员队" t ON t."实施_课程表编号" = s."编号"
/* 课表信息三层关联 */
LEFT JOIN "实施_课程" c ON CAST(c."编号" AS CHAR(36)) = s."实施_课程编号"
LEFT JOIN "实施_课程" c ON LOWER(RAWTOHEX(c."编号")) = LOWER(REPLACE(TRIM(s."实施_课程编号"), '-', ''))
LEFT JOIN "课表" k ON k."科目代码" = c."科目编号"
/* ========== 新增 JOIN:聚合该冲突课次涉及的所有班次/教员/教室信息 ========== */
LEFT JOIN "实施_课程表_学员队" xyd_link ON xyd_link."实施_课程表编号" = s."编号"
@@ -206,7 +207,7 @@
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 "实施_课程" c ON LOWER(RAWTOHEX(c."编号")) = LOWER(REPLACE(TRIM(s."实施_课程编号"), '-', ''))
LEFT JOIN "课表" k ON k."科目代码" = c."科目编号"
/* ========== 新增 JOIN:三类业务信息 ========== */
LEFT JOIN "实施_课程表_学员队" xyd_link ON xyd_link."实施_课程表编号" = s."编号"
@@ -2,20 +2,32 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.roomroot.jwgl.mapper.SSKCMapper">
<select id="selectBySemester" resultType="com.roomroot.jwgl.entity.SSKC">
<resultMap id="SSKCMap" type="com.roomroot.jwgl.entity.SSKC" autoMapping="true">
<id column="编号" property="bh" jdbcType="VARBINARY"
typeHandler="com.roomroot.jwgl.handler.DmGuidTypeHandler"/>
</resultMap>
<select id="selectBySemester" resultMap="SSKCMap">
SELECT * FROM 实施_课程
WHERE 年度 = #{nd} AND 学期第次 = #{xqdc} AND 停用 = 0
</select>
<select id="selectByClassBh" resultType="com.roomroot.jwgl.entity.SSKC">
<!--
达梦:实施_课程.编号 为 VARBINARY(16),实施_课程学员队.实施_课程编号 为 CHAR(36)。
禁止 编号 = 实施_课程编号:达梦会把 CHAR 当十六进制转 VARBINARY,UUID 中的 '-' 报 -6147。
统一:LOWER(RAWTOHEX(主键)) = LOWER(REPLACE(TRIM(外键), '-', ''))
-->
<select id="selectByClassBh" resultMap="SSKCMap">
SELECT DISTINCT sc.* FROM 实施_课程 sc
INNER JOIN 实施_课程学员队 scxd ON sc.编号 = scxd.实施_课程编号
INNER JOIN 实施_课程学员队 scxd
ON LOWER(RAWTOHEX(sc."编号")) = LOWER(REPLACE(TRIM(scxd."实施_课程编号"), '-', ''))
WHERE scxd.学员队编号 = #{classBh} AND sc.年度 = #{nd} AND sc.停用 = 0
</select>
<select id="selectByCourseBhAndClassBh" resultType="com.roomroot.jwgl.entity.SSKC">
<select id="selectByCourseBhAndClassBh" resultMap="SSKCMap">
SELECT sc.* FROM 实施_课程 sc
INNER JOIN 实施_课程学员队 scxd ON sc.编号 = scxd.实施_课程编号
INNER JOIN 实施_课程学员队 scxd
ON LOWER(RAWTOHEX(sc."编号")) = LOWER(REPLACE(TRIM(scxd."实施_课程编号"), '-', ''))
WHERE sc.课编号 = #{courseBh} AND scxd.学员队编号 = #{classBh} AND sc.年度 = #{nd} AND sc.停用 = 0
</select>
@@ -1,16 +0,0 @@
package com.roomroot.jwgl.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.roomroot.jwgl.entity.ScheduleAdjustStrategy;
import org.apache.ibatis.annotations.Mapper;
/**
* 实施_调课策略 Mapper。
* <p>
* 提供调课策略的基础 CRUD 能力,时间段限制与免审批策略的匹配查询
* 由 Service 层调用 BaseMapper 方法完成。
* </p>
*/
@Mapper
public interface ScheduleAdjustStrategyMapper extends BaseMapper<ScheduleAdjustStrategy> {
}
@@ -1,12 +1,14 @@
package com.roomroot.jwgl.mapper;
import com.roomroot.jwgl.vo.jwtest.PkConflictVO;
import com.roomroot.jwgl.vo.jwtest.PkLessonVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 排课测试全查。
* 排课测试全查与冲突检查。
*/
@Mapper
public interface TestPkMapper {
@@ -15,4 +17,24 @@ public interface TestPkMapper {
* 查询全部排课记录,无筛选条件。
*/
List<PkLessonVO> selectAll();
/**
* 同一班次同一日期节次被安排多门课程。
*/
List<PkConflictVO> selectClassTimeConflict(@Param("nd") Integer nd);
/**
* 同一班次同一日期节次必修课与选修课重叠。
*/
List<PkConflictVO> selectElectiveRequiredConflict(@Param("nd") Integer nd);
/**
* 同一教员同一日期节次被安排多门课程。
*/
List<PkConflictVO> selectTeacherTimeConflict(@Param("nd") Integer nd);
/**
* 同一教室同一日期节次被多门课程占用。
*/
List<PkConflictVO> selectClassroomTimeConflict(@Param("nd") Integer nd);
}
@@ -39,8 +39,8 @@
INNER JOIN "实施_课程表" s
ON TRIM(s."编号") = TRIM(tx."实施_课程表编号")
LEFT JOIN "实施_课程" c
ON REPLACE(LOWER(TRIM(CAST(c."编号" AS CHAR(36)))), '-', '')
= REPLACE(LOWER(TRIM(s."实施_课程编号")), '-', '')
ON LOWER(RAWTOHEX(c."编号"))
= LOWER(REPLACE(TRIM(s."实施_课程编号"), '-', ''))
LEFT JOIN "课表" k
ON k."课编号" = c."科目编号"
LEFT JOIN "课程表" kcb
@@ -64,4 +64,247 @@
GROUP BY s."编号", s."日期", s."节次", s."年度"
</select>
<!-- 课次明细:一门课一行,含课程/教员/班次/教室/节次时间 -->
<sql id="pkLessonRows">
SELECT
s."编号" AS kcbh,
TO_CHAR(s."日期", 'YYYY-MM-DD') AS skrq,
s."日期" AS rq,
s."节次" AS jc,
MAX(jc."开始时间") AS kssj,
MAX(jc."结束时间") AS jssj,
COALESCE(NULLIF(TRIM(MAX(s."教学内容")), ''), MAX(k."课名称")) AS ktmc,
MAX(k."课名称") AS kcmc,
MAX(NVL(k."课程类型", NVL(k."课类型", c."课类型"))) AS kclx,
COALESCE(WM_CONCAT(DISTINCT jyf."教员姓名"), MAX(jy."教员姓名")) AS jymc,
WM_CONCAT(DISTINCT xyd."学员队名称") AS bjmc,
COALESCE(WM_CONCAT(DISTINCT jsb."教室名称"), MAX(js."教室名称")) AS jsmc
FROM "实施_课程表" s
LEFT JOIN "实施_课程" c
ON LOWER(RAWTOHEX(c."编号")) = LOWER(REPLACE(TRIM(s."实施_课程编号"), '-', ''))
LEFT JOIN "课表" k
ON (k."课编号" = c."科目编号" OR k."科目代码" = c."科目编号")
LEFT JOIN "课程表" kcb
ON kcb."年度" = s."年度"
AND kcb."安排时间" = s."计划时间安排"
AND TRIM(kcb."教员编号") = TRIM(c."教员编号")
LEFT JOIN "教员表" jy
ON TRIM(jy."教员编号") = TRIM(NVL(kcb."授课教员编号", c."教员编号"))
LEFT JOIN "实施_课程表_辅助教员" fz ON TRIM(fz."实施_课程表编号") = TRIM(s."编号")
LEFT JOIN "教员表" jyf ON TRIM(jyf."教员编号") = TRIM(fz."辅助教员编号")
LEFT JOIN "实施_课程表_学员队" tx ON TRIM(tx."实施_课程表编号") = TRIM(s."编号")
LEFT JOIN "学员队表" xyd ON xyd."学员队编号" = tx."学员队编号"
LEFT JOIN "实施_课程表_教室" jsx ON TRIM(jsx."实施_课程表编号") = TRIM(s."编号")
LEFT JOIN "教室表" jsb ON jsb."教室编号" = jsx."教室编号"
LEFT JOIN "教室表" js ON js."教室编号" = NVL(kcb."教室编号", c."教室编号")
LEFT JOIN "节次时间表" jc ON TRIM(jc."节次索引") = TRIM(CAST(s."节次" AS VARCHAR))
WHERE NVL(s."删除状态", 0) = 0
<if test="nd != null">
AND s."年度" = #{nd}
</if>
GROUP BY s."编号", s."日期", s."节次"
</sql>
<!-- 1. 同一班次同一时间被安排多门课程 -->
<select id="selectClassTimeConflict" resultType="com.roomroot.jwgl.vo.jwtest.PkConflictVO">
WITH lesson AS (
<include refid="pkLessonRows"/>
)
SELECT
'教学班时间冲突' AS ctlx,
COALESCE(xyd."学员队名称", ta."学员队编号") AS ctzy,
a.skrq AS skrq,
a.jc AS jc,
a.kssj AS kssj,
a.jssj AS jssj,
'【' || COALESCE(xyd."学员队名称", ta."学员队编号") || '】在 '
|| a.skrq || ' 第' || CAST(a.jc AS VARCHAR) || '节('
|| NVL(a.kssj, '-') || '~' || NVL(a.jssj, '-') || ')同时安排了《'
|| NVL(a.kcmc, NVL(a.ktmc, '未命名')) || '》(教员:' || NVL(a.jymc, '无')
|| ',教室:' || NVL(a.jsmc, '无') || ')与《'
|| NVL(b.kcmc, NVL(b.ktmc, '未命名')) || '》(教员:' || NVL(b.jymc, '无')
|| ',教室:' || NVL(b.jsmc, '无') || ')' AS sm,
a.kcbh AS aKcBh, a.ktmc AS aKtmc, a.kcmc AS aKcmc, a.kclx AS aKclx,
a.jymc AS aJymc, a.bjmc AS aBjmc, a.jsmc AS aJsmc,
b.kcbh AS bKcBh, b.ktmc AS bKtmc, b.kcmc AS bKcmc, b.kclx AS bKclx,
b.jymc AS bJymc, b.bjmc AS bBjmc, b.jsmc AS bJsmc
FROM lesson a
INNER JOIN lesson b
ON TRUNC(a.rq) = TRUNC(b.rq)
AND a.jc = b.jc
AND TRIM(a.kcbh) &lt; TRIM(b.kcbh)
INNER JOIN "实施_课程表_学员队" ta ON TRIM(ta."实施_课程表编号") = TRIM(a.kcbh)
INNER JOIN "实施_课程表_学员队" tb
ON TRIM(tb."实施_课程表编号") = TRIM(b.kcbh)
AND ta."学员队编号" = tb."学员队编号"
LEFT JOIN "学员队表" xyd ON xyd."学员队编号" = ta."学员队编号"
ORDER BY a.rq, a.jc, ta."学员队编号"
</select>
<!-- 2. 同一班次同一时间必修课与选修课冲突 -->
<select id="selectElectiveRequiredConflict" resultType="com.roomroot.jwgl.vo.jwtest.PkConflictVO">
WITH lesson AS (
<include refid="pkLessonRows"/>
)
SELECT
'必修选修时间冲突' AS ctlx,
COALESCE(xyd."学员队名称", ta."学员队编号") AS ctzy,
a.skrq AS skrq,
a.jc AS jc,
a.kssj AS kssj,
a.jssj AS jssj,
'【' || COALESCE(xyd."学员队名称", ta."学员队编号") || '】在 '
|| a.skrq || ' 第' || CAST(a.jc AS VARCHAR) || '节('
|| NVL(a.kssj, '-') || '~' || NVL(a.jssj, '-') || ')必修课《'
|| NVL(CASE WHEN INSTR(NVL(a.kclx, ''), '选修') &gt; 0 THEN b.kcmc ELSE a.kcmc END,
'未命名')
|| '》与选修课《'
|| NVL(CASE WHEN INSTR(NVL(a.kclx, ''), '选修') &gt; 0 THEN a.kcmc ELSE b.kcmc END,
'未命名')
|| '》时间重叠;教员分别为 '
|| NVL(a.jymc, '无') || ' / ' || NVL(b.jymc, '无')
|| ',教室分别为 '
|| NVL(a.jsmc, '无') || ' / ' || NVL(b.jsmc, '无') AS sm,
a.kcbh AS aKcBh, a.ktmc AS aKtmc, a.kcmc AS aKcmc, a.kclx AS aKclx,
a.jymc AS aJymc, a.bjmc AS aBjmc, a.jsmc AS aJsmc,
b.kcbh AS bKcBh, b.ktmc AS bKtmc, b.kcmc AS bKcmc, b.kclx AS bKclx,
b.jymc AS bJymc, b.bjmc AS bBjmc, b.jsmc AS bJsmc
FROM lesson a
INNER JOIN lesson b
ON TRUNC(a.rq) = TRUNC(b.rq)
AND a.jc = b.jc
AND TRIM(a.kcbh) &lt; TRIM(b.kcbh)
INNER JOIN "实施_课程表_学员队" ta ON TRIM(ta."实施_课程表编号") = TRIM(a.kcbh)
INNER JOIN "实施_课程表_学员队" tb
ON TRIM(tb."实施_课程表编号") = TRIM(b.kcbh)
AND ta."学员队编号" = tb."学员队编号"
LEFT JOIN "学员队表" xyd ON xyd."学员队编号" = ta."学员队编号"
WHERE (
INSTR(NVL(a.kclx, ''), '必修') &gt; 0 AND INSTR(NVL(b.kclx, ''), '选修') &gt; 0
) OR (
INSTR(NVL(a.kclx, ''), '选修') &gt; 0 AND INSTR(NVL(b.kclx, ''), '必修') &gt; 0
)
ORDER BY a.rq, a.jc, ta."学员队编号"
</select>
<!-- 3. 同一教员同一时间被安排多门课程 -->
<select id="selectTeacherTimeConflict" resultType="com.roomroot.jwgl.vo.jwtest.PkConflictVO">
WITH lesson AS (
<include refid="pkLessonRows"/>
),
teacher_link AS (
SELECT TRIM(s."编号") AS kcbh, TRIM(NVL(kcb."授课教员编号", c."教员编号")) AS jybh
FROM "实施_课程表" s
LEFT JOIN "实施_课程" c
ON LOWER(RAWTOHEX(c."编号")) = LOWER(REPLACE(TRIM(s."实施_课程编号"), '-', ''))
LEFT JOIN "课程表" kcb
ON kcb."年度" = s."年度"
AND kcb."安排时间" = s."计划时间安排"
AND TRIM(kcb."教员编号") = TRIM(c."教员编号")
WHERE NVL(s."删除状态", 0) = 0
AND TRIM(NVL(kcb."授课教员编号", c."教员编号")) IS NOT NULL
AND TRIM(NVL(kcb."授课教员编号", c."教员编号")) &lt;&gt; ''
<if test="nd != null">
AND s."年度" = #{nd}
</if>
UNION
SELECT TRIM(fz."实施_课程表编号") AS kcbh, TRIM(fz."辅助教员编号") AS jybh
FROM "实施_课程表_辅助教员" fz
INNER JOIN "实施_课程表" s ON TRIM(s."编号") = TRIM(fz."实施_课程表编号")
WHERE NVL(s."删除状态", 0) = 0
AND TRIM(fz."辅助教员编号") IS NOT NULL
AND TRIM(fz."辅助教员编号") &lt;&gt; ''
<if test="nd != null">
AND s."年度" = #{nd}
</if>
)
SELECT
'教员时间冲突' AS ctlx,
COALESCE(jy."教员姓名", ta.jybh) AS ctzy,
a.skrq AS skrq,
a.jc AS jc,
a.kssj AS kssj,
a.jssj AS jssj,
'教员【' || COALESCE(jy."教员姓名", ta.jybh) || '】在 '
|| a.skrq || ' 第' || CAST(a.jc AS VARCHAR) || '节('
|| NVL(a.kssj, '-') || '~' || NVL(a.jssj, '-') || ')同时被安排《'
|| NVL(a.kcmc, NVL(a.ktmc, '未命名')) || '》(班级:' || NVL(a.bjmc, '无')
|| ',教室:' || NVL(a.jsmc, '无') || ')与《'
|| NVL(b.kcmc, NVL(b.ktmc, '未命名')) || '》(班级:' || NVL(b.bjmc, '无')
|| ',教室:' || NVL(b.jsmc, '无') || ')' AS sm,
a.kcbh AS aKcBh, a.ktmc AS aKtmc, a.kcmc AS aKcmc, a.kclx AS aKclx,
a.jymc AS aJymc, a.bjmc AS aBjmc, a.jsmc AS aJsmc,
b.kcbh AS bKcBh, b.ktmc AS bKtmc, b.kcmc AS bKcmc, b.kclx AS bKclx,
b.jymc AS bJymc, b.bjmc AS bBjmc, b.jsmc AS bJsmc
FROM lesson a
INNER JOIN lesson b
ON TRUNC(a.rq) = TRUNC(b.rq)
AND a.jc = b.jc
AND TRIM(a.kcbh) &lt; TRIM(b.kcbh)
INNER JOIN teacher_link ta ON TRIM(ta.kcbh) = TRIM(a.kcbh)
INNER JOIN teacher_link tb ON TRIM(tb.kcbh) = TRIM(b.kcbh) AND ta.jybh = tb.jybh
LEFT JOIN "教员表" jy ON TRIM(jy."教员编号") = ta.jybh
ORDER BY a.rq, a.jc, ta.jybh
</select>
<!-- 4. 同一教室同一时间被多门课程占用 -->
<select id="selectClassroomTimeConflict" resultType="com.roomroot.jwgl.vo.jwtest.PkConflictVO">
WITH lesson AS (
<include refid="pkLessonRows"/>
),
room_link AS (
SELECT TRIM(s."编号") AS kcbh, TRIM(NVL(kcb."教室编号", c."教室编号")) AS jsbh
FROM "实施_课程表" s
LEFT JOIN "实施_课程" c
ON LOWER(RAWTOHEX(c."编号")) = LOWER(REPLACE(TRIM(s."实施_课程编号"), '-', ''))
LEFT JOIN "课程表" kcb
ON kcb."年度" = s."年度"
AND kcb."安排时间" = s."计划时间安排"
AND TRIM(kcb."教员编号") = TRIM(c."教员编号")
WHERE NVL(s."删除状态", 0) = 0
AND TRIM(NVL(kcb."教室编号", c."教室编号")) IS NOT NULL
AND TRIM(NVL(kcb."教室编号", c."教室编号")) &lt;&gt; ''
<if test="nd != null">
AND s."年度" = #{nd}
</if>
UNION
SELECT TRIM(jsx."实施_课程表编号") AS kcbh, TRIM(jsx."教室编号") AS jsbh
FROM "实施_课程表_教室" jsx
INNER JOIN "实施_课程表" s ON TRIM(s."编号") = TRIM(jsx."实施_课程表编号")
WHERE NVL(s."删除状态", 0) = 0
AND TRIM(jsx."教室编号") IS NOT NULL
AND TRIM(jsx."教室编号") &lt;&gt; ''
<if test="nd != null">
AND s."年度" = #{nd}
</if>
)
SELECT
'教室时间冲突' AS ctlx,
COALESCE(js."教室名称", ta.jsbh) AS ctzy,
a.skrq AS skrq,
a.jc AS jc,
a.kssj AS kssj,
a.jssj AS jssj,
'教室【' || COALESCE(js."教室名称", ta.jsbh) || '】在 '
|| a.skrq || ' 第' || CAST(a.jc AS VARCHAR) || '节('
|| NVL(a.kssj, '-') || '~' || NVL(a.jssj, '-') || ')同时被《'
|| NVL(a.kcmc, NVL(a.ktmc, '未命名')) || '》(教员:' || NVL(a.jymc, '无')
|| ',班级:' || NVL(a.bjmc, '无') || ')与《'
|| NVL(b.kcmc, NVL(b.ktmc, '未命名')) || '》(教员:' || NVL(b.jymc, '无')
|| ',班级:' || NVL(b.bjmc, '无') || ')占用' AS sm,
a.kcbh AS aKcBh, a.ktmc AS aKtmc, a.kcmc AS aKcmc, a.kclx AS aKclx,
a.jymc AS aJymc, a.bjmc AS aBjmc, a.jsmc AS aJsmc,
b.kcbh AS bKcBh, b.ktmc AS bKtmc, b.kcmc AS bKcmc, b.kclx AS bKclx,
b.jymc AS bJymc, b.bjmc AS bBjmc, b.jsmc AS bJsmc
FROM lesson a
INNER JOIN lesson b
ON TRUNC(a.rq) = TRUNC(b.rq)
AND a.jc = b.jc
AND TRIM(a.kcbh) &lt; TRIM(b.kcbh)
INNER JOIN room_link ta ON TRIM(ta.kcbh) = TRIM(a.kcbh)
INNER JOIN room_link tb ON TRIM(tb.kcbh) = TRIM(b.kcbh) AND ta.jsbh = tb.jsbh
LEFT JOIN "教室表" js ON TRIM(js."教室编号") = ta.jsbh
ORDER BY a.rq, a.jc, ta.jsbh
</select>
</mapper>
@@ -216,7 +216,7 @@
LEFT JOIN "教员表" j ON j."教员编号" = sp."审批人编号"
LEFT JOIN "请假课堂表" qjkt ON qjkt."请假编号" = q."编号"
LEFT JOIN "实施_课程表" sskcb ON sskcb."编号" = qjkt."实施_课堂编号"
LEFT JOIN "实施_课程" sskc ON sskcb."实施_课程编号" = sskc."编号"
LEFT JOIN "实施_课程" sskc ON LOWER(RAWTOHEX(sskc."编号")) = LOWER(REPLACE(TRIM(sskcb."实施_课程编号"), '-', ''))
LEFT JOIN "课表" kb ON sskc."科目编号" = kb."课编号"
WHERE q."编号" = #{qjbh}
</select>
@@ -18,7 +18,6 @@ import com.roomroot.jwgl.dto.courserunning.StudentLeaveApplyDTO;
import com.roomroot.jwgl.dto.courserunning.StudentLeaveAuditDTO;
import com.roomroot.jwgl.dto.courserunning.StudentLeaveQueryDTO;
import com.roomroot.jwgl.dto.courserunning.OperationAnalysisQueryDTO;
import com.roomroot.jwgl.dto.courserunning.ScheduleAdjustStrategyDTO;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.vo.courserunning.AdjustableLessonVO;
import com.roomroot.jwgl.vo.courserunning.CourseRunningImportResultVO;
@@ -37,7 +36,6 @@ import com.roomroot.jwgl.vo.courserunning.OperationOverviewVO;
import com.roomroot.jwgl.vo.courserunning.CourseCompletionByDeptVO;
import com.roomroot.jwgl.vo.courserunning.VenueUsageRateVO;
import com.roomroot.jwgl.vo.courserunning.ScheduleAdjustTrendVO;
import com.roomroot.jwgl.vo.courserunning.ScheduleAdjustStrategyVO;
import java.util.List;
@@ -215,101 +213,23 @@ public interface CourseRunningImportService {
/**
* 教研室审批调课申请。
* <p>
* 教研室审批教员提交的调课申请,包含同意、发回、拒绝三种处理方式。
* 同意时同步更新课次数据,发回时记录发回原因,拒绝时标记申请为已拒绝。
* 教研室审批调课申请。点审批即已查收,审批人同时记为查收人。
* 同意后写回课次;发回、拒绝也记为已查收。
* </p>
*
* @param dto 教研室审批参数
*/
void auditByTeachingOffice(ScheduleAdjustAuditDTO dto);
/**
* 教研室查收调课申请。
* <p>
* 教研室查收调课申请,标记查收状态和查收时间。
* </p>
*
* @param ssdksqbh 调课申请编号
* @param csrbh 查收人编号
*/
void receiveByTeachingOffice(String ssdksqbh, String csrbh);
/**
* 教学系审批调课申请。
* <p>
* 教学系审批教研室已同意的调课申请,包含同意、发回、拒绝三种处理方式。
* 教学系同意后流转到机关审批。
* </p>
*
* @param dto 教学系审批参数
*/
void auditByTeachingDepartment(ScheduleAdjustAuditDTO dto);
/**
* 机关审批调课申请。
* <p>
* 机关审批教研室已同意的调课申请,包含同意、发回、拒绝三种处理方式。
* 机关全部同意后调课申请最终生效,课次按调整后的内容执行。
* </p>
*
* @param dto 机关审批参数
*/
void auditByAuthority(ScheduleAdjustAuditDTO dto);
/**
* 机关查收调课申请。
* <p>
* 机关查收调课申请,标记查收状态和查收时间。
* </p>
*
* @param ssdksqbh 调课申请编号
* @param jgbh 机关编号
* @param csrbh 查收人编号
*/
void receiveByAuthority(String ssdksqbh, String jgbh, String csrbh);
/**
* 撤销调课申请。
* <p>
* 教员撤销自己提交的、未经过审批的调课申请。
* 软删除调课申请,标记删除状态为1。
* 教员撤销未经过审批的调课申请。列表已按登录教员过滤。
* </p>
*
* @param ssdksqbh 调课申请编号
* @param sqjybh 申请教员编号(用于权限校验)
*/
void cancelScheduleAdjust(String ssdksqbh, String sqjybh);
// ==================== 调课策略管理 ====================
/**
* 查询调课策略列表。
* <p>
* 支持按策略类型筛选,返回所有策略(含停用)。
* </p>
*
* @param cllx 策略类型:TIME_LIMIT/AUTO_APPROVE,为空时查询全部
* @return 调课策略列表
*/
List<ScheduleAdjustStrategyVO> listScheduleAdjustStrategies(String cllx);
/**
* 保存调课策略(新增或修改)。
* <p>
* 策略编号为空时执行新增,非空时执行修改。
* </p>
*
* @param dto 策略参数
* @return 策略编号
*/
String saveScheduleAdjustStrategy(ScheduleAdjustStrategyDTO dto);
/**
* 删除调课策略。
*
* @param clbh 策略编号
*/
void deleteScheduleAdjustStrategy(String clbh);
void cancelScheduleAdjust(String ssdksqbh);
// ==================== 教学保障管理 ====================
@@ -76,6 +76,16 @@ public interface FacultyService {
*/
JYB getJYBById(String jYBH);
/**
* 从 Excel(.xls/.xlsx) 导入教员
*/
int importJYBFromExcel(MultipartFile file) throws Exception;
/**
* 导出教员到 Excel(.xlsx)
*/
void exportJYBExcel(jakarta.servlet.http.HttpServletResponse response) throws Exception;
/**
* 分页条件查询教员列表
*/
@@ -53,4 +53,10 @@ public interface TrainingProgramService {
* @return 分页结果
*/
PageResult<ZYB> pageList(PageQuery query, ZYB cond);
/** 从 Excel(.xls/.xlsx) 导入培养方案(专业) */
int importZYBFromExcel(org.springframework.web.multipart.MultipartFile file) throws Exception;
/** 导出培养方案(专业)到 Excel(.xlsx) */
void exportZYBExcel(jakarta.servlet.http.HttpServletResponse response) throws Exception;
}
@@ -60,4 +60,10 @@ public interface ZYJXJHBService {
* @return 列表
*/
List<ZYJXJHB> listByZydhAndTy(String zydh, Integer ty);
/** 从 Excel(.xls/.xlsx) 导入专业教学计划 */
int importZYJXJHBFromExcel(org.springframework.web.multipart.MultipartFile file) throws Exception;
/** 导出专业教学计划到 Excel(.xlsx) */
void exportZYJXJHBExcel(jakarta.servlet.http.HttpServletResponse response) throws Exception;
}
@@ -112,12 +112,6 @@ public class CourseRunningImportServiceImpl implements CourseRunningImportServic
@Resource
private KCBMapper kcbMapper;
/**
* 实施_调课申请审批 Mapper,用于调课申请审批记录的数据访问。
*/
@Resource
private SSDKSQSPMapper ssdksqspMapper;
/**
* 调课申请_教室 Mapper,用于调课申请中教室关联的数据访问。
*/
@@ -196,12 +190,6 @@ public class CourseRunningImportServiceImpl implements CourseRunningImportServic
@Resource
private OperationAnalysisMapper operationAnalysisMapper;
/**
* 调课策略 Mapper,用于调课时间段限制策略与免审批策略的数据访问。
*/
@Resource
private ScheduleAdjustStrategyMapper scheduleAdjustStrategyMapper;
/**
* 教员表数据访问接口,用于审批时根据教员编号查询教研室代号。
*/
@@ -533,16 +521,17 @@ public class CourseRunningImportServiceImpl implements CourseRunningImportServic
* @param operationContent 操作内容描述
*/
private void recordOperationLog(String targetId, String operationType, String operationContent) {
// 创建日志实体
KCJXYX_CZRZ log = new KCJXYX_CZRZ();
log.setBh(UuidUtil.getOriginalUUID()); // 生成唯一编号
log.setTybh(targetId); // 目标编号
log.setCzlx(operationType); // 操作类型
log.setCznr(operationContent); // 操作内容
log.setCzsj(LocalDateTime.now()); // 操作时间
// 写入数据库
kcjxyxCzrzMapper.insert(log);
try {
KCJXYX_CZRZ log = new KCJXYX_CZRZ();
log.setBh(UuidUtil.getOriginalUUID());
log.setTybh(targetId);
log.setCzlx(operationType);
log.setCznr(operationContent);
log.setCzsj(LocalDateTime.now());
kcjxyxCzrzMapper.insert(log);
} catch (Exception ignored) {
// 操作日志表缺失或字段不匹配时,不影响调课主流程
}
}
// ==================== 教学实施计划填写 ====================
@@ -895,7 +884,6 @@ public class CourseRunningImportServiceImpl implements CourseRunningImportServic
query.getNd(),
query.getJyspzzt(),
query.getJyscszt(),
query.getJgspzt(),
query.getSczt(),
cjsjStartStr,
cjsjEndStr
@@ -910,61 +898,22 @@ public class CourseRunningImportServiceImpl implements CourseRunningImportServic
@Override
public List<AdjustableLessonVO> listAdjustableLessons(Integer nd) {
if (jwglRoleHelper.isDepartmentPersonnel()) {
throw new BusinessException(BasicManagementConstants.FORBIDDEN, "机关人员仅可查看和审批调课申请");
}
if (jwglRoleHelper.isResearchOffice()) {
throw new BusinessException(BasicManagementConstants.FORBIDDEN, "教研室仅可查看和审批本室调课申请");
}
String teacherId = jwglRoleHelper.requireTeacherId();
return ssdksqMapper.selectAdjustableLessons(teacherId, nd);
return ssdksqMapper.selectAdjustableLessons(jwglRoleHelper.requireTeacherId(), nd);
}
private void applyScheduleAdjustDataScope(ScheduleAdjustQueryDTO query) {
if (jwglRoleHelper.isAdmin() || jwglRoleHelper.isDepartmentPersonnel()) {
return;
}
if (jwglRoleHelper.isResearchOffice()) {
query.setJysdh(jwglRoleHelper.requireResearchOfficeId());
query.setSqjybh(null);
return;
}
if (jwglRoleHelper.isTeacher()) {
query.setSqjybh(jwglRoleHelper.requireTeacherId());
query.setJysdh(null);
return;
}
throw new BusinessException(BasicManagementConstants.FORBIDDEN, "无权查看调课申请");
}
private void assertCanViewScheduleAdjust(SSDKSQ apply) {
if (jwglRoleHelper.isAdmin() || jwglRoleHelper.isDepartmentPersonnel()) {
return;
}
if (jwglRoleHelper.isResearchOffice()) {
String officeId = jwglRoleHelper.requireResearchOfficeId();
String applyOffice = apply.getJysdh();
if (StringUtils.isEmpty(applyOffice) && StringUtils.isNotEmpty(apply.getSqjybh())) {
JYB teacher = jybMapper.selectById(apply.getSqjybh());
applyOffice = teacher == null ? null : teacher.getJysdh();
}
if (applyOffice == null || !officeId.equals(applyOffice.trim())) {
throw new BusinessException(BasicManagementConstants.FORBIDDEN, "只能查看本教研室的调课申请");
}
return;
}
if (jwglRoleHelper.isTeacher()) {
String teacherId = jwglRoleHelper.requireTeacherId();
if (apply.getSqjybh() == null || !teacherId.equals(apply.getSqjybh().trim())) {
throw new BusinessException(BasicManagementConstants.FORBIDDEN, "只能查看本人提交的调课申请");
}
return;
}
throw new BusinessException(BasicManagementConstants.FORBIDDEN, "无权查看调课申请");
}
private String currentOperatorId() {
try {
String teacherId = jwglRoleHelper.findTeacherId();
if (StringUtils.isNotEmpty(teacherId)) {
return teacherId.trim();
}
String username = SecurityUtils.getUsername();
if (StringUtils.isNotEmpty(username)) {
return username;
@@ -987,37 +936,17 @@ public class CourseRunningImportServiceImpl implements CourseRunningImportServic
*/
@Override
public ScheduleAdjustDetailVO getScheduleAdjustDetail(String ssdksqbh) {
// 步骤1:校验调课申请编号参数
if (ssdksqbh == null || ssdksqbh.trim().isEmpty()) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "调课申请编号不能为空");
}
// 步骤2:查询调课申请主表信息
ScheduleAdjustDetailVO detail = ssdksqMapper.selectDetailById(ssdksqbh);
if (detail == null) {
throw new BusinessException(BasicManagementConstants.NOT_FOUND, "未找到该调课申请");
}
SSDKSQ apply = ssdksqMapper.selectById(ssdksqbh);
if (apply != null) {
assertCanViewScheduleAdjust(apply);
}
// 步骤3:查询关联的机关审批记录列表
detail.setAuditList(ssdksqspMapper.selectBySsdksqbh(ssdksqbh));
// 步骤4:查询关联的新教室列表
detail.setRoomList(dksqjsMapper.selectByDksqbh(ssdksqbh));
// 步骤5:查询关联的新辅助教员列表
detail.setTeacherList(dksqfzjyMapper.selectByDksqbh(ssdksqbh));
// 步骤6:查询关联的新学员队列表
detail.setTeamList(dksqxydMapper.selectByDksqbh(ssdksqbh));
// 步骤7:查询关联的新保障明细列表
detail.setSupportList(dksqbzmxMapper.selectByDksqbh(ssdksqbh));
// 步骤8:返回调课申请完整详情
return detail;
}
@@ -1036,30 +965,11 @@ public class CourseRunningImportServiceImpl implements CourseRunningImportServic
@Override
@Transactional(rollbackFor = Exception.class)
public String submitScheduleAdjust(ScheduleAdjustApplyDTO dto) {
if (dto == null) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "调课申请参数不能为空");
}
if (jwglRoleHelper.isDepartmentPersonnel()) {
throw new BusinessException(BasicManagementConstants.FORBIDDEN, "机关人员仅可查看和审批,不能提交调课申请");
}
if (jwglRoleHelper.isResearchOffice()) {
throw new BusinessException(BasicManagementConstants.FORBIDDEN, "教研室仅可查看和审批本室申请,不能提交调课申请");
}
if (StringUtils.isEmpty(dto.getSskcbbh())) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "课次编号不能为空");
}
if (dto.getSy() == null || dto.getSy().trim().isEmpty()) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "调课事由不能为空");
}
String teacherId = jwglRoleHelper.requireTeacherId();
SSKCB lesson = sskcbMapper.selectById(dto.getSskcbbh().trim());
if (lesson == null) {
if (lesson == null || (lesson.getSczt() != null && lesson.getSczt() == 1)) {
throw new BusinessException(BasicManagementConstants.NOT_FOUND, "课次不存在");
}
if (lesson.getSczt() != null && lesson.getSczt() == 1) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该课次已删除,不允许申请调课");
}
AdjustableLessonVO picked = null;
List<AdjustableLessonVO> mine = ssdksqMapper.selectAdjustableLessons(teacherId, lesson.getNd());
@@ -1072,59 +982,55 @@ public class CourseRunningImportServiceImpl implements CourseRunningImportServic
}
}
if (picked == null) {
throw new BusinessException(BasicManagementConstants.FORBIDDEN, "只能对自己任课的课次申请调课");
throw new BusinessException(BasicManagementConstants.NOT_FOUND, "课次不存在");
}
validateScheduleAdjustTimeLimitStrategy(dto.getRq());
boolean autoApproved = matchAutoApproveStrategy();
SSDKSQ apply = new SSDKSQ();
String ssdksqbh = UuidUtil.getOriginalUUID();
apply.setBh(ssdksqbh);
apply.setSskcbbh(lesson.getBh());
apply.setXydrwbh(StringUtils.isNotEmpty(dto.getXydrwbh()) ? dto.getXydrwbh() : picked.getXydrwbh());
apply.setKbh(StringUtils.isNotEmpty(dto.getKbh()) ? dto.getKbh() : picked.getKbh());
apply.setKcbbh(StringUtils.isNotEmpty(dto.getKcbbh()) ? dto.getKcbbh() : picked.getKcbbh());
apply.setJysdh(picked.getJysdh());
apply.setYjsbh(picked.getJsbh());
apply.setXjsbh(resolveNewClassroom(dto));
apply.setSqjybh(teacherId);
apply.setSy(dto.getSy());
apply.setYdsjap(resolveYdsjap(lesson));
apply.setZxzt(0);
apply.setYszt(0);
apply.setXydrwbh(cut(StringUtils.isNotEmpty(dto.getXydrwbh()) ? dto.getXydrwbh() : picked.getXydrwbh(), 50));
apply.setKbh(cut(StringUtils.isNotEmpty(dto.getKbh()) ? dto.getKbh() : picked.getKbh(), 50));
apply.setKcbbh(cut(StringUtils.isNotEmpty(dto.getKcbbh()) ? dto.getKcbbh() : picked.getKcbbh(), 50));
apply.setJysdh(cut(picked.getJysdh(), 50));
apply.setYjsbh(cut(picked.getJsbh(), 50));
apply.setXjsbh(cut(resolveNewClassroom(dto), 50));
apply.setSqjybh(cut(teacherId, 50));
apply.setSy(cut(dto.getSy(), 200));
apply.setRq(dto.getRq());
apply.setJc(dto.getJc());
apply.setNd(dto.getNd() != null ? dto.getNd() : lesson.getNd());
apply.setJxnr(dto.getJxnr());
apply.setJc(dto.getJc() != null ? dto.getJc() : 0);
apply.setNd(dto.getNd() != null ? dto.getNd() : (lesson.getNd() != null ? lesson.getNd() : 0));
apply.setJxnr(cut(dto.getJxnr(), 500));
apply.setJxyd(dto.getJxyd());
apply.setJxff(dto.getJxff());
String jxff = cut(dto.getJxff(), 50);
apply.setJxff(jxff == null ? "" : jxff);
apply.setJxbzbz(dto.getJxbzbz());
apply.setYcxx(dto.getYcxx());
String originalDesc = buildOriginalScheduleDesc(lesson);
if (dto.getBz() != null && !dto.getBz().trim().isEmpty()) {
apply.setBz(dto.getBz() + " | " + originalDesc);
} else {
apply.setBz(originalDesc);
}
if (autoApproved) {
apply.setBz((apply.getBz() == null ? "" : apply.getBz() + " | ") + "[免审批策略自动通过]");
apply.setJyspzzt(1);
} else {
apply.setJyspzzt(0);
}
apply.setBz(cut(dto.getBz(), 50));
apply.setJyspzzt(0);
apply.setJyscszt(0);
apply.setSczt(0);
apply.setFhzt(0);
apply.setDk(1);
LocalDateTime now = LocalDateTime.now();
apply.setCjsj(now);
apply.setCjsj(LocalDateTime.now());
ssdksqMapper.insert(apply);
if (dto.getRoomList() != null && !dto.getRoomList().isEmpty()) {
for (AdjustRoomDTO roomDto : dto.getRoomList()) {
if (StringUtils.isEmpty(roomDto.getJsbh())) {
continue;
}
DKSQJS roomEntity = new DKSQJS();
roomEntity.setBh(UuidUtil.getOriginalUUID());
roomEntity.setDksqbh(ssdksqbh);
roomEntity.setJsbh(roomDto.getJsbh());
roomEntity.setBz(roomDto.getBz());
roomEntity.setNd(apply.getNd());
roomEntity.setRq(apply.getRq());
roomEntity.setJc(apply.getJc());
dksqjsMapper.insert(roomEntity);
}
} else if (StringUtils.isNotEmpty(apply.getXjsbh())) {
@@ -1132,44 +1038,61 @@ public class CourseRunningImportServiceImpl implements CourseRunningImportServic
roomEntity.setBh(UuidUtil.getOriginalUUID());
roomEntity.setDksqbh(ssdksqbh);
roomEntity.setJsbh(apply.getXjsbh());
roomEntity.setNd(apply.getNd());
roomEntity.setRq(apply.getRq());
roomEntity.setJc(apply.getJc());
dksqjsMapper.insert(roomEntity);
}
if (dto.getTeacherList() != null && !dto.getTeacherList().isEmpty()) {
for (AdjustTeacherDTO teacherDto : dto.getTeacherList()) {
if (StringUtils.isEmpty(teacherDto.getFzjybh())) {
continue;
}
DKSQFZJY teacherEntity = new DKSQFZJY();
teacherEntity.setBh(UuidUtil.getOriginalUUID());
teacherEntity.setDksqbh(ssdksqbh);
teacherEntity.setFzjybh(teacherDto.getFzjybh());
teacherEntity.setZjy(teacherDto.getZjy());
teacherEntity.setZjy(teacherDto.getZjy() == null ? 0 : teacherDto.getZjy());
teacherEntity.setBz(teacherDto.getBz());
teacherEntity.setNd(apply.getNd());
teacherEntity.setRq(apply.getRq());
teacherEntity.setJc(apply.getJc());
dksqfzjyMapper.insert(teacherEntity);
}
}
if (dto.getTeamList() != null && !dto.getTeamList().isEmpty()) {
for (AdjustTeamDTO teamDto : dto.getTeamList()) {
if (StringUtils.isEmpty(teamDto.getXydbh())) {
continue;
}
DKSQXYD teamEntity = new DKSQXYD();
teamEntity.setBh(UuidUtil.getOriginalUUID());
teamEntity.setDksqbh(ssdksqbh);
teamEntity.setXydbh(teamDto.getXydbh());
teamEntity.setNd(apply.getNd());
teamEntity.setRq(apply.getRq());
teamEntity.setJc(apply.getJc());
dksqxydMapper.insert(teamEntity);
}
}
if (dto.getSupportList() != null && !dto.getSupportList().isEmpty()) {
for (AdjustSupportDTO supportDto : dto.getSupportList()) {
if (StringUtils.isEmpty(supportDto.getBztgmxbh()) || supportDto.getSl() == null) {
continue;
}
DKSQBZMX supportEntity = new DKSQBZMX();
supportEntity.setBh(UuidUtil.getOriginalUUID());
supportEntity.setSqbh(ssdksqbh);
supportEntity.setBztgmxbh(supportDto.getBztgmxbh());
supportEntity.setSl(supportDto.getSl());
supportEntity.setBz(supportDto.getBz());
supportEntity.setNd(apply.getNd());
supportEntity.setRq(apply.getRq());
supportEntity.setJc(apply.getJc());
dksqbzmxMapper.insert(supportEntity);
}
}
if (autoApproved) {
applyApprovedSchedule(apply);
}
recordOperationLog(ssdksqbh, "申请", "提交调课申请:" + ssdksqbh);
return ssdksqbh;
}
@@ -1203,6 +1126,32 @@ public class CourseRunningImportServiceImpl implements CourseRunningImportServic
return kcbMapper.selectOne(wrapper);
}
private int resolveYdsjap(SSKCB lesson) {
if (lesson == null) {
return 0;
}
if (lesson.getJhsjap() != null) {
return lesson.getJhsjap();
}
if (lesson.getRq() != null) {
return lesson.getRq().getYear() * 10000
+ lesson.getRq().getMonthValue() * 100
+ lesson.getRq().getDayOfMonth();
}
return 0;
}
private String cut(String value, int max) {
if (value == null) {
return null;
}
String text = value.trim();
if (text.isEmpty()) {
return null;
}
return text.length() <= max ? text : text.substring(0, max);
}
private String resolveNewClassroom(ScheduleAdjustApplyDTO dto) {
if (StringUtils.isNotEmpty(dto.getXjsbh())) {
return dto.getXjsbh().trim();
@@ -1217,8 +1166,8 @@ public class CourseRunningImportServiceImpl implements CourseRunningImportServic
/**
* 教研室审批调课申请。
* <p>
* 教研室审批教员提交的调课申请,包含同意、发回、拒绝三种处理方式。
* 同意时同步更新课次数据,发回时记录发回原因,拒绝时标记申请为已拒绝。
* 教研室审批调课申请。点审批即已查收,审批人同时记为查收人。
* 同意后写回课次;发回、拒绝也记为已查收。
* </p>
*
* @param dto 教研室审批参数
@@ -1226,26 +1175,6 @@ public class CourseRunningImportServiceImpl implements CourseRunningImportServic
@Override
@Transactional(rollbackFor = Exception.class)
public void auditByTeachingOffice(ScheduleAdjustAuditDTO dto) {
if (dto == null) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "审批参数不能为空");
}
if (jwglRoleHelper.isTeacher()) {
throw new BusinessException(BasicManagementConstants.FORBIDDEN, "教员无权审批调课申请");
}
if (jwglRoleHelper.isDepartmentPersonnel() && !jwglRoleHelper.isAdmin()) {
throw new BusinessException(BasicManagementConstants.FORBIDDEN, "机关人员请使用机关审批,不办理教研室审批");
}
if (!jwglRoleHelper.isAdmin() && !jwglRoleHelper.isResearchOffice()) {
throw new BusinessException(BasicManagementConstants.FORBIDDEN, "仅教研室可进行一审");
}
if (!"jys".equals(dto.getAuditRole())) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "审批角色不正确,应为jys");
}
if (dto.getSpzt() == null
|| (dto.getSpzt() != 1 && dto.getSpzt() != 2 && dto.getSpzt() != 3)) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "审批结果不正确,应为1(同意)、2(发回)或3(拒绝)");
}
SSDKSQ apply = ssdksqMapper.selectById(dto.getSsdksqbh());
if (apply == null) {
throw new BusinessException(BasicManagementConstants.NOT_FOUND, "调课申请不存在");
@@ -1253,17 +1182,6 @@ public class CourseRunningImportServiceImpl implements CourseRunningImportServic
if (apply.getSczt() != null && apply.getSczt() == 1) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该调课申请已撤销,不允许审批");
}
if (jwglRoleHelper.isResearchOffice()) {
String officeId = jwglRoleHelper.requireResearchOfficeId();
String applyOffice = apply.getJysdh();
if (StringUtils.isEmpty(applyOffice) && StringUtils.isNotEmpty(apply.getSqjybh())) {
JYB teacher = jybMapper.selectById(apply.getSqjybh());
applyOffice = teacher == null ? null : teacher.getJysdh();
}
if (applyOffice == null || !officeId.equals(applyOffice.trim())) {
throw new BusinessException(BasicManagementConstants.FORBIDDEN, "只能审批本教研室的调课申请");
}
}
if (apply.getJyspzzt() != null && apply.getJyspzzt() != 0) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该调课申请已审批,无需重复审批");
}
@@ -1273,275 +1191,36 @@ public class CourseRunningImportServiceImpl implements CourseRunningImportServic
apply.setJyspzzt(dto.getSpzt());
apply.setJyspzrbh(auditor);
apply.setJyspzsj(now);
apply.setJyscszt(1);
apply.setJyscsrbh(auditor);
apply.setJyscssj(now);
apply.setFhyj(dto.getFhyj());
if (dto.getSpzt() != null && dto.getSpzt() == 2) {
if (dto.getSpzt() == 2) {
apply.setFhzt(1);
apply.setFhsj(now);
}
ssdksqMapper.updateById(apply);
recordOperationLog(apply.getBh(), "教研室审批", "教研室审批调课申请:" + apply.getBh()
+ ",审批结果:" + dto.getSpzt() + ",审批意见:" + dto.getFhyj());
}
/**
* 教研室查收调课申请。
* <p>
* 教研室查收调课申请,标记查收状态和查收时间。
* </p>
*
* @param ssdksqbh 调课申请编号
* @param csrbh 查收人编号
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void receiveByTeachingOffice(String ssdksqbh, String csrbh) {
// 步骤1:校验参数
if (ssdksqbh == null || ssdksqbh.trim().isEmpty()) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "调课申请编号不能为空");
}
if (csrbh == null || csrbh.trim().isEmpty()) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "查收人编号不能为空");
}
// 步骤2:查询调课申请实体
SSDKSQ apply = ssdksqMapper.selectById(ssdksqbh);
if (apply == null) {
throw new BusinessException(BasicManagementConstants.NOT_FOUND, "调课申请不存在");
}
// 步骤3:校验是否已查收,避免重复查收
if (apply.getJyscszt() != null && apply.getJyscszt() == 1) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该调课申请已查收,无需重复查收");
}
// 步骤4:更新查收状态和查收时间
apply.setJyscszt(1); // 教研室查收状态:已查收
apply.setJyscsrbh(csrbh); // 教研室查收人编号
apply.setJyscssj(LocalDateTime.now()); // 教研室查收时间
apply.setUpdateTime(LocalDateTime.now()); // 更新时间
ssdksqMapper.updateById(apply);
// 步骤5:记录查收操作日志
recordOperationLog(apply.getBh(), "教研室查收", "教研室查收调课申请:" + apply.getBh());
}
/**
* 教学系审批调课申请。
* <p>
* 教学系审批教研室已同意的调课申请,包含同意、发回、拒绝三种处理方式。
* 教学系同意后流转到机关审批,构成三级审批流程(教研室→教学系→机关)。
* </p>
*
* @param dto 教学系审批参数
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void auditByTeachingDepartment(ScheduleAdjustAuditDTO dto) {
// 步骤1:校验审批参数
if (dto == null) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "审批参数不能为空");
}
if (!"jxx".equals(dto.getAuditRole())) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "审批角色不正确,应为jxx");
}
if (dto.getSpzt() == null
|| (dto.getSpzt() != 1 && dto.getSpzt() != 2 && dto.getSpzt() != 3)) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "审批结果不正确,应为1(同意)、2(发回)或3(拒绝)");
}
// 步骤2:查询调课申请实体
SSDKSQ apply = ssdksqMapper.selectById(dto.getSsdksqbh());
if (apply == null) {
throw new BusinessException(BasicManagementConstants.NOT_FOUND, "调课申请不存在");
}
if (apply.getSczt() != null && apply.getSczt() == 1) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该调课申请已撤销,不允许审批");
}
// 步骤3:校验教研室是否已同意,教学系审批应在教研室同意之后进行
if (apply.getJyspzzt() == null || apply.getJyspzzt() != 1) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "教研室未同意或未审批,无法进行教学系审批");
}
// 步骤4:校验教学系是否已审批,避免重复审批
if (apply.getJxxspzzt() != null && apply.getJxxspzzt() != 0) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该调课申请教学系已审批,无需重复审批");
}
// 步骤5:更新调课申请主表的教学系审批状态
LocalDateTime now = LocalDateTime.now();
apply.setJxxspzzt(dto.getSpzt()); // 教学系审批状态
apply.setJxxspzrbh(dto.getSprbh()); // 教学系审批人编号
apply.setJxxspzsj(now); // 教学系审批时间
apply.setFhyj(dto.getFhyj()); // 发回意见
apply.setUpdateTime(now); // 更新时间
ssdksqMapper.updateById(apply);
// 步骤6:根据审批结果执行不同的后续处理
if (dto.getSpzt() == 3) {
// 拒绝:恢复课次原数据,标记申请最终结束
restoreLessonSchedule(apply);
} else if (dto.getSpzt() == 2) {
// 发回:发回教研室,课次保留调整后数据,由教研室重新处理
} else {
// 同意:流转到机关审批
}
// 步骤7:记录教学系审批操作日志
recordOperationLog(apply.getBh(), "教学系审批", "教学系审批调课申请:" + apply.getBh()
+ ",审批结果:" + dto.getSpzt() + ",审批意见:" + dto.getFhyj());
}
/**
* 机关审批调课申请。
* <p>
* 机关审批教研室已同意的调课申请,包含同意、发回、拒绝三种处理方式。
* 机关全部同意后调课申请最终生效,课次按调整后的内容执行。
* </p>
*
* @param dto 机关审批参数
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void auditByAuthority(ScheduleAdjustAuditDTO dto) {
// 步骤1:校验审批参数
if (dto == null) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "审批参数不能为空");
}
if (!"jg".equals(dto.getAuditRole())) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "审批角色不正确,应为jg");
}
if (dto.getSpzt() == null
|| (dto.getSpzt() != 1 && dto.getSpzt() != 2 && dto.getSpzt() != 3)) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "审批结果不正确");
}
if (jwglRoleHelper.isTeacher() || jwglRoleHelper.isResearchOffice()) {
throw new BusinessException(BasicManagementConstants.FORBIDDEN, "仅机关人员可进行终审");
}
if (!jwglRoleHelper.isAdmin() && !jwglRoleHelper.isDepartmentPersonnel()) {
throw new BusinessException(BasicManagementConstants.FORBIDDEN, "仅机关人员可进行终审");
}
SSDKSQ apply = ssdksqMapper.selectById(dto.getSsdksqbh());
if (apply == null) {
throw new BusinessException(BasicManagementConstants.NOT_FOUND, "调课申请不存在");
}
if (apply.getSczt() != null && apply.getSczt() == 1) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该调课申请已撤销,不允许审批");
}
if (apply.getJyspzzt() == null || apply.getJyspzzt() != 1) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "教研室未同意,无法进行机关审批");
}
String jgbh = StringUtils.isNotEmpty(dto.getJgbh()) ? dto.getJgbh() : "JG";
String auditor = StringUtils.isNotEmpty(dto.getSprbh()) ? dto.getSprbh() : currentOperatorId();
SSDKSQSP audit = new SSDKSQSP();
audit.setBh(UuidUtil.getOriginalUUID());
audit.setSsdksqbh(dto.getSsdksqbh());
audit.setJgbh(jgbh);
audit.setSprbh(auditor);
audit.setSpsj(LocalDateTime.now());
audit.setSpzt(dto.getSpzt());
audit.setFhyj(dto.getFhyj());
audit.setNd(apply.getNd());
audit.setCreateTime(LocalDateTime.now());
audit.setUpdateTime(LocalDateTime.now());
ssdksqspMapper.insert(audit);
if (dto.getSpzt() == 1) {
applyApprovedSchedule(apply);
}
recordOperationLog(apply.getBh(), "机关审批", "机关审批调课申请:" + apply.getBh()
+ ",机关编号:" + jgbh
recordOperationLog(apply.getBh(), "教研室审批", "教研室审批调课申请:" + apply.getBh()
+ ",审批结果:" + dto.getSpzt() + ",审批意见:" + dto.getFhyj());
}
/**
* 机关查收调课申请。
* <p>
* 机关查收调课申请,标记查收状态和查收时间。
* </p>
*
* @param ssdksqbh 调课申请编号
* @param jgbh 机关编号
* @param csrbh 查收人编号
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void receiveByAuthority(String ssdksqbh, String jgbh, String csrbh) {
// 步骤1:校验参数
if (ssdksqbh == null || ssdksqbh.trim().isEmpty()) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "调课申请编号不能为空");
}
if (jgbh == null || jgbh.trim().isEmpty()) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "机关编号不能为空");
}
if (csrbh == null || csrbh.trim().isEmpty()) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "查收人编号不能为空");
}
// 步骤2:查询调课申请实体
SSDKSQ apply = ssdksqMapper.selectById(ssdksqbh);
if (apply == null) {
throw new BusinessException(BasicManagementConstants.NOT_FOUND, "调课申请不存在");
}
// 步骤3:检查当前机关是否已查收
LambdaQueryWrapper<SSDKSQSP> checkWrapper = new LambdaQueryWrapper<>();
checkWrapper.eq(SSDKSQSP::getSsdksqbh, ssdksqbh);
checkWrapper.eq(SSDKSQSP::getJgbh, jgbh);
checkWrapper.eq(SSDKSQSP::getCszt, 1);
if (ssdksqspMapper.selectCount(checkWrapper) > 0) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "当前机关已查收该调课申请,无需重复查收");
}
// 步骤4:构建并保存机关查收记录
SSDKSQSP receive = new SSDKSQSP();
receive.setBh(UuidUtil.getOriginalUUID());
receive.setSsdksqbh(ssdksqbh);
receive.setJgbh(jgbh);
receive.setCsrbh(csrbh);
receive.setCszt(1);
receive.setCssj(LocalDateTime.now());
receive.setNd(apply.getNd());
receive.setCreateTime(LocalDateTime.now());
receive.setUpdateTime(LocalDateTime.now());
ssdksqspMapper.insert(receive);
// 步骤5:记录查收操作日志
recordOperationLog(apply.getBh(), "机关查收", "机关查收调课申请:" + apply.getBh()
+ ",机关编号:" + jgbh);
}
/**
* 撤销调课申请。
* <p>
* 教员撤销自己提交的、未经过审批的调课申请。
* 软删除调课申请,标记删除状态为1。
* 事务保证主表软删除与课次原数据恢复的一致性。
* 教员撤销未经过审批的调课申请。列表已按登录教员过滤。
* </p>
*
* @param ssdksqbh 调课申请编号
* @param sqjybh 申请教员编号(用于权限校验)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void cancelScheduleAdjust(String ssdksqbh, String sqjybh) {
if (jwglRoleHelper.isDepartmentPersonnel() || jwglRoleHelper.isResearchOffice()) {
throw new BusinessException(BasicManagementConstants.FORBIDDEN, "仅申请教员本人可撤销调课申请");
}
if (ssdksqbh == null || ssdksqbh.trim().isEmpty()) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "调课申请编号不能为空");
}
String teacherId = jwglRoleHelper.requireTeacherId();
if (sqjybh != null && !sqjybh.trim().isEmpty() && !teacherId.equals(sqjybh.trim())) {
throw new BusinessException(BasicManagementConstants.FORBIDDEN, "仅申请教员本人可撤销该调课申请");
}
sqjybh = teacherId;
public void cancelScheduleAdjust(String ssdksqbh) {
SSDKSQ apply = ssdksqMapper.selectById(ssdksqbh);
if (apply == null) {
throw new BusinessException(BasicManagementConstants.NOT_FOUND, "调课申请不存在");
@@ -1549,29 +1228,19 @@ public class CourseRunningImportServiceImpl implements CourseRunningImportServic
if (apply.getSczt() != null && apply.getSczt() == 1) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该调课申请已撤销,无需重复撤销");
}
if (!sqjybh.equals(apply.getSqjybh())) {
throw new BusinessException(BasicManagementConstants.FORBIDDEN, "仅申请教员本人可撤销该调课申请");
if (apply.getJyspzzt() != null && apply.getJyspzzt() != 0) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该调课申请已审批,不允许撤销");
}
if (apply.getJyspzzt() != null && apply.getJyspzzt() == 1) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该调课申请教研室已同意,不允许撤销");
}
apply.setSczt(1);
apply.setJyscszt(0);
apply.setJyscsrbh(null);
apply.setJyscssj(null);
ssdksqMapper.updateById(apply);
recordOperationLog(apply.getBh(), "撤销", "撤销调课申请:" + apply.getBh());
}
/**
* 构建原日期节次描述。
* <p>
* 根据课次实体生成原日期节次的字符串描述,便于调课申请中保留原信息。
* </p>
*
* @param lesson 课次实体
* @return 原日期节次描述
*/
/**
* 机关终审同意后,将调整结果写回学员队任务表、课程表,以及可选的实施课次。
* 教研室同意后,将调整结果写回课次、学员队任务表和课程表。
*/
private void applyApprovedSchedule(SSDKSQ apply) {
if (apply == null || StringUtils.isEmpty(apply.getSskcbbh())) {
@@ -1642,28 +1311,6 @@ public class CourseRunningImportServiceImpl implements CourseRunningImportServic
return sb.toString();
}
/**
* 恢复课次原数据。
* <p>
* 当调课申请被拒绝或撤销时,恢复课次到申请前的原始数据。
* 实际业务中通常需要从历史快照恢复,此处简化为清空日期节次。
* </p>
*
* @param apply 调课申请实体
*/
private void restoreLessonSchedule(SSDKSQ apply) {
// 步骤1:查询课次实体
SSKCB lesson = sskcbMapper.selectById(apply.getSskcbbh());
if (lesson == null) {
return;
}
// 步骤2:清空课次因调课而设置的日期节次(恢复原状)
lesson.setRq(null);
lesson.setJc(null);
sskcbMapper.updateById(lesson);
}
// ==================== 教学保障管理 ====================
/**
@@ -2971,192 +2618,6 @@ public class CourseRunningImportServiceImpl implements CourseRunningImportServic
return value == null ? "" : String.valueOf(value);
}
// ============================================================
// 调课策略管理实现(时间段限制策略 + 免审批策略)
// ============================================================
/** 策略类型常量:时间段限制 */
private static final String STRATEGY_TIME_LIMIT = "TIME_LIMIT";
/** 策略类型常量:免审批 */
private static final String STRATEGY_AUTO_APPROVE = "AUTO_APPROVE";
/** 星期中文描述 */
private static final String[] WEEK_DAY_NAMES = {"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
@Override
public List<ScheduleAdjustStrategyVO> listScheduleAdjustStrategies(String cllx) {
LambdaQueryWrapper<ScheduleAdjustStrategy> wrapper = new LambdaQueryWrapper<>();
if (cllx != null && !cllx.trim().isEmpty()) {
wrapper.eq(ScheduleAdjustStrategy::getCllx, cllx);
}
wrapper.orderByDesc(ScheduleAdjustStrategy::getGxsj);
List<ScheduleAdjustStrategy> list = scheduleAdjustStrategyMapper.selectList(wrapper);
List<ScheduleAdjustStrategyVO> result = new ArrayList<>();
if (list == null) {
return result;
}
for (ScheduleAdjustStrategy strategy : list) {
result.add(buildStrategyVO(strategy));
}
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public String saveScheduleAdjustStrategy(ScheduleAdjustStrategyDTO dto) {
if (!STRATEGY_TIME_LIMIT.equals(dto.getCllx()) && !STRATEGY_AUTO_APPROVE.equals(dto.getCllx())) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST,
"策略类型不正确,应为 TIME_LIMIT 或 AUTO_APPROVE");
}
if (STRATEGY_TIME_LIMIT.equals(dto.getCllx())) {
if (dto.getJzxq() != null && (dto.getJzxq() < 0 || dto.getJzxq() > 6)) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST,
"截止星期取值范围为0-6(0=周日,6=周六)");
}
}
if (dto.getSxkssj() != null && dto.getSxjssj() != null
&& dto.getSxkssj().isAfter(dto.getSxjssj())) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST,
"生效开始时间不能晚于生效结束时间");
}
LocalDateTime now = LocalDateTime.now();
if (dto.getClbh() == null || dto.getClbh().trim().isEmpty()) {
ScheduleAdjustStrategy strategy = new ScheduleAdjustStrategy();
strategy.setClbh(UuidUtil.getOriginalUUID());
strategy.setClmc(dto.getClmc());
strategy.setCllx(dto.getCllx());
strategy.setSfqy(dto.getSfqy() == null ? 1 : dto.getSfqy());
strategy.setJzxq(dto.getJzxq());
strategy.setJzjc(dto.getJzjc());
strategy.setSxkssj(dto.getSxkssj());
strategy.setSxjssj(dto.getSxjssj());
strategy.setBz(dto.getBz());
strategy.setCjsj(now);
strategy.setGxsj(now);
scheduleAdjustStrategyMapper.insert(strategy);
return strategy.getClbh();
} else {
ScheduleAdjustStrategy existing = scheduleAdjustStrategyMapper.selectById(dto.getClbh());
if (existing == null) {
throw new BusinessException(BasicManagementConstants.NOT_FOUND, "调课策略不存在");
}
existing.setClmc(dto.getClmc());
existing.setCllx(dto.getCllx());
existing.setSfqy(dto.getSfqy() == null ? 1 : dto.getSfqy());
existing.setJzxq(dto.getJzxq());
existing.setJzjc(dto.getJzjc());
existing.setSxkssj(dto.getSxkssj());
existing.setSxjssj(dto.getSxjssj());
existing.setBz(dto.getBz());
existing.setGxsj(now);
scheduleAdjustStrategyMapper.updateById(existing);
return existing.getClbh();
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteScheduleAdjustStrategy(String clbh) {
if (clbh == null || clbh.trim().isEmpty()) {
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "策略编号不能为空");
}
ScheduleAdjustStrategy existing = scheduleAdjustStrategyMapper.selectById(clbh);
if (existing == null) {
throw new BusinessException(BasicManagementConstants.NOT_FOUND, "调课策略不存在");
}
scheduleAdjustStrategyMapper.deleteById(clbh);
}
/** 调课时间段策略校验:超过截止星期时不允许调整下周课程 */
private void validateScheduleAdjustTimeLimitStrategy(LocalDateTime newDate) {
if (newDate == null) {
return;
}
LambdaQueryWrapper<ScheduleAdjustStrategy> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ScheduleAdjustStrategy::getCllx, STRATEGY_TIME_LIMIT);
wrapper.eq(ScheduleAdjustStrategy::getSfqy, 1);
List<ScheduleAdjustStrategy> strategies = scheduleAdjustStrategyMapper.selectList(wrapper);
if (strategies == null || strategies.isEmpty()) {
return;
}
LocalDateTime now = LocalDateTime.now();
int currentDayOfWeek = now.getDayOfWeek().getValue();
boolean isFutureWeek = newDate.toLocalDate().isAfter(now.toLocalDate().plusDays(7));
for (ScheduleAdjustStrategy strategy : strategies) {
if (strategy.getSxkssj() != null && now.isBefore(strategy.getSxkssj())) {
continue;
}
if (strategy.getSxjssj() != null && now.isAfter(strategy.getSxjssj())) {
continue;
}
Integer deadlineDay = strategy.getJzxq();
if (deadlineDay == null) {
continue;
}
int deadlineDayOfWeek = (deadlineDay == 0) ? 7 : deadlineDay;
if (isFutureWeek && currentDayOfWeek > deadlineDayOfWeek) {
String dayName = (deadlineDay >= 0 && deadlineDay <= 6) ? WEEK_DAY_NAMES[deadlineDay] : String.valueOf(deadlineDay);
throw new BusinessException(BasicManagementConstants.BAD_REQUEST,
"根据调课策略[" + strategy.getClmc() + "]," + dayName + "之后不允许调整下周课程");
}
}
}
/** 判断当前是否命中免审批策略 */
private boolean matchAutoApproveStrategy() {
LambdaQueryWrapper<ScheduleAdjustStrategy> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ScheduleAdjustStrategy::getCllx, STRATEGY_AUTO_APPROVE);
wrapper.eq(ScheduleAdjustStrategy::getSfqy, 1);
List<ScheduleAdjustStrategy> strategies = scheduleAdjustStrategyMapper.selectList(wrapper);
if (strategies == null || strategies.isEmpty()) {
return false;
}
LocalDateTime now = LocalDateTime.now();
for (ScheduleAdjustStrategy strategy : strategies) {
boolean inRange = true;
if (strategy.getSxkssj() != null && now.isBefore(strategy.getSxkssj())) {
inRange = false;
}
if (strategy.getSxjssj() != null && now.isAfter(strategy.getSxjssj())) {
inRange = false;
}
if (inRange) {
return true;
}
}
return false;
}
/** 组装调课策略展示VO */
private ScheduleAdjustStrategyVO buildStrategyVO(ScheduleAdjustStrategy strategy) {
ScheduleAdjustStrategyVO vo = new ScheduleAdjustStrategyVO();
vo.setClbh(strategy.getClbh());
vo.setClmc(strategy.getClmc());
vo.setCllx(strategy.getCllx());
vo.setCllxDesc(STRATEGY_TIME_LIMIT.equals(strategy.getCllx()) ? "时间段限制" :
(STRATEGY_AUTO_APPROVE.equals(strategy.getCllx()) ? "免审批" : strategy.getCllx()));
vo.setSfqy(strategy.getSfqy());
vo.setSfqyDesc(strategy.getSfqy() != null && strategy.getSfqy() == 1 ? "启用" : "停用");
vo.setJzxq(strategy.getJzxq());
if (strategy.getJzxq() != null && strategy.getJzxq() >= 0 && strategy.getJzxq() <= 6) {
vo.setJzxqDesc(WEEK_DAY_NAMES[strategy.getJzxq()]);
}
vo.setJzjc(strategy.getJzjc());
vo.setSxkssj(strategy.getSxkssj());
vo.setSxjssj(strategy.getSxjssj());
vo.setBz(strategy.getBz());
vo.setCjsj(strategy.getCjsj());
vo.setGxsj(strategy.getGxsj());
return vo;
}
/**
* 根据教员编号自动解析其归属的实施_机关编号。
* <p>
@@ -9,6 +9,7 @@ import com.roomroot.jwgl.service.FacultyService;
import com.roomroot.jwgl.unit.BusinessException;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.utils.ExcelExportUtil;
import com.roomroot.jwgl.utils.ExcelParseUtil;
import com.roomroot.jwgl.utils.UuidUtil;
import com.roomroot.jwgl.vo.faculty.TeacherListVO;
@@ -160,6 +161,52 @@ public class FacultyServiceImpl implements FacultyService {
return jybMapper.selectById(jybh);
}
@Override
@Transactional(rollbackFor = Exception.class)
public int importJYBFromExcel(MultipartFile file) throws Exception {
if (file == null || file.isEmpty()) {
throw new BusinessException("导入文件不能为空");
}
List<JYB> list = ExcelParseUtil.parseJYBExcel(
file.getInputStream(), file.getOriginalFilename());
if (list.isEmpty()) {
throw new BusinessException("Excel中无有效数据");
}
for (JYB entity : list) {
// 编号为空自动生成
if (entity.getJybh() == null || entity.getJybh().isEmpty()) {
entity.setJybh(UuidUtil.getUUID());
}
// 离职状态默认 0
if (entity.getLzzt() == null) {
entity.setLzzt(0);
}
jybMapper.insert(entity);
}
return list.size();
}
@Override
public void exportJYBExcel(jakarta.servlet.http.HttpServletResponse response) throws Exception {
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<JYB> w =
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
w.eq(JYB::getLzzt, 0).orderByAsc(JYB::getXh);
List<JYB> list = jybMapper.selectList(w);
String[] headers = {"教员编号", "教员姓名", "教研室代号", "职称", "专业技术职务等级", "教员类别",
"性别", "手机号", "身份证号", "备注", "序号", "虚实类型"};
byte[] bytes = ExcelExportUtil.export("教员", headers, list,
JYB::getJybh, JYB::getJyxm, JYB::getJysdh, JYB::getZc,
JYB::getZyjszwdj, JYB::getJylb, JYB::getXb, JYB::getSjh,
JYB::getSfzh, JYB::getBz, JYB::getXh, JYB::getXslx);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition",
"attachment; filename=" + java.net.URLEncoder.encode(
"教员列表.xlsx", java.nio.charset.StandardCharsets.UTF_8));
response.getOutputStream().write(bytes);
response.getOutputStream().flush();
}
@Override
public PageResult<TeacherListVO> pageJYBDetail(PageQuery query, JYB cond) {
Page<TeacherListVO> page = new Page<>(query.getPageNum(), query.getPageSize());
@@ -73,4 +73,45 @@ public class TrainingProgramServiceImpl implements TrainingProgramService {
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
@Override
@Transactional(rollbackFor = Exception.class)
public int importZYBFromExcel(org.springframework.web.multipart.MultipartFile file) throws Exception {
if (file == null || file.isEmpty()) {
throw new RuntimeException("导入文件不能为空");
}
java.util.List<ZYB> list = com.roomroot.jwgl.utils.ExcelParseUtil.parseZYBExcel(
file.getInputStream(), file.getOriginalFilename());
if (list.isEmpty()) {
throw new RuntimeException("Excel中无有效数据");
}
for (ZYB entity : list) {
if (entity.getZydh() == null || entity.getZydh().isEmpty()) {
entity.setZydh(com.roomroot.jwgl.utils.UuidUtil.getUUID());
}
if (entity.getTy() == null) {
entity.setTy(false);
}
zybMapper.insert(entity);
}
return list.size();
}
@Override
public void exportZYBExcel(jakarta.servlet.http.HttpServletResponse response) throws Exception {
java.util.List<ZYB> list = zybMapper.selectList(
new LambdaQueryWrapper<ZYB>().orderByAsc(ZYB::getZydh));
String[] headers = {"专业代号", "专业名称", "专业代码", "专业版本", "培训类型", "培训层次",
"学年制", "学期数", "专业方向", "专业备注", "学员类别", "简称", "培训类型2"};
byte[] bytes = com.roomroot.jwgl.utils.ExcelExportUtil.export("培养方案", headers, list,
ZYB::getZydh, ZYB::getZymc, ZYB::getZydm, ZYB::getZybb, ZYB::getPxlx,
ZYB::getPxcc, ZYB::getXnz, ZYB::getXqs, ZYB::getZyfx, ZYB::getZybz,
ZYB::getXylb, ZYB::getJc, ZYB::getPxlx2);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition",
"attachment; filename=" + java.net.URLEncoder.encode(
"培养方案.xlsx", java.nio.charset.StandardCharsets.UTF_8));
response.getOutputStream().write(bytes);
response.getOutputStream().flush();
}
}
@@ -71,4 +71,43 @@ public class ZYJXJHBServiceImpl implements ZYJXJHBService {
.eq(ZYJXJHB::getTy, ty);
return zyjxjhbMapper.selectList(wrapper);
}
@Override
@org.springframework.transaction.annotation.Transactional(rollbackFor = Exception.class)
public int importZYJXJHBFromExcel(org.springframework.web.multipart.MultipartFile file) throws Exception {
if (file == null || file.isEmpty()) {
throw new RuntimeException("导入文件不能为空");
}
List<ZYJXJHB> list = com.roomroot.jwgl.utils.ExcelParseUtil.parseZYJXJHBExcel(
file.getInputStream(), file.getOriginalFilename());
if (list.isEmpty()) {
throw new RuntimeException("Excel中无有效数据");
}
for (ZYJXJHB entity : list) {
if (entity.getTy() == null) {
entity.setTy(0);
}
zyjxjhbMapper.insert(entity);
}
return list.size();
}
@Override
public void exportZYJXJHBExcel(jakarta.servlet.http.HttpServletResponse response) throws Exception {
List<ZYJXJHB> list = zyjxjhbMapper.selectList(
new LambdaQueryWrapper<ZYJXJHB>().orderByAsc(ZYJXJHB::getZydh));
String[] headers = {"专业代号", "课编号", "学期第次", "课类型", "学时", "学分", "周课时",
"课程定位", "模块", "成绩分制", "理论学时", "实践学时", "考试课时", "教研室代号", "简称"};
byte[] bytes = com.roomroot.jwgl.utils.ExcelExportUtil.export("专业教学计划", headers, list,
ZYJXJHB::getZydh, ZYJXJHB::getKbh, ZYJXJHB::getXqdc, ZYJXJHB::getKlx,
ZYJXJHB::getXs, ZYJXJHB::getXf, ZYJXJHB::getZks, ZYJXJHB::getKcdw,
ZYJXJHB::getMk, ZYJXJHB::getCjfz, ZYJXJHB::getLlxs, ZYJXJHB::getSjxs,
ZYJXJHB::getKsks, ZYJXJHB::getJysdh, ZYJXJHB::getJc);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition",
"attachment; filename=" + java.net.URLEncoder.encode(
"专业教学计划.xlsx", java.nio.charset.StandardCharsets.UTF_8));
response.getOutputStream().write(bytes);
response.getOutputStream().flush();
}
}
@@ -5,10 +5,13 @@ import com.roomroot.jwgl.dto.courserunning.SemesterImportDTO;
import com.roomroot.jwgl.dto.courserunning.SingleCourseImportDTO;
import com.roomroot.jwgl.dto.departmentpersonnel.DepartmentPersonnelCreateDTO;
import com.roomroot.jwgl.entity.JXSSJH;
import com.roomroot.jwgl.entity.JYB;
import com.roomroot.jwgl.entity.JYSB;
import com.roomroot.jwgl.entity.KSBZXM;
import com.roomroot.jwgl.entity.XYDB;
import com.roomroot.jwgl.entity.XYXX;
import com.roomroot.jwgl.entity.ZYB;
import com.roomroot.jwgl.entity.ZYJXJHB;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
@@ -386,6 +389,144 @@ public class ExcelParseUtil {
return result;
}
/**
* 解析教员管理导入 Excel 文件(支持 .xls / .xlsx)。
* <p>表头顺序:</p>
* 教员编号, 教员姓名, 教研室代号, 职称, 专业技术职务等级, 教员类别,
* 性别, 手机号, 身份证号, 备注, 序号, 虚实类型
*
* @return 教员实体列表(离职状态由业务层默认填充)
* @throws Exception 解析异常
*/
public static List<JYB> parseJYBExcel(InputStream inputStream, String fileName) throws Exception {
List<JYB> result = new ArrayList<>();
Workbook workbook = createWorkbook(inputStream, fileName);
try {
Sheet sheet = workbook.getSheetAt(0);
int lastRowNum = sheet.getLastRowNum();
for (int rowNum = 1; rowNum <= lastRowNum; rowNum++) {
Row row = sheet.getRow(rowNum);
if (row == null) {
continue;
}
JYB entity = new JYB();
entity.setJybh(getCellStringValue(row.getCell(0)));
entity.setJyxm(getCellStringValue(row.getCell(1)));
entity.setJysdh(getCellStringValue(row.getCell(2)));
entity.setZc(getCellStringValue(row.getCell(3)));
entity.setZyjszwdj(getCellStringValue(row.getCell(4)));
entity.setJylb(getCellStringValue(row.getCell(5)));
entity.setXb(parseIntCell(row.getCell(6)));
entity.setSjh(getCellStringValue(row.getCell(7)));
entity.setSfzh(getCellStringValue(row.getCell(8)));
entity.setBz(getCellStringValue(row.getCell(9)));
entity.setXh(getCellStringValue(row.getCell(10)));
entity.setXslx(parseIntCell(row.getCell(11)));
// 至少要有姓名才视为有效行
if (entity.getJyxm() != null && !entity.getJyxm().isEmpty()) {
result.add(entity);
}
}
} finally {
workbook.close();
}
return result;
}
/**
* 解析培养方案(专业表)导入 Excel 文件(支持 .xls / .xlsx)。
* <p>表头顺序:</p>
* 专业代号, 专业名称, 专业代码, 专业版本, 培训类型, 培训层次, 学年制, 学期数,
* 专业方向, 专业备注, 学员类别, 简称, 培训类型2
*
* @return 专业实体列表
* @throws Exception 解析异常
*/
public static List<ZYB> parseZYBExcel(InputStream inputStream, String fileName) throws Exception {
List<ZYB> result = new ArrayList<>();
Workbook workbook = createWorkbook(inputStream, fileName);
try {
Sheet sheet = workbook.getSheetAt(0);
int lastRowNum = sheet.getLastRowNum();
for (int rowNum = 1; rowNum <= lastRowNum; rowNum++) {
Row row = sheet.getRow(rowNum);
if (row == null) {
continue;
}
ZYB entity = new ZYB();
entity.setZydh(getCellStringValue(row.getCell(0)));
entity.setZymc(getCellStringValue(row.getCell(1)));
entity.setZydm(getCellStringValue(row.getCell(2)));
entity.setZybb(getCellStringValue(row.getCell(3)));
entity.setPxlx(getCellStringValue(row.getCell(4)));
entity.setPxcc(getCellStringValue(row.getCell(5)));
entity.setXnz(getCellStringValue(row.getCell(6)));
entity.setXqs(parseIntCell(row.getCell(7)));
entity.setZyfx(getCellStringValue(row.getCell(8)));
entity.setZybz(getCellStringValue(row.getCell(9)));
entity.setXylb(getCellStringValue(row.getCell(10)));
entity.setJc(getCellStringValue(row.getCell(11)));
entity.setPxlx2(getCellStringValue(row.getCell(12)));
// 至少要有专业代号或名称才视为有效行
if (entity.getZydh() != null && !entity.getZydh().isEmpty()
|| entity.getZymc() != null && !entity.getZymc().isEmpty()) {
result.add(entity);
}
}
} finally {
workbook.close();
}
return result;
}
/**
* 解析专业教学计划表导入 Excel 文件(支持 .xls / .xlsx)。
* <p>表头顺序:</p>
* 专业代号, 课编号, 学期第次, 课类型, 学时, 学分, 周课时, 课程定位,
* 模块, 成绩分制, 理论学时, 实践学时, 考试课时, 教研室代号, 简称
*
* @return 专业教学计划实体列表
* @throws Exception 解析异常
*/
public static List<ZYJXJHB> parseZYJXJHBExcel(InputStream inputStream, String fileName) throws Exception {
List<ZYJXJHB> result = new ArrayList<>();
Workbook workbook = createWorkbook(inputStream, fileName);
try {
Sheet sheet = workbook.getSheetAt(0);
int lastRowNum = sheet.getLastRowNum();
for (int rowNum = 1; rowNum <= lastRowNum; rowNum++) {
Row row = sheet.getRow(rowNum);
if (row == null) {
continue;
}
ZYJXJHB entity = new ZYJXJHB();
entity.setZydh(getCellStringValue(row.getCell(0)));
entity.setKbh(getCellStringValue(row.getCell(1)));
entity.setXqdc(parseIntCell(row.getCell(2)));
entity.setKlx(getCellStringValue(row.getCell(3)));
entity.setXs(parseIntCell(row.getCell(4)));
entity.setXf(parseFloatCell(row.getCell(5)));
entity.setZks(parseIntCell(row.getCell(6)));
entity.setKcdw(getCellStringValue(row.getCell(7)));
entity.setMk(getCellStringValue(row.getCell(8)));
entity.setCjfz(getCellStringValue(row.getCell(9)));
entity.setLlxs(parseIntCell(row.getCell(10)));
entity.setSjxs(parseIntCell(row.getCell(11)));
entity.setKsks(parseIntCell(row.getCell(12)));
entity.setJysdh(getCellStringValue(row.getCell(13)));
entity.setJc(getCellStringValue(row.getCell(14)));
// 至少要有专业代号或课编号才视为有效行
if (entity.getZydh() != null && !entity.getZydh().isEmpty()
|| entity.getKbh() != null && !entity.getKbh().isEmpty()) {
result.add(entity);
}
}
} finally {
workbook.close();
}
return result;
}
private static Workbook createWorkbook(InputStream inputStream, String fileName) throws Exception {
String name = fileName == null ? "" : fileName.toLowerCase();
if (!name.endsWith(".xlsx") && !name.endsWith(".xls")) {
@@ -426,6 +567,21 @@ public class ExcelParseUtil {
}
}
/**
* 解析单元格浮点数值。
*/
private static Float parseFloatCell(Cell cell) {
String value = getCellStringValue(cell);
if (value == null || value.isEmpty()) {
return null;
}
try {
return Float.valueOf(value);
} catch (NumberFormatException e) {
return null;
}
}
private static String getCellStringValue(Cell cell) {
if (cell == null) {
return null;
@@ -37,4 +37,42 @@ public class UuidUtil {
public static String getUuidWithPrefix(String prefix) {
return prefix + getUUID();
}
/**
* 将 32 位十六进制或 36 位带横线 UUID 转为 16 字节,供达梦 VARBINARY 主键写入。
*/
public static byte[] toBytes(String guid) {
if (guid == null) {
return null;
}
String hex = guid.trim().replace("-", "");
if (hex.length() < 32) {
throw new IllegalArgumentException("GUID hex length must be 32, actual=" + hex.length());
}
hex = hex.substring(0, 32);
byte[] out = new byte[16];
for (int i = 0; i < 16; i++) {
out[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
}
return out;
}
/**
* 将 VARBINARY(16) 转为带横线的 36 位 UUID,与 CHAR(36) 外键存储格式一致。
*/
public static String fromBytes(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
return null;
}
StringBuilder hex = new StringBuilder(bytes.length * 2);
for (byte value : bytes) {
hex.append(String.format("%02x", value));
}
String h = hex.toString();
if (h.length() < 32) {
return h;
}
return h.substring(0, 8) + "-" + h.substring(8, 12) + "-" + h.substring(12, 16)
+ "-" + h.substring(16, 20) + "-" + h.substring(20, 32);
}
}
@@ -100,11 +100,6 @@ public class ScheduleAdjustApplyVO {
*/
private Integer jyscszt;
/**
* 机关审批状态:0-未审批,1-已同意,2-已发回,3-已拒绝。
*/
private Integer jgspzt;
/**
* 发回意见。
*/
@@ -1,81 +0,0 @@
package com.roomroot.jwgl.vo.courserunning;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 调课申请审批记录展示数据。
* <p>
* 用于展示调课申请中机关审批环节的审批记录,包括机关编号、审批时间、
* 审批状态、审批人、查收状态、发回意见等信息。
* </p>
*/
@Data
public class ScheduleAdjustAuditVO {
/**
* 审批记录编号。
*/
private String bh;
/**
* 调课申请编号。
*/
private String ssdksqbh;
/**
* 机关编号。
*/
private String jgbh;
/**
* 机关名称(关联机构表获取)。
*/
private String jgmc;
/**
* 审批时间。
*/
private LocalDateTime spsj;
/**
* 审批状态:0-未审批,1-已同意,2-已发回,3-已拒绝。
*/
private Integer spzt;
/**
* 审批人编号。
*/
private String sprbh;
/**
* 审批人姓名(关联教员表获取)。
*/
private String sprxm;
/**
* 查收状态:0-未查收,1-已查收。
*/
private Integer cszt;
/**
* 查收时间。
*/
private LocalDateTime cssj;
/**
* 查收人编号。
*/
private String csrbh;
/**
* 发回意见。
*/
private String fhyj;
/**
* 年度。
*/
private Integer nd;
}
@@ -10,7 +10,7 @@ import java.util.List;
* <p>
* 用于展示调课申请的完整信息,包括课次信息、申请教员、调整后的日期节次、
* 教学内容变更、新的教室列表、辅助教员列表、学员队列表、保障明细列表,
* 以及教研室和机关的审批记录。
* 以及教研室审批与查收信息。
* </p>
*/
@Data
@@ -148,14 +148,14 @@ public class ScheduleAdjustDetailVO {
private LocalDateTime jyscssj;
/**
* 教研室查收人编号。
* 教研室查收人编号,与审批人为同一人。
*/
private String jyscsrbh;
/**
* 机关审批列表,记录所有机关审批结果。
* 发回意见。
*/
private List<ScheduleAdjustAuditVO> auditList;
private String fhyj;
/**
* 新教室列表。
@@ -1,54 +0,0 @@
package com.roomroot.jwgl.vo.courserunning;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 调课策略展示VO。
*/
@Data
public class ScheduleAdjustStrategyVO {
/** 策略编号 */
private String clbh;
/** 策略名称 */
private String clmc;
/** 策略类型:TIME_LIMIT=时间段限制,AUTO_APPROVE=免审批 */
private String cllx;
/** 策略类型描述 */
private String cllxDesc;
/** 是否启用:0=停用,1=启用 */
private Integer sfqy;
/** 是否启用描述 */
private String sfqyDesc;
/** 截止星期:0-6 */
private Integer jzxq;
/** 截止星期描述 */
private String jzxqDesc;
/** 截止节次 */
private Integer jzjc;
/** 生效开始时间 */
private LocalDateTime sxkssj;
/** 生效结束时间 */
private LocalDateTime sxjssj;
/** 备注 */
private String bz;
/** 创建时间 */
private LocalDateTime cjsj;
/** 更新时间 */
private LocalDateTime gxsj;
}
@@ -0,0 +1,24 @@
package com.roomroot.jwgl.vo.jwtest;
import lombok.Data;
import java.util.List;
/**
* 一类冲突检查结果。
*/
@Data
public class PkConflictCheckVO {
/** 冲突类型 */
private String ctlx;
/** 检查说明 */
private String jcsm;
/** 冲突条数 */
private Integer ts;
/** 成对冲突明细 */
private List<PkConflictVO> mx;
}
@@ -0,0 +1,73 @@
package com.roomroot.jwgl.vo.jwtest;
import lombok.Data;
/**
* 排课冲突成对明细:一条记录表示两门课在同一时间资源上互相冲突。
*/
@Data
public class PkConflictVO {
/** 冲突类型 */
private String ctlx;
/** 冲突资源(班次 / 教员 / 教室) */
private String ctzy;
/** 上课日期 */
private String skrq;
/** 节次 */
private Integer jc;
/** 开始时间 */
private String kssj;
/** 结束时间 */
private String jssj;
/** 冲突说明 */
private String sm;
/** 课次A编号 */
private String aKcBh;
/** 课次A课堂名称 */
private String aKtmc;
/** 课次A课程名称 */
private String aKcmc;
/** 课次A课程类型 */
private String aKclx;
/** 课次A教员名称 */
private String aJymc;
/** 课次A班级名称 */
private String aBjmc;
/** 课次A教室名称 */
private String aJsmc;
/** 课次B编号 */
private String bKcBh;
/** 课次B课堂名称 */
private String bKtmc;
/** 课次B课程名称 */
private String bKcmc;
/** 课次B课程类型 */
private String bKclx;
/** 课次B教员名称 */
private String bJymc;
/** 课次B班级名称 */
private String bBjmc;
/** 课次B教室名称 */
private String bJsmc;
}