初始化提交

This commit is contained in:
liweijie
2026-08-18 09:49:02 +08:00
commit 5a74ab0b0d
1102 changed files with 119160 additions and 0 deletions
@@ -0,0 +1,184 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.dto.accountmanagement.AccountCreateDTO;
import com.roomroot.jwgl.dto.accountmanagement.AccountDisableDTO;
import com.roomroot.jwgl.dto.accountmanagement.AccountIdDTO;
import com.roomroot.jwgl.dto.accountmanagement.AccountLoginDTO;
import com.roomroot.jwgl.dto.accountmanagement.AccountLoginInformationQueryDTO;
import com.roomroot.jwgl.dto.accountmanagement.AccountMergeDTO;
import com.roomroot.jwgl.dto.accountmanagement.AccountPasswordChangeDTO;
import com.roomroot.jwgl.dto.accountmanagement.AccountPasswordResetDTO;
import com.roomroot.jwgl.dto.accountmanagement.AccountQueryDTO;
import com.roomroot.jwgl.dto.accountmanagement.AccountRoleConfigurationDTO;
import com.roomroot.jwgl.dto.accountmanagement.AccountRoleOptionQueryDTO;
import com.roomroot.jwgl.dto.accountmanagement.AccountSsoSessionResolveDTO;
import com.roomroot.jwgl.dto.accountmanagement.AccountUpdateDTO;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.vo.accountmanagement.AccountMergeResultVO;
import com.roomroot.jwgl.vo.accountmanagement.AccountLoginCaptchaVO;
import com.roomroot.jwgl.vo.accountmanagement.AccountLoginInformationVO;
import com.roomroot.jwgl.vo.accountmanagement.AccountLoginVO;
import com.roomroot.jwgl.vo.accountmanagement.AccountRoleOptionVO;
import com.roomroot.jwgl.vo.accountmanagement.AccountRoleTypeVO;
import com.roomroot.jwgl.vo.accountmanagement.AccountRoleVO;
import com.roomroot.jwgl.vo.accountmanagement.AccountSsoSessionVO;
import com.roomroot.jwgl.vo.accountmanagement.AccountVO;
import java.util.List;
/**
* 账户管理服务。
*
* <p>账户资料、启停、密码重置、角色配置和账户合并均集中在当前服务中,
* 避免同一业务功能被拆分为多个相互依赖的业务类。</p>
*/
public interface AccountManagementService {
/**
* 查询账户列表。
*
* @param dto 账户查询条件
* @return 经过筛选和稳定排序的账户分页结果
*/
PageResult<AccountVO> list(AccountQueryDTO dto);
/**
* 查询账户详情。
*
* @param dto 账户编号参数
* @return 包含角色和数据范围的账户详情
*/
AccountVO get(AccountIdDTO dto);
/**
* 新增账户并配置初始角色。
*
* @param dto 账户新增参数
* @return 新增后的账户详情
*/
AccountVO add(AccountCreateDTO dto);
/**
* 修改账户基本信息。
*
* @param dto 账户修改参数
* @return 修改后的账户详情
*/
AccountVO update(AccountUpdateDTO dto);
/**
* 停用账户。
*
* @param dto 账户停用参数
* @return 停用后的账户详情
*/
AccountVO disable(AccountDisableDTO dto);
/**
* 启用账户。
*
* @param dto 账户编号参数
* @return 启用后的账户详情
*/
AccountVO enable(AccountIdDTO dto);
/**
* 重置账户密码。
*
* @param dto 密码重置参数
* @return 密码重置后的账户详情
*/
AccountVO resetPassword(AccountPasswordResetDTO dto);
/**
* 当前登录账户修改自己的密码。
*
* @param dto 原密码、新密码和确认密码
* @return 密码修改后的脱敏账户详情
*/
AccountVO changePassword(AccountPasswordChangeDTO dto);
/**
* 查询当前登录账户的登录信息和成功登录历史。
*
* @param dto 登录历史分页参数
* @return 当前账户的登录统计和成功登录历史
*/
AccountLoginInformationVO getLoginInformation(
AccountLoginInformationQueryDTO dto);
/**
* 生成本地账户登录使用的一次性图形验证码。
*
* @return 验证码编号、PNG 图片数据和有效秒数
*/
AccountLoginCaptchaVO generateLoginCaptcha();
/**
* 使用本地登录账号和密码建立账户管理登录会话。
*
* @param dto 本地登录参数
* @return 脱敏后的本地账户登录结果
*/
AccountLoginVO login(AccountLoginDTO dto);
/**
* 校验统一身份认证会话并解析唯一的本地账户。
*
* <p>由于现有资料没有明确 LoginID 和 UserID 中哪一个固定对应本地统一账号,
* 当前业务会同时把两者作为候选,并拒绝候选值指向不同本地账户的场景。
* 校验成功后同时建立本项目的 Servlet Session 和在线用户记录。</p>
*
* @param dto SSO 会话解析参数
* @return SSO 会话及其对应的本地账户信息
*/
AccountSsoSessionVO resolveSsoSession(
AccountSsoSessionResolveDTO dto);
/**
* 退出当前 HTTP 会话。
*
* <p>退出接口不接收业务参数,也不返回业务数据,因此不新增 DTO 或 VO。</p>
*/
void logout();
/**
* 查询系统支持的全部角色类型。
*
* @return 按页面展示顺序排列的角色类型列表
*/
List<AccountRoleTypeVO> listRoleTypes();
/**
* 查询指定角色类型下可供配置的身份。
*
* @param dto 角色身份查询条件
* @return 经过数据库筛选、去重和稳定排序的角色身份分页结果
*/
PageResult<AccountRoleOptionVO> listRoleOptions(
AccountRoleOptionQueryDTO dto);
/**
* 查询账户已经绑定的角色和数据范围。
*
* @param dto 账户编号参数
* @return 账户角色列表
*/
List<AccountRoleVO> listRoles(AccountIdDTO dto);
/**
* 整体替换账户角色和数据范围。
*
* @param dto 账户角色配置参数
* @return 配置完成后的账户详情
*/
AccountVO configureRoles(AccountRoleConfigurationDTO dto);
/**
* 将源账户角色迁移到目标账户并停用源账户。
*
* @param dto 账户合并参数
* @return 源账户和目标账户的最新信息
*/
AccountMergeResultVO merge(AccountMergeDTO dto);
}
@@ -0,0 +1,157 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.dto.affiliationsetting.PersonnelAffiliationDTO;
import com.roomroot.jwgl.dto.affiliationsetting.PersonnelTransferDTO;
import com.roomroot.jwgl.dto.affiliationsetting.ResearchOfficeAffiliationAddDTO;
import com.roomroot.jwgl.dto.affiliationsetting.ResearchOfficeAffiliationIdDTO;
import com.roomroot.jwgl.dto.affiliationsetting.ResearchOfficeAffiliationQueryDTO;
import com.roomroot.jwgl.dto.affiliationsetting.ResearchOfficeOptionQueryDTO;
import com.roomroot.jwgl.dto.affiliationsetting.TeachingClassAffiliationDTO;
import com.roomroot.jwgl.vo.affiliationsetting.AffiliationDepartmentVO;
import com.roomroot.jwgl.vo.affiliationsetting.PersonnelAffiliationVO;
import com.roomroot.jwgl.vo.affiliationsetting.ResearchOfficeAffiliationVO;
import com.roomroot.jwgl.vo.affiliationsetting.ResearchOfficeVO;
import com.roomroot.jwgl.vo.affiliationsetting.TeachingClassAffiliationVO;
import com.roomroot.jwgl.vo.affiliationsetting.TransferHistoryVO;
import java.util.List;
/**
* 隶属关系设置统一服务。
*/
public interface AffiliationSettingService {
/**
* 查询可供隶属关系页面选择的启用部别。
*
* @param dto 查询条件
* @return 可选部别列表
*/
List<AffiliationDepartmentVO> listResearchOfficeDepartments(
ResearchOfficeOptionQueryDTO dto);
/**
* 查询指定部别已经关联的教研室。
*
* @param dto 部别编号及查询条件
* @return 已建立的教研室隶属关系列表
*/
List<ResearchOfficeAffiliationVO> listResearchOfficeAffiliations(
ResearchOfficeAffiliationQueryDTO dto);
/**
* 查询尚未分配且处于启用状态的教研室。
*
* @param dto 查询条件
* @return 可添加的教研室列表
*/
List<ResearchOfficeVO> listAvailableResearchOffices(ResearchOfficeOptionQueryDTO dto);
/**
* 新增教研室隶属关系。
*
* @param dto 部别编号和教研室编号
* @return 新增后的隶属关系
*/
ResearchOfficeAffiliationVO addResearchOfficeAffiliation(
ResearchOfficeAffiliationAddDTO dto);
/**
* 解除教研室隶属关系。
*
* @param dto 隶属关系编号
* @return 被解除的隶属关系
*/
ResearchOfficeAffiliationVO removeResearchOfficeAffiliation(
ResearchOfficeAffiliationIdDTO dto);
/**
* 查询可供教学班次隶属关系页面选择的启用部别。
*
* @param dto 查询条件
* @return 可选部别列表
*/
List<AffiliationDepartmentVO> listTeachingClassDepartments(
TeachingClassAffiliationDTO dto);
/**
* 查询指定部别已经关联的教学班次。
*
* @param dto 部别编号及查询条件
* @return 已建立的教学班次隶属关系列表
*/
List<TeachingClassAffiliationVO> listTeachingClassAffiliations(
TeachingClassAffiliationDTO dto);
/**
* 查询尚未分配给任何部别的教学班次。
*
* @param dto 查询条件
* @return 可添加的教学班次列表
*/
List<TeachingClassAffiliationVO> listAvailableTeachingClasses(
TeachingClassAffiliationDTO dto);
/**
* 新增教学班次隶属关系。
*
* @param dto 部别编号和教学班次编号
* @return 新增后的隶属关系
*/
TeachingClassAffiliationVO addTeachingClassAffiliation(
TeachingClassAffiliationDTO dto);
/**
* 解除教学班次隶属关系。
*
* @param dto 部别编号和教学班次编号
* @return 被解除的隶属关系
*/
TeachingClassAffiliationVO removeTeachingClassAffiliation(
TeachingClassAffiliationDTO dto);
// ============ 人员隶属关系统一接口(方向1新增)============
/**
* 查询人员当前隶属关系。
* <p>按人员类型+部门编号筛选,返回当前归属该部门的人员列表。</p>
*
* @param dto 查询条件(personnelType必填,departmentId可选筛选)
* @return 人员隶属关系列表
*/
List<PersonnelAffiliationVO> listPersonnelAffiliations(PersonnelAffiliationDTO dto);
/**
* 分配人员到部门/组织单元(首次归属,不写异动历史)。
*
* @param dto 分配参数(personnelType+personnelId+departmentId必填)
* @return 分配后的人员隶属关系
*/
PersonnelAffiliationVO assignPersonnel(PersonnelAffiliationDTO dto);
/**
* 解除人员隶属关系(清空当前归属,不写异动历史)。
*
* @param dto 解除参数(personnelType+personnelId必填)
* @return 解除后的人员隶属关系
*/
PersonnelAffiliationVO unassignPersonnel(PersonnelAffiliationDTO dto);
/**
* 人员异动调整(核心接口)。
* <p>更新当前归属 + 记录旧归属到previousDept + 追加异动历史JSON +
* 校验新教研室/学员队在SSJGJYS/SSJGXYD中已有部门关系。</p>
*
* @param dto 异动参数(含reason必填)
* @return 异动后的人员隶属关系
*/
PersonnelAffiliationVO transferPersonnel(PersonnelTransferDTO dto);
/**
* 查询人员异动历史。
*
* @param dto 查询参数(personnelType+personnelId必填)
* @return 异动历史VO(含JSON原文)
*/
TransferHistoryVO getTransferHistory(PersonnelAffiliationDTO dto);
}
@@ -0,0 +1,58 @@
package com.roomroot.jwgl.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.BZLB;
import java.util.List;
/**
* 保障类别 Service
*/
public interface BZLBService {
/**
* 新增
*
* @param bzlb 保障类别
*/
void add(BZLB bzlb);
/**
* 删除
*
* @param bh 编号
*/
void delete(String bh);
/**
* 更新
*
* @param bzlb 保障类别
*/
void update(BZLB bzlb);
/**
* 根据编号查询
*
* @param bh 编号
* @return 保障类别
*/
BZLB getByBh(String bh);
/**
* 分页查询
*
* @param pageNum 页码
* @param pageSize 每页数量
* @param bzlb 查询条件
* @return 分页结果
*/
Page<BZLB> list(int pageNum, int pageSize, BZLB bzlb);
/**
* 查询所有
*
* @return 所有保障类别
*/
List<BZLB> all();
}
@@ -0,0 +1,84 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.entity.JXLB;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 教学楼表服务接口
*/
public interface BuildService {
/**
* 新增教学楼
*
* @param jxlb 教学楼
*/
void add(JXLB jxlb);
/**
* 删除教学楼
*
* @param id 主键
*/
void delete(String id);
/**
* 更新教学楼
*
* @param jxlb 教学楼
*/
void update(JXLB jxlb);
/**
* 根据教学楼代号查询
*
* @param id 唯一标识
* @return 教学楼
*/
JXLB getById(String id);
/**
* 分页查询教学楼列表
*
* @param query 分页参数
* @param jxlb 查询条件
* @return 分页结果
*/
PageResult<JXLB> pageList(PageQuery query, JXLB jxlb);
/**
* 查询所有教学楼列表
*
* @param jxlb 查询条件
* @return 教学楼列表
*/
List<JXLB> list(JXLB jxlb);
/**
* 导入Excel文件
*
* @param file Excel文件
* @return 导入数量
*/
int importExcel(MultipartFile file);
/**
* 下载模板文件
*
* @param response HTTP响应
*/
void downloadTemplate(HttpServletResponse response);
/**
* 生成下一个序号/教学楼代号
*
* @return 格式化后的序号
*/
String generateNextXh();
}
@@ -0,0 +1,75 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.entity.JXCDL;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import java.util.List;
/**
* 教学场地历服务接口
*/
public interface ClassRoomCalendarService {
/**
* 新增教学场地历
*
* @param jxcdl 教学场地历
*/
void add(JXCDL jxcdl);
/**
* 删除教学场地历
*
* @param id 主键ID
*/
void delete(String id);
/**
* 更新教学场地历
*
* @param jxcdl 教学场地历
*/
void update(JXCDL jxcdl);
/**
* 根据ID查询
*
* @param id 主键ID
* @return 教学场地历
*/
JXCDL getById(String id);
/**
* 分页查询教学场地历列表
*
* @param query 分页参数
* @param jxcdl 查询条件
* @return 分页结果
*/
PageResult<JXCDL> pageList(PageQuery query, JXCDL jxcdl);
/**
* 查询所有教学场地历列表
*
* @param jxcdl 查询条件
* @return 教学场地历列表
*/
List<JXCDL> list(JXCDL jxcdl);
/**
* 根据教室编号查询教学场地历列表
*
* @param jsbh 教室编号
* @return 教学场地历列表
*/
List<JXCDL> listByJsbh(String jsbh);
/**
* 根据日期查询教学场地历列表
*
* @param rq 日期
* @return 教学场地历列表
*/
List<JXCDL> listByRq(String rq);
}
@@ -0,0 +1,68 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.entity.JSBKCB;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import java.util.List;
/**
* 教室输出课程表服务接口
*/
public interface ClassRoomLessonService {
/**
* 新增教室输出课程表
*
* @param jsbkcb 教室输出课程表
*/
void add(JSBKCB jsbkcb);
/**
* 删除教室输出课程表
*
* @param id 主键ID
*/
void delete(String id);
/**
* 更新教室输出课程表
*
* @param jsbkcb 教室输出课程表
*/
void update(JSBKCB jsbkcb);
/**
* 根据ID查询
*
* @param id 主键ID
* @return 教室输出课程表
*/
JSBKCB getById(String id);
/**
* 根据教室编号和年度查询
*
* @param jsbh 教室编号
* @param nd 年度
* @return 教室输出课程表
*/
JSBKCB getByJsbhAndNd(String jsbh, Integer nd);
/**
* 分页查询教室输出课程表列表
*
* @param query 分页参数
* @param jsbkcb 查询条件
* @return 分页结果
*/
PageResult<JSBKCB> pageList(PageQuery query, JSBKCB jsbkcb);
/**
* 查询所有教室输出课程表列表
*
* @param jsbkcb 查询条件
* @return 教室输出课程表列表
*/
List<JSBKCB> list(JSBKCB jsbkcb);
}
@@ -0,0 +1,67 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.entity.JSB;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import java.util.List;
/**
* 教室表服务接口
*/
public interface ClassRoomService {
/**
* 新增教室
*
* @param jsb 教室
*/
void add(JSB jsb);
/**
* 删除教室
*
* @param id 唯一标识符
*/
void delete(String id);
/**
* 更新教室
*
* @param jsb 教室
*/
void update(JSB jsb);
/**
* 根据教室编号查询
*
* @param id 教室编号
* @return 教室
*/
JSB getById(String id);
/**
* 分页查询教室列表
*
* @param query 分页参数
* @param jsb 查询条件
* @return 分页结果
*/
PageResult<JSB> pageList(PageQuery query, JSB jsb);
/**
* 查询所有教室列表
*
* @param jsb 查询条件
* @return 教室列表
*/
List<JSB> list(JSB jsb);
/**
* 根据教学楼代号查询教室列表
*
* @param jxldh 教学楼代号
* @return 教室列表
*/
List<JSB> listByJxldh(String jxldh);
}
@@ -0,0 +1,52 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.entity.XYDRWB;
import java.util.List;
import java.util.Map;
/**
* 学员队任务表服务接口
*/
public interface ClassSemesterCalendarService {
/**
* 更新
*
* @param xydrwb 实体
*/
void update(XYDRWB xydrwb);
/**
* 根据编号查询
*
* @param bh 编号
* @return 实体
*/
XYDRWB getByBh(String bh);
/**
* 条目批次复制
*
* @param sourceBhList 源学员队任务表编号列表
* @param targetBhList 目标学员队期表编号列表
* @return 包含count(复制数量)、failedSourceBhs(不存在的源编号)、failedTargetBhs(不存在的目标编号)
*/
Map<String, Object> batchCopy(List<String> sourceBhList, List<String> targetBhList);
/**
* 按学期批次复制
*
* @param sourceXydxqbh 源学员队期表编号
* @param targetBhList 目标学员队期表编号列表
* @return 包含count(复制数量)、failedTargetBhs(不存在的目标编号)
*/
Map<String, Object> batchCopyBySemester(String sourceXydxqbh, List<String> targetBhList);
/**
* 查询所有有效数据(DEL_FLAG = 0)
*
* @return 所有有效数据列表
*/
List<XYDRWB> listAllValid(XYDRWB xydrwb);
}
@@ -0,0 +1,116 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.entity.XYDNDXQJBXXB;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import java.util.List;
/**
* 班次学期服务接口
*/
public interface ClassSemesterService {
/**
* 新增
*
* @param xydndxqjbxxb 实体
*/
void add(XYDNDXQJBXXB xydndxqjbxxb);
/**
* 删除
*
* @param bh 编号
*/
void delete(String bh);
/**
* 批量删除
*
* @param bhList 编号列表
*/
void batchDelete(List<String> bhList);
/**
* 更新
*
* @param xydndxqjbxxb 实体
*/
void update(XYDNDXQJBXXB xydndxqjbxxb);
/**
* 根据编号查询
*
* @param bh 编号
* @return 实体
*/
XYDNDXQJBXXB getByBh(String bh);
/**
* 分页查询列表
*
* @param query 分页参数
* @param xydndxqjbxxb 查询条件
* @return 分页结果
*/
PageResult<XYDNDXQJBXXB> pageList(PageQuery query, XYDNDXQJBXXB xydndxqjbxxb);
/**
* 查询所有有效数据(DEL_FLAG = 0)
*
* @return 所有有效数据列表
*/
List<XYDNDXQJBXXB> listAllValid();
/**
* 根据选修班次列表批量添加班次学期
*
* @param startTime 时间范围开始
* @param endTime 时间范围结束
* @param nd 年度
* @param xqdc 学期第次
* @param xqkssj 学期开始时间
* @param xqjssj 学期结束时间
* @return 添加数量
*/
int batchAddFromElective(java.time.LocalDateTime startTime, java.time.LocalDateTime endTime,
String nd, String xqdc, String xqkssj, String xqjssj);
/**
* 批量修改开放教员排课状态
*
* @param bhList 编号列表
* @param kfjypk 开放教员排课(0-否,1-是)
* @return 更新数量
*/
int batchUpdateKfjypk(List<String> bhList, Integer kfjypk);
/**
* 批量修改学期第次
*
* @param bhList 编号列表
* @param xqdc 学期第次
* @return 更新数量
*/
int batchUpdateXqdc(List<String> bhList, Integer xqdc);
/**
* 批量修改教学任务编号
*
* @param bhList 编号列表
* @param jxrwbh 教学任务编号
* @return 更新数量
*/
int batchUpdateJxrwbh(List<String> bhList, String jxrwbh);
/**
* 批量修改班次学期的日期范围,并同步更新日历记录
*
* @param bhList 编号列表
* @param xqkssj 学期开始时间
* @param xqjssj 学期结束时间
* @return 更新数量
*/
int batchUpdateDateRange(List<String> bhList, String xqkssj, String xqjssj);
}
@@ -0,0 +1,548 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.dto.courserunning.ClassesImportDTO;
import com.roomroot.jwgl.dto.courserunning.ImportRemoveDTO;
import com.roomroot.jwgl.dto.courserunning.ScheduleAdjustApplyDTO;
import com.roomroot.jwgl.dto.courserunning.ScheduleAdjustAuditDTO;
import com.roomroot.jwgl.dto.courserunning.ScheduleAdjustQueryDTO;
import com.roomroot.jwgl.dto.courserunning.SemesterImportDTO;
import com.roomroot.jwgl.dto.courserunning.SingleCourseImportDTO;
import com.roomroot.jwgl.dto.courserunning.TeachingPlanBatchFillDTO;
import com.roomroot.jwgl.dto.courserunning.TeachingPlanFillDTO;
import com.roomroot.jwgl.dto.courserunning.TeachingPlanQueryDTO;
import com.roomroot.jwgl.dto.courserunning.TeachingSupportBatchPlanDTO;
import com.roomroot.jwgl.dto.courserunning.TeachingSupportConfirmDTO;
import com.roomroot.jwgl.dto.courserunning.TeachingSupportPlanDTO;
import com.roomroot.jwgl.dto.courserunning.TeachingSupportUpdateDTO;
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.CourseRunningImportResultVO;
import com.roomroot.jwgl.vo.courserunning.ScheduleAdjustApplyVO;
import com.roomroot.jwgl.vo.courserunning.ScheduleAdjustDetailVO;
import com.roomroot.jwgl.vo.courserunning.TeachingPlanDetailVO;
import com.roomroot.jwgl.vo.courserunning.TeachingPlanFillResultVO;
import com.roomroot.jwgl.vo.courserunning.TeachingPlanLessonVO;
import com.roomroot.jwgl.vo.courserunning.TeachingSupportConfirmResultVO;
import com.roomroot.jwgl.vo.courserunning.TeachingSupportHistoryVO;
import com.roomroot.jwgl.vo.courserunning.TeachingSupportPlanVO;
import com.roomroot.jwgl.vo.courserunning.TeachingSupportResourceVO;
import com.roomroot.jwgl.vo.courserunning.StudentLeaveApplyVO;
import com.roomroot.jwgl.vo.courserunning.StudentLeaveDetailVO;
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;
/**
* 课程教学运行管理服务接口。
* <p>
* 提供课程教学运行管理的全部业务能力,包括:<br>
* 1. 教学运行数据导入:从课程教学计划编制子系统向课程教学运行管理子系统导入数据;<br>
* 2. 教学实施计划填写:对已导入的课次进行教学实施计划内容的填写、查询和提交。
* </p>
*/
public interface CourseRunningImportService {
// ==================== 教学运行数据导入 ====================
/**
* 按学期导入课程。
* <p>
* 从课程教学计划编制子系统导入指定年度学期的所有课程安排数据。
* </p>
*
* @param params 学期导入参数
* @return 导入结果
*/
CourseRunningImportResultVO importBySemester(SemesterImportDTO params);
/**
* 按教学班次导入课程。
* <p>
* 从课程教学计划编制子系统导入指定教学班次的课程安排数据。
* </p>
*
* @param params 教学班次导入参数
* @return 导入结果
*/
CourseRunningImportResultVO importByClasses(ClassesImportDTO params);
/**
* 导入单门课程。
* <p>
* 从课程教学计划编制子系统导入指定教学班次的单门课程安排数据。
* </p>
*
* @param params 单门课程导入参数
* @return 导入结果
*/
CourseRunningImportResultVO importSingleCourse(SingleCourseImportDTO params);
/**
* 删除已导入课程。
* <p>
* 删除已导入到课程教学运行管理子系统的课次数据,支持按学期、班次或单个课次删除。
* </p>
*
* @param params 删除参数
*/
void removeImportedCourses(ImportRemoveDTO params);
// ==================== 教学实施计划填写 ====================
/**
* 分页查询待填写或已填写的课次列表。
* <p>
* 根据查询条件(实施课程编号、年度、教学方法、日期范围、填写状态等)
* 分页查询课次列表,返回课次基本信息及填写状态。
* </p>
/**
* 分页查询待填写或已填写的课次列表。
*
* @param query 查询参数(包含分页信息和筛选条件)
* @return 分页课次列表
*/
PageResult<TeachingPlanLessonVO> queryLessonList(TeachingPlanQueryDTO query);
/**
* 按查询条件导出课次数据为 Excel 文件。
*
* @param query 查询参数(包含筛选条件)
* @return Excel 文件字节数组
*/
byte[] exportLessonListToExcel(TeachingPlanQueryDTO query);
/**
* 查询单个课次的教学实施计划详情。
* <p>
* 根据课次编号查询该课次的完整教学实施计划信息,包括教学内容、教学要点、
* 教学方法、日期节次、班组信息、保障需求及教员备注等。
* </p>
*
* @param sskcbbh 实施_课程表编号(课次编号)
* @return 教学实施计划详情
*/
TeachingPlanDetailVO getTeachingPlanDetail(String sskcbbh);
/**
* 单条填写教学实施计划。
* <p>
* 对单个课次填写教学实施计划内容,保存后填写状态更新为"已填写"。
* </p>
*
* @param dto 教学实施计划填写参数
* @return 填写结果
*/
TeachingPlanFillResultVO fillTeachingPlan(TeachingPlanFillDTO dto);
/**
* 批量填写教学实施计划。
* <p>
* 一次性提交多个课次的教学实施计划填写数据,逐条处理并返回整体执行结果。
* </p>
*
* @param dto 批量教学实施计划填写参数
* @return 批量填写结果
*/
TeachingPlanFillResultVO batchFillTeachingPlan(TeachingPlanBatchFillDTO dto);
/**
* 提交教学实施计划。
* <p>
* 将已填写的教学实施计划提交审核,提交后填写状态更新为"已提交",
* 已提交的实施计划原则上不允许再修改,如需修改需先撤回。
* </p>
*
* @param sskcbbh 实施_课程表编号(课次编号)
*/
void submitTeachingPlan(String sskcbbh);
// ==================== 调课管理 ====================
/**
* 分页查询调课申请列表。
* <p>
* 根据查询条件(实施_课程表编号、申请教员、年度、审批状态、查收状态、
* 删除状态、申请日期范围等)分页查询调课申请列表。
* </p>
*
* @param query 查询参数(包含分页信息和筛选条件)
* @return 分页调课申请列表
*/
PageResult<ScheduleAdjustApplyVO> queryScheduleAdjustList(ScheduleAdjustQueryDTO query);
/**
* 查询单个调课申请的完整详情。
* <p>
* 根据调课申请编号查询该申请的完整信息,包括申请信息、调整后的内容、
* 教研室审批状态、机关审批记录、关联的教室、辅助教员、学员队、保障明细等。
* </p>
*
* @param ssdksqbh 调课申请编号
* @return 调课申请详情
*/
ScheduleAdjustDetailVO getScheduleAdjustDetail(String ssdksqbh);
/**
* 提交调课申请。
* <p>
* 教员提交调课申请,包含课次编号、调课事由、新的日期节次、新的教室、
* 辅助教员、学员队、保障明细等完整信息。
* 同时更新课次数据,将课次的日期节次替换为调整后的新值。
* </p>
*
* @param dto 调课申请参数
* @return 调课申请编号
*/
String submitScheduleAdjust(ScheduleAdjustApplyDTO dto);
/**
* 教研室审批调课申请。
* <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);
// ==================== 教学保障管理 ====================
/**
* 查询教学保障提供资源列表。
* <p>
* 从保障提供明细表中查询可供选择的保障资源,支持按保障类别编号筛选。
* </p>
*
* @param bzlbbh 保障类别编号,为空时查询全部
* @return 保障提供资源列表
*/
List<TeachingSupportResourceVO> querySupportResourceList(String bzlbbh);
/**
* 查询课次的教学保障计划列表。
* <p>
* 根据课次编号查询该课次已登记的教学保障计划明细,包括保障名称、单位、
* 数量、备注、确认状态及确认信息等。
* </p>
*
* @param sskcbbh 实施_课程表编号(课次编号)
* @return 教学保障计划列表
*/
List<TeachingSupportPlanVO> queryTeachingSupportPlanList(String sskcbbh);
/**
* 登记单条教学保障计划。
* <p>
* 为指定课次登记一条教学保障需求,指定保障提供明细、数量、备注等信息。
* </p>
*
* @param dto 教学保障计划登记参数
* @return 课程表_保障明细编号
*/
String saveTeachingSupportPlan(TeachingSupportPlanDTO dto);
/**
* 批量登记教学保障计划。
* <p>
* 一次性为指定课次登记多条教学保障需求。
* </p>
*
* @param dto 教学保障计划批量登记参数
*/
void batchSaveTeachingSupportPlan(TeachingSupportBatchPlanDTO dto);
/**
* 修改教学保障计划。
* <p>
* 修改已登记的教学保障计划,包括调整数量和备注信息。
* 已确认的教学保障计划不允许修改,如需修改需先取消确认。
* </p>
*
* @param dto 教学保障计划修改参数
*/
void updateTeachingSupportPlan(TeachingSupportUpdateDTO dto);
/**
* 删除教学保障计划。
* <p>
* 删除已登记的教学保障计划。
* 已确认的教学保障计划不允许删除,如需删除需先取消确认。
* </p>
*
* @param kcbbzmxbh 课程表_保障明细编号
*/
void deleteTeachingSupportPlan(String kcbbzmxbh);
/**
* 确认教学保障到位。
* <p>
* 确认某条教学保障计划已经落实到位,记录确认人、确认时间和确认备注。
* 已确认的保障计划不允许重复确认。
* </p>
*
* @param dto 教学保障到位确认参数
*/
void confirmTeachingSupport(TeachingSupportConfirmDTO dto);
/**
* 批量确认教学保障到位。
* <p>
* 一次性确认多条教学保障计划已经落实到位,逐条处理并返回整体执行结果。
* </p>
*
* @param dtoList 教学保障到位确认参数列表
* @return 批量确认结果
*/
TeachingSupportConfirmResultVO batchConfirmTeachingSupport(List<TeachingSupportConfirmDTO> dtoList);
/**
* 取消教学保障到位确认。
* <p>
* 取消某条教学保障计划的到位确认,恢复为未确认状态。
* </p>
*
* @param kcbbzmxbh 课程表_保障明细编号
*/
void cancelConfirmTeachingSupport(String kcbbzmxbh);
/**
* 查询教学保障历史联想列表。
* <p>
* 根据当前课次编号,查询同一实施课程下其他历史课次已登记过的保障物资,
* 按保障提供明细编号聚合去重,统计历史使用次数、最近登记时间和平均数量,
* 供教师在登记新保障时快速联想选择,避免重复录入。
* </p>
*
* @param sskcbbh 当前课次编号(实施_课程表编号)
* @return 历史联想保障物资列表
*/
List<TeachingSupportHistoryVO> listTeachingSupportHistory(String sskcbbh);
// ==================== 学员正课请假 ====================
/**
* 分页查询学员请假申请列表。
* <p>
* 根据查询条件分页查询学员正课请假申请记录,支持按请假编号、学员编号、
* 学员姓名、学员队编号、审批状态、年度、创建时间范围等多条件筛选。
* </p>
*
* @param query 查询参数(包含分页信息和筛选条件)
* @return 分页学员请假申请列表
*/
PageResult<StudentLeaveApplyVO> queryStudentLeaveList(StudentLeaveQueryDTO query);
/**
* 查询学员请假申请详情。
* <p>
* 根据请假编号查询该申请的完整信息,包括学员信息、请假时间范围、
* 审批状态、审批记录及关联课次列表。
* </p>
*
* @param qjbh 请假编号
* @return 学员请假申请详情
*/
StudentLeaveDetailVO getStudentLeaveDetail(String qjbh);
/**
* 学员提交请假申请。
* <p>
* 学员在线提交正课请假申请,包含请假原因、时间范围、请假类型、
* 关联课次等信息。系统自动生成请假编号并记录申请时间。
* </p>
*
* @param dto 学员请假申请参数
* @return 请假编号
*/
String submitStudentLeave(StudentLeaveApplyDTO dto);
/**
* 教师审批学员请假申请。
* <p>
* 教师审批学员提交的正课请假申请,包含同意、拒绝、发回修改三种处理方式。
* 审批通过后联动更新考勤记录。
* </p>
*
* @param dto 教师审批参数
*/
void auditStudentLeave(StudentLeaveAuditDTO dto);
/**
* 学员撤回请假申请。
* <p>
* 学员撤回自己提交的、未经过审批的请假申请。
* </p>
*
* @param qjbh 请假编号
* @param xybh 学员编号(用于权限校验)
*/
void cancelStudentLeave(String qjbh, String xybh);
/**
* 学员查询自己的请假申请列表。
* <p>
* 学员自助查询接口,只能查看自己提交的请假记录,支持分页和按审批状态筛选。
* 返回结果中请假类型已翻译为字典标签。
* </p>
*
* @param query 查询参数(学员编号xybh必填,分页信息和审批状态可选)
* @return 分页学员请假申请列表
*/
PageResult<StudentLeaveApplyVO> queryStudentOwnLeaveList(StudentLeaveQueryDTO query);
// ==================== 运行分析 ====================
/**
* 获取教学运行概览指标。
* <p>
* 返回教学运行关键指标的汇总数据,包括整体排课完成率、学员缺勤率、
* 场地平均使用率、教师周均课时等核心指标。
* </p>
*
* @param query 查询参数(包含年度、学期筛选条件)
* @return 运行概览指标
*/
OperationOverviewVO getOperationOverview(OperationAnalysisQueryDTO query);
/**
* 获取各教学系排课完成率对比数据。
* <p>
* 返回各教学系的排课完成率,用于柱状图展示。
* </p>
*
* @param query 查询参数(包含年度、学期筛选条件)
* @return 各教学系排课完成率列表
*/
List<CourseCompletionByDeptVO> getCourseCompletionByDept(OperationAnalysisQueryDTO query);
/**
* 获取场地使用率分布数据。
* <p>
* 返回不同场地类型的使用率分布情况,用于饼图展示。
* </p>
*
* @param query 查询参数(包含年度、学期筛选条件)
* @return 场地使用率分布列表
*/
List<VenueUsageRateVO> getVenueUsageRate(OperationAnalysisQueryDTO query);
/**
* 获取学期调课频次趋势数据。
* <p>
* 返回学期内各时间段的调课频次变化趋势,用于折线图展示。
* </p>
*
* @param query 查询参数(包含年度、学期筛选条件)
* @return 调课频次趋势列表
*/
List<ScheduleAdjustTrendVO> getScheduleAdjustTrend(OperationAnalysisQueryDTO query);
/**
* 导出运行分析报表为 Excel 文件。
* <p>
* 汇总运行概览、各教学系排课完成率、场地使用率分布、调课频次趋势四类数据,
* 分别写入同一 Excel 文件的多个工作表,便于教学管理决策存档与汇报。
* </p>
*
* @param query 查询参数(包含年度、学期筛选条件)
* @return Excel 文件字节数组
*/
byte[] exportAnalysisToExcel(OperationAnalysisQueryDTO query);
}
@@ -0,0 +1,71 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.entity.KCBZ;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import java.util.List;
/**
* 课程教学管理服务接口
* <p>
* 提供课程标准的增加、删除(填写撤回原因)、更新、查询(含课堂列表)及多版本管理。
* </p>
*/
public interface CourseTeachingService {
/**
* 新增课程标准(含课堂列表)
*
* @param kcbz 课程标准(ktList 附带课堂列表)
*/
void add(KCBZ kcbz);
/**
* 撤回(删除)课程标准——填写撤回原因
*
* @param bh 课程标准编号
* @param chyy 撤回原因
*/
void delete(String bh, String chyy);
/**
* 更新课程标准(先删后插课堂列表)
*
* @param kcbz 课程标准(ktList 附带课堂列表)
*/
void update(KCBZ kcbz);
/**
* 查询课程标准详情(含课堂列表)
*
* @param bh 编号
* @return 课程标准(内嵌 ktList)
*/
KCBZ getById(String bh);
/**
* 分页查询课程标准(含课堂列表)
*
* @param query 分页参数
* @param kcbz 查询条件
* @return 分页结果(每项内嵌 ktList)
*/
PageResult<KCBZ> pageList(PageQuery query, KCBZ kcbz);
/**
* 查询某课程的所有版本
*
* @param kbh 课编号
* @return 版本列表
*/
List<KCBZ> getVersionsByCourse(String kbh);
/**
* 从课程表自动生成课程标准
*
* @param kbh 课编号
* @return 课程标准编号
*/
String generateFromCourse(String kbh);
}
@@ -0,0 +1,82 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.dto.department.DepartmentCreateDTO;
import com.roomroot.jwgl.dto.department.DepartmentIdDTO;
import com.roomroot.jwgl.dto.department.DepartmentQueryDTO;
import com.roomroot.jwgl.dto.department.DepartmentUpdateDTO;
import com.roomroot.jwgl.vo.department.DepartmentTreeVO;
import com.roomroot.jwgl.vo.department.DepartmentVO;
import java.util.List;
/**
* 部别管理服务。
*/
public interface DepartmentManagementService {
/**
* 新增部别。
*
* @param dto 新增参数
* @return 新增后的部别信息
*/
DepartmentVO add(DepartmentCreateDTO dto);
/**
* 修改部别。
*
* @param dto 修改参数
* @return 修改后的部别信息
*/
DepartmentVO update(DepartmentUpdateDTO dto);
/**
* 查询单个部别。
*
* @param dto 部别编号参数
* @return 部别详情
*/
DepartmentVO get(DepartmentIdDTO dto);
/**
* 查询部别平铺列表。
*
* @param dto 查询条件
* @return 部别列表
*/
List<DepartmentVO> list(DepartmentQueryDTO dto);
/**
* 查询部别树。
*
* @param dto 查询条件
* @return 部别树
*/
List<DepartmentTreeVO> tree(DepartmentQueryDTO dto);
/**
* 停用部别。
*
* @param dto 部别编号参数
* @return 停用后的部别信息
*/
DepartmentVO disable(DepartmentIdDTO dto);
/**
* 启用部别。
*
* @param dto 部别编号参数
* @return 启用后的部别信息
*/
DepartmentVO enable(DepartmentIdDTO dto);
/**
* 删除部别(逻辑删除)。
* <p>删除前必须确认不存在启用中的下级部别,
* 并且不存在教研室、学员队、机关参谋或教学管理机构引用。</p>
*
* @param dto 部别编号参数
* @return 被删除的部别信息
*/
DepartmentVO delete(DepartmentIdDTO dto);
}
@@ -0,0 +1,107 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.dto.departmentpersonnel.DepartmentPersonnelCreateDTO;
import com.roomroot.jwgl.dto.departmentpersonnel.DepartmentPersonnelIdDTO;
import com.roomroot.jwgl.dto.departmentpersonnel.DepartmentPersonnelImportDTO;
import com.roomroot.jwgl.dto.departmentpersonnel.DepartmentPersonnelQueryDTO;
import com.roomroot.jwgl.dto.departmentpersonnel.DepartmentPersonnelUpdateDTO;
import com.roomroot.jwgl.vo.departmentpersonnel.DepartmentPersonnelDepartmentVO;
import com.roomroot.jwgl.vo.departmentpersonnel.DepartmentPersonnelVO;
import java.util.List;
/**
* 部别人员管理服务。
*/
public interface DepartmentPersonnelService {
/**
* 查询人员管理页面可选择的启用部别。
*
* @param dto 部别查询条件
* @return 启用部别列表
*/
List<DepartmentPersonnelDepartmentVO> listDepartments(
DepartmentPersonnelQueryDTO dto);
/**
* 查询部别人员列表。
*
* @param dto 人员查询条件
* @return 人员列表
*/
List<DepartmentPersonnelVO> list(DepartmentPersonnelQueryDTO dto);
/**
* 查询部别人员详情。
*
* @param dto 人员编号参数
* @return 人员详情
*/
DepartmentPersonnelVO get(DepartmentPersonnelIdDTO dto);
/**
* 新增部别人员。
*
* @param dto 人员新增参数
* @return 新增后的人员信息
*/
DepartmentPersonnelVO add(DepartmentPersonnelCreateDTO dto);
/**
* 修改部别人员。
*
* @param dto 人员修改参数
* @return 修改后的人员信息
*/
DepartmentPersonnelVO update(DepartmentPersonnelUpdateDTO dto);
/**
* 将人员标记为离职。
*
* @param dto 人员编号参数
* @return 离职后的人员信息
*/
DepartmentPersonnelVO leave(DepartmentPersonnelIdDTO dto);
/**
* 将人员恢复为在职。
*
* @param dto 人员编号参数
* @return 复职后的人员信息
*/
DepartmentPersonnelVO restore(DepartmentPersonnelIdDTO dto);
/**
* 批量导入部别人员。
*
* @param dto 批量导入参数
* @return 成功导入的人员列表
*/
List<DepartmentPersonnelVO> batchImport(DepartmentPersonnelImportDTO dto);
/**
* 通过Excel文件导入部别人员。
*
* @param inputStream Excel文件输入流
* @param fileName 文件名
* @return 成功导入的人员列表
*/
List<DepartmentPersonnelVO> importFromExcel(java.io.InputStream inputStream, String fileName);
/**
* 按查询条件导出人员数据。
*
* @param dto 导出筛选条件
* @return 排序后的人员数据
*/
List<DepartmentPersonnelVO> export(DepartmentPersonnelQueryDTO dto);
/**
* 按查询条件导出人员数据为 Excel 文件。
*
* @param dto 导出筛选条件
* @return Excel 文件字节数组
*/
byte[] exportToExcel(DepartmentPersonnelQueryDTO dto);
}
@@ -0,0 +1,106 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.dto.dict.DictDataBatchQueryDTO;
import com.roomroot.jwgl.dto.dict.DictDataQueryDTO;
import com.roomroot.jwgl.dto.dict.DictDataSaveDTO;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.vo.dict.DictDataVO;
import java.util.List;
import java.util.Map;
/**
* 字典数据服务。
* <p>
* 负责字典数据的查询(单类型、分页、批量)、保存和删除业务逻辑。
* 字典数据是字典类型下的具体选项,包含标签(显示文本)、值(存储值)、
* 排序、状态、是否默认等属性,为前端下拉框、单选、多选等组件提供数据源。
* </p>
*/
public interface DictDataService {
/**
* 按类型编码查询全部启用的字典数据(不分页,用于前端下拉框)。
* <p>
* 返回指定字典类型下状态为启用的所有数据项,按排序号升序排列。
* 仅返回启用状态的数据,保证前端下拉框中不展示已停用的选项。
* </p>
*
* @param dictTypeCode 字典类型编码
* @return 字典数据列表
*/
List<DictDataVO> listByType(String dictTypeCode);
/**
* 分页查询字典数据列表(管理页面用)。
* <p>
* 根据查询条件进行分页筛选,支持按字典类型编码、关键字(匹配标签或值)、
* 状态进行过滤,结果按排序号升序排列。
* 与 {@link #listByType} 不同,此接口可查看启用和停用的全部数据项。
* </p>
*
* @param dto 查询条件,包含分页参数和筛选条件
* @return 字典数据分页结果
*/
PageResult<DictDataVO> page(DictDataQueryDTO dto);
/**
* 批量查询多个类型的字典数据。
* <p>
* 根据传入的多个字典类型编码,一次性查询所有类型的启用数据,
* 返回结果按类型编码分组,便于前端页面初始化时批量获取多个下拉框数据。
* </p>
*
* @param dto 批量查询参数,包含字典类型编码列表
* @return 按类型编码分组的数据
*/
Map<String, List<DictDataVO>> batchList(DictDataBatchQueryDTO dto);
/**
* 保存字典数据(新增或编辑)。
* <p>
* 当 {@code dto.dictDataId} 为空时执行新增操作,
* 当 {@code dto.dictDataId} 有值时执行编辑操作。
* 保存时会校验关联的字典类型是否存在;
* 若 {@code isDefault} 设置为 1,会自动取消该类型下其他数据的默认标记,
* 保证同一类型下只有一个默认选项。
* </p>
*
* @param dto 保存参数
* @return 保存后的字典数据
*/
DictDataVO save(DictDataSaveDTO dto);
/**
* 删除字典数据。
* <p>
* 根据字典数据ID删除单条记录,删除前校验数据是否存在。
* </p>
*
* @param dictDataId 字典数据ID
*/
void delete(Long dictDataId);
/**
* 获取字典数据的历史版本列表。
* <p>
* 返回该字典数据所有的历史快照信息,用于版本回溯查看。
* </p>
*
* @param dictDataId 字典数据ID
* @return 历史版本快照列表
*/
List<Map<String, Object>> getHistory(Long dictDataId);
/**
* 回滚字典数据到指定版本。
* <p>
* 根据目标版本号,从历史快照中恢复数据,并创建新的版本记录。
* </p>
*
* @param dictDataId 字典数据ID
* @param targetVersion 目标版本号
* @return 回滚后的字典数据详情
*/
DictDataVO rollback(Long dictDataId, Integer targetVersion);
}
@@ -0,0 +1,80 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.dto.dict.DictTypeQueryDTO;
import com.roomroot.jwgl.dto.dict.DictTypeSaveDTO;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.vo.dict.DictTypeVO;
import java.util.List;
import java.util.Map;
/**
* 字典类型服务。
* <p>
* 负责字典类型的查询、保存和删除业务逻辑。
* 字典类型是对系统字典的分类,如课程类型、职称等级、教室用途等,
* 每个类型下可维护多条具体的字典数据项。
* </p>
*/
public interface DictTypeService {
/**
* 分页查询字典类型列表。
* <p>
* 根据查询条件进行分页筛选,支持按关键字(匹配类型编码或名称)、
* 所属分组、状态进行过滤,结果按创建时间降序排列。
* </p>
*
* @param dto 查询条件,包含分页参数和筛选条件
* @return 字典类型分页结果
*/
PageResult<DictTypeVO> page(DictTypeQueryDTO dto);
/**
* 保存字典类型(新增或编辑)。
* <p>
* 当 {@code dto.dictTypeId} 为空时执行新增操作,
* 当 {@code dto.dictTypeId} 有值时执行编辑操作。
* 新增时校验字典类型编码全局唯一性;
* 编辑时若修改了编码,同样需要校验新编码的唯一性。
* </p>
*
* @param dto 保存参数
* @return 保存后的字典类型
*/
DictTypeVO save(DictTypeSaveDTO dto);
/**
* 删除字典类型。
* <p>
* 删除前校验字典类型是否存在,并检查该类型下是否有关联的字典数据项。
* 若存在关联数据项,则抛出业务异常,禁止删除,防止影响已引用该字典的业务数据。
* </p>
*
* @param dictTypeId 字典类型ID
*/
void delete(Long dictTypeId);
/**
* 获取字典类型的历史版本列表。
* <p>
* 返回该字典类型所有的历史快照信息,用于版本回溯查看。
* </p>
*
* @param dictTypeId 字典类型ID
* @return 历史版本快照列表
*/
List<Map<String, Object>> getHistory(Long dictTypeId);
/**
* 回滚字典类型到指定版本。
* <p>
* 根据目标版本号,从历史快照中恢复数据,并创建新的版本记录。
* </p>
*
* @param dictTypeId 字典类型ID
* @param targetVersion 目标版本号
* @return 回滚后的字典类型详情
*/
DictTypeVO rollback(Long dictTypeId, Integer targetVersion);
}
@@ -0,0 +1,54 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.entity.XKZYXX;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import org.springframework.stereotype.Service;
/**
* 学科专业管理服务接口
* <p>
* 提供学科、专业的增加、删除(逻辑删除)、更新、分页查询等功能。
* 对应数据表:学科专业信息
* </p>
*/
@Service
public interface DisciplineService {
/**
* 新增学科专业
*
* @param entity 学科专业实体
*/
void add(XKZYXX entity);
/**
* 逻辑删除(停用)学科专业
*
* @param bSH 标识号
*/
void disable(String bSH);
/**
* 更新学科专业
*
* @param entity 学科专业实体
*/
void update(XKZYXX entity);
/**
* 根据标识号查询学科专业详情
*
* @param bSH 标识号
* @return 学科专业实体
*/
XKZYXX getById(String bSH);
/**
* 分页条件查询学科专业列表
*
* @param query 分页参数
* @return 分页结果
*/
PageResult<XKZYXX> pageList(PageQuery query, XKZYXX xkzyxx);
}
@@ -0,0 +1,131 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.dto.elective.AddClassDTO;
import com.roomroot.jwgl.entity.*;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.vo.elective.*;
import com.roomroot.jwgl.vo.elective.*;
import java.time.LocalDateTime;
import java.util.List;
/**
* 教学班次管理服务接口
*/
public interface ElectiveService {
// ========== 教学班次(学员队表) ==========
/** 新增教学班次(同时写入学员队表和学员队期表) */
void add(AddClassDTO dto);
void disable(String xydbh);
void update(XYDB entity);
XYDB getById(String xydbh);
PageResult<XYDB> pageList(PageQuery query, XYDB cond);
/** 双表联查分页列表(含学号规则、注册开关) */
PageResult<ElectiveClassVO> pageListWithPeriod(PageQuery query, ElectiveClassVO cond);
/**
* 根据时间范围过滤学员队表
* <p>
* 查找入学日期和毕业日期包含指定时间范围的学员队
* 即:入学日期 <= startTime 且 毕业日期 >= endTime
* </p>
*
* @param startTime 时间范围开始
* @param endTime 时间范围结束
* @return 符合条件的学员队列表
*/
List<XYDB> filterByDateRange(LocalDateTime startTime, LocalDateTime endTime);
// ========== 学员队期(学号规则+注册设置) ==========
XYDQB getPeriodByXydbh(String xydbh);
void updatePeriod(XYDQB entity);
/** 设置是否允许学员自行注册 */
void setRegistrationEnabled(String xydbh, Integer enabled);
/** 生成下一个学员学号(格式:入学年份年月日 + 三位流水号) */
String generateNextStudentNumber(String xydbh);
// ========== 关联信息查询 ==========
/** 查询班次的课程列表(通过实施_课程学员队关联) */
List<ClassScheduleVO> getClassCourses(String xydbh, Integer nd);
/** 查询班次的学期课程表 */
List<ClassScheduleVO> getClassSchedule(String xydbh, Integer nd);
/** 查询班次的学员列表 */
List<XYXX> getClassStudents(String xydbh);
/** 查询班次的学员成绩 */
List<StudentGradeVO> getClassGrades(String xydbh, String kmbh);
/** 查询班次的学员缺勤记录 */
List<StudentAbsenceVO> getClassAbsences(String xydbh, Integer nd);
// ========== 教学班次学期信息设置 ==========
/**
* 查询教学楼列表(教学班次学期信息设置窗口 - 教学楼下拉框)
*
* @return 教学楼列表
*/
List<JXLB> listBuildings();
/**
* 查询教室列表(教学班次学期信息设置窗口 - 专用教室选择)
* <p>支持按教学楼代号过滤,以及按教室名称拼音首字母快速查找。</p>
*
* @param jxldh 教学楼代号(可选)
* @param py 教室名称拼音首字母(可选,模糊匹配)
* @return 教室列表
*/
List<JSB> listRooms(String jxldh, String py);
/**
* 获取教学班次学期信息设置窗口的初始化默认值
* <p>
* 根据班次学制(入学/毕业日期)计算:
* <ul>
* <li>学制半年以内:学期第次固定为第一学期,开学日期=入学日期,结束日期=毕业日期</li>
* <li>学制半年以上:按入学日期智能推断当前学期第次(可手动修改),
* 开学/结束日期默认取窗口预设日期(presetKxrq/presetJsrq)</li>
* <li>第二以上学期:默认预设上学期的专用教室</li>
* </ul>
* </p>
*
* @param xydbh 班次编号(学员队编号)
* @param presetKxrq 窗口预设开学日期(yyyy-MM-dd,半年以上班次使用,可选)
* @param presetJsrq 窗口预设结束日期(yyyy-MM-dd,半年以上班次使用,可选)
* @return 初始化默认值
*/
SemesterInitVO getSemesterInit(String xydbh, String presetKxrq, String presetJsrq);
/**
* 创建班次学期信息(教学班次学期信息设置窗口 - 确定按钮)
* <p>
* 自动处理默认逻辑:
* <ul>
* <li>学制半年以内:学期第次强制为第一学期(传入其他值将校验失败),
* 开学/结束日期为空时默认取班次的入学/毕业日期</li>
* <li>学制半年以上:学期第次为空时按入学日期智能推断;
* 开学/结束日期需由窗口预设值传入,不允许为空</li>
* <li>第二以上学期:专用教室为空时,默认预设上学期的专用教室</li>
* <li>同一班次+年度+学期第次已存在时拒绝重复创建</li>
* </ul>
* </p>
*
* @param xydndxqjbxxb 班次学期信息(编号/变动时间/删除标记等由服务端自动填充)
*/
void addSemester(XYDNDXQJBXXB xydndxqjbxxb);
}
@@ -0,0 +1,99 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.dto.faculty.AddTeacherDTO;
import com.roomroot.jwgl.entity.*;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.vo.faculty.TeacherListVO;
/**
* 教研室与教员管理服务接口
* <p>
* 提供教研室、教员的增加、删除、更新、查询等功能管理,
* 以及教员属性、年度综合评定、课程表等关联信息的查询。
* </p>
*/
public interface FacultyService {
// ==================== 教研室管理 ====================
/**
* 新增教研室
*/
void addJYSB(JYSB entity);
/**
* 停用(逻辑删除)教研室
*/
void disableJYSB(String jYSDH);
/**
* 更新教研室
*/
void updateJYSB(JYSB entity);
/**
* 根据代号查询教研室详情
*/
JYSB getJYSBById(String jYSDH);
/**
* 分页条件查询教研室列表
*/
PageResult<JYSB> pageJYSB(PageQuery query, JYSB cond);
// ==================== 教员管理 ====================
/**
* 新增教员(同时创建教员属性记录)
* <p>接收前端传回的教员基本信息和属性信息,一次性写入两张表。</p>
*/
void addJYB(AddTeacherDTO dto);
/**
* 离职(逻辑删除)教员
*/
void disableJYB(String jYBH);
/**
* 更新教员基本信息
*/
void updateJYB(JYB entity);
/**
* 根据编号查询教员详情(含属性信息)
*/
JYB getJYBById(String jYBH);
/**
* 分页条件查询教员列表
*/
/** 分页条件查询教员列表(三表联查) */
PageResult<TeacherListVO> pageJYBDetail(PageQuery query, JYB cond);
// ==================== 教员属性管理 ====================
/**
* 根据教员编号查询教员属性
*/
JYSX getJYSXByJYBH(String jYBH);
/**
* 更新教员属性
*/
void updateJYSX(JYSX entity);
// ==================== 年度综合评定查询 ====================
/**
* 分页查询年度综合评定_教员
*/
PageResult<NDZHPDJY> pageNDZHPDJY(PageQuery query, NDZHPDJY cond);
// ==================== 教员课程表查询 ====================
/**
* 分页查询教员输出课程表
*/
PageResult<JYSCKCB> pageJYSCKCB(PageQuery query, JYSCKCB cond);
}
@@ -0,0 +1,54 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.entity.KCB;
import java.util.List;
/**
* 课程表服务接口
*/
public interface KCBService {
/**
* 新增
*
* @param kcb 实体
*/
void add(KCB kcb);
/**
* 删除
*
* @param kbh 课编号
*/
void delete(String kbh);
/**
* 批量删除
*
* @param kbhList 课编号列表
*/
void batchDelete(List<String> kbhList);
/**
* 更新
*
* @param kcb 实体
*/
void update(KCB kcb);
/**
* 根据课编号查询
*
* @param kbh 课编号
* @return 实体
*/
KCB getByKbh(String kbh);
/**
* 查询所有数据
*
* @return 所有数据列表
*/
List<KCB> list();
}
@@ -0,0 +1,116 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.dto.LeaveDTO;
import com.roomroot.jwgl.dto.LeaveQueryDTO;
import com.roomroot.jwgl.entity.XYJXQJB;
import com.roomroot.jwgl.entity.XYQJSPB;
import java.util.List;
/**
* 请假服务接口
*/
public interface LeaveService {
/**
* 新增请假(主表+课堂+学员)
*
* @param dto 请假DTO
*/
void add(LeaveDTO dto);
/**
* 更新请假主表
*
* @param xyjxqjb 实体
* @return 是否成功
*/
boolean update(XYJXQJB xyjxqjb);
/**
* 删除请假(主表+课堂+学员+审批)
*
* @param bh 编号
* @return 是否成功
*/
boolean delete(String bh);
/**
* 根据编号查询请假主表
*
* @param bh 编号
* @return 实体
*/
XYJXQJB getByBh(String bh);
/**
* 查询所有请假主表数据
*
* @return 列表
*/
List<XYJXQJB> list();
/**
* 根据条件查询请假主表数据
* 支持起始时间、结束时间、创建时间范围查询
*
* @param queryDTO 查询条件
* @return 列表
*/
List<XYJXQJB> list(LeaveQueryDTO queryDTO);
/**
* 根据请假编号查询请假课堂列表
*
* @param qjbh 请假编号
* @return 课堂编号列表
*/
List<String> listClassroomBhsByQjbh(String qjbh);
/**
* 根据请假编号查询请假学员列表
*
* @param qjbh 请假编号
* @return 学员编号列表
*/
List<String> listStudentBhsByQjbh(String qjbh);
// ===== 审批表 CRUD =====
/**
* 新增审批
*
* @param xyqjspb 审批实体
*/
void addApproval(XYQJSPB xyqjspb);
/**
* 删除审批
*
* @param bh 编号
*/
void deleteApproval(String bh);
/**
* 更新审批
*
* @param xyqjspb 审批实体
*/
void updateApproval(XYQJSPB xyqjspb);
/**
* 根据编号查询审批
*
* @param bh 编号
* @return 审批实体
*/
XYQJSPB getApprovalByBh(String bh);
/**
* 根据请假编号查询审批列表
*
* @param qjbh 请假编号
* @return 审批列表
*/
List<XYQJSPB> listApprovalsByQjbh(String qjbh);
}
@@ -0,0 +1,172 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.entity.*;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.vo.log.TeacherHoursVO;
import com.roomroot.jwgl.vo.log.TeachingLogListVO;
import org.springframework.stereotype.Service;
/**
* 教学日志管理服务接口
* <p>
* 涵盖教学日志核心管理、课时核算标准、课时补助标准、联教联训、教师工作量等全功能。
* </p>
*/
@Service
public interface LogService {
// ==================== 教学日志核心 ====================
/** 新增教学日志 */
void addTeachingLog(BCKTJXRZ entity);
/** 更新教学日志 */
void updateTeachingLog(BCKTJXRZ entity);
/** 根据编号查询 */
BCKTJXRZ getTeachingLogById(String bh);
/** 分页条件查询(基于班次课堂教学日志) */
PageResult<BCKTJXRZ> pageTeachingLog(PageQuery query, BCKTJXRZ cond);
/** 分页条件查询教学日志列表(基于实施_课程表_日志联查,展示字段更丰富) */
PageResult<TeachingLogListVO> pageTeachingLogDetail(PageQuery query, TeachingLogListVO cond);
// ==================== 教学日志(基于实施_课程表_日志 36字段表) ====================
/** 新增教学日志 */
void addLog(SSKCB_RZ entity);
/** 退回教学日志(填写退回意见) */
void rejectLog(String bh, String thyj);
/** 根据课表自动创建教学日志 */
SSKCB_RZ autoCreateLog(String tybh);
/** 导入教学日志 */
void importLog(java.util.List<SSKCB_RZ> list);
/** 批量上报教学日志(根据主键编号组将状态修改为已上报,仅对待上报数据生效,返回实际更新条数) */
int batchReportLogs(java.util.List<String> bhList);
/** 批量审核教学日志(根据主键编号组将状态修改为已审核,仅对已上报数据生效,返回实际更新条数) */
int batchAuditLogs(java.util.List<String> bhList);
/** 从 Excel 导入教学日志 */
void importLogFromExcel(org.springframework.web.multipart.MultipartFile file);
/** 导出教学日志 Excel */
void exportLogExcel(TeachingLogListVO cond, jakarta.servlet.http.HttpServletResponse response);
/** 提交审核(状态流转) */
void submitApproval(String bh, String targetStatus);
/** 审核通过 */
void approve(String bh, String feedback);
/** 审核退回 */
void reject(String bh, String feedback);
/** 自动创建教学日志(基于课表信息) */
BCKTJXRZ autoCreateFromSchedule(String sskcbh);
/** 对照课表查看教学日志填报状态 */
PageResult<?> checkLogStatusBySchedule(PageQuery query, String jybh, Integer nd);
// ==================== 教学日志授课教员 ====================
void addLogTeacher(BCKTJXRZSKJY entity);
PageResult<BCKTJXRZSKJY> pageLogTeacher(PageQuery query, BCKTJXRZSKJY cond);
// ==================== 教学日志学员学习情况 ====================
void addStudentLearningStatus(JXRZXYXXQK entity);
PageResult<JXRZXYXXQK> pageStudentLearningStatus(PageQuery query, String jxrzbh);
// ==================== 课时核算标准 ====================
void addCalculationStandard(KSHBZ entity);
void updateCalculationStandard(KSHBZ entity);
KSHBZ getCalculationStandardById(String bh);
PageResult<KSHBZ> pageCalculationStandard(PageQuery query, KSHBZ cond);
// ==================== 课时系数方案 ====================
/** 新增课时系数方案 */
void addCoefficientPlan(KSXSFA entity);
/** 更新课时系数方案(需含编号) */
void updateCoefficientPlan(KSXSFA entity);
/** 根据编号查询课时系数方案 */
KSXSFA getCoefficientPlanById(String bh);
/** 分页条件查询课时系数方案列表(名称模糊匹配) */
PageResult<KSXSFA> pageCoefficientPlan(PageQuery query, KSXSFA cond);
// ==================== 课时费标准 ====================
void addFeeStandard(KSFBZ entity);
void updateFeeStandard(KSFBZ entity);
KSFBZ getFeeStandardById(String bh);
PageResult<KSFBZ> pageFeeStandard(PageQuery query, KSFBZ cond);
// ==================== 技术职务课时费标准 ====================
void addTechPosFeeStandard(JSZWKSFBZ entity);
void updateTechPosFeeStandard(JSZWKSFBZ entity);
JSZWKSFBZ getTechPosFeeStandardById(String bh);
PageResult<JSZWKSFBZ> pageTechPosFeeStandard(PageQuery query, JSZWKSFBZ cond);
// ==================== 课时补助项目 ====================
void addSubsidyProject(KSBZXM entity);
void updateSubsidyProject(KSBZXM entity);
KSBZXM getSubsidyProjectById(String bh);
PageResult<KSBZXM> pageSubsidyProject(PageQuery query, KSBZXM cond);
// ==================== 教学日志课时补助审批表(汇总) ====================
void addSubsidyApproval(JXRZKSBZSPB entity);
void updateSubsidyApproval(JXRZKSBZSPB entity);
JXRZKSBZSPB getSubsidyApprovalById(String bh);
PageResult<JXRZKSBZSPB> pageSubsidyApproval(PageQuery query, JXRZKSBZSPB cond);
/** 计算汇总数据(总课时量、优质课时量、总补助金额) */
void calculateSummary(String bh);
// ==================== 教学日志教员课时补助明细 ====================
PageResult<JXRZJYKSBZ> pageSubsidyDetail(PageQuery query, JXRZJYKSBZ cond);
// ==================== 教员课时补助 ====================
void addTeacherSubsidy(JYKSBZ entity);
PageResult<JYKSBZ> pageTeacherSubsidy(PageQuery query, JYKSBZ cond);
// ==================== 年终课时费 ====================
void addYearEndFee(NZKSF entity);
void updateYearEndFee(NZKSF entity);
NZKSF getYearEndFeeById(String bh);
PageResult<NZKSF> pageYearEndFee(PageQuery query, NZKSF cond);
// ==================== 教员年终课时费 ====================
PageResult<JYNZKSF> pageTeacherYearEndFee(PageQuery query, JYNZKSF cond);
// ==================== 教师工作量 ====================
void addWorkload(JZGZL entity);
void updateWorkload(JZGZL entity);
JZGZL getWorkloadById(String bh);
PageResult<JZGZL> pageWorkload(PageQuery query, JZGZL cond);
/** 自动统计课程教学工作量 */
void calcCourseWorkload(String jybh, Integer nd, String xq);
/** 比对各类教师工作量情况 */
PageResult<?> compareWorkload(PageQuery query, Integer nd, String xq);
// ==================== 教学实施计划统计 ====================
PageResult<JXSSJHTJB> pageTeachingStats(PageQuery query, JXSSJHTJB cond);
// ==================== 教员课时查询管理 ====================
/**
* 分页查询教员课时信息
* <p>
* 数据来源:学期教员信息表 JOIN 教员表 JOIN 教研室表。
* 列表展示教研室名称、教员姓名、教员类别、当前/学期职称、当前/学期专业技术职务等级、
* 主讲课时、辅讲课时、课程总课时、课程折算总课时、项目补助课时、总课时。
* 支持按教员姓名、教研室、部系、年度(学期)、职称、职务等级以及各课时量数字范围(xx课时至xx课时)筛选。
* </p>
*
* @param query 分页参数
* @param cond 查询条件(含课时量范围)
* @return 分页结果
*/
PageResult<TeacherHoursVO> pageTeacherHours(PageQuery query, TeacherHoursVO cond);
}
@@ -0,0 +1,98 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementCreateDTO;
import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementIdDTO;
import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementPriorityDTO;
import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementPublishDTO;
import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementQueryDTO;
import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementRecipientQueryDTO;
import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementUpdateDTO;
import com.roomroot.jwgl.dto.noticeannouncement.UserNoticeAnnouncementQueryDTO;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementDetailVO;
import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementPublishResultVO;
import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementRecipientVO;
import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementStatisticsVO;
import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementVO;
import com.roomroot.jwgl.vo.noticeannouncement.UserNoticeAnnouncementDetailVO;
import com.roomroot.jwgl.vo.noticeannouncement.UserNoticeAnnouncementVO;
/**
* 通知公告管理服务。
*
* <p>在同一个业务服务中维护公告生命周期、发布范围、接收人快照、
* 用户阅读和统计查询,不为同一功能重复创建 Service 类。</p>
*/
public interface NoticeAnnouncementService {
/**
* 分页查询后台通知公告。
*/
PageResult<NoticeAnnouncementVO> list(
NoticeAnnouncementQueryDTO dto);
/**
* 查询通知公告后台详情。
*/
NoticeAnnouncementDetailVO get(
NoticeAnnouncementIdDTO dto);
/**
* 新增一条拟制公告。
*/
NoticeAnnouncementDetailVO add(
NoticeAnnouncementCreateDTO dto);
/**
* 修改拟制或已下架公告。
*/
NoticeAnnouncementDetailVO update(
NoticeAnnouncementUpdateDTO dto);
/**
* 发布公告并生成接收人快照。
*/
NoticeAnnouncementPublishResultVO publish(
NoticeAnnouncementPublishDTO dto);
/**
* 下架已发布公告。
*/
NoticeAnnouncementDetailVO down(
NoticeAnnouncementIdDTO dto);
/**
* 删除拟制或已下架公告。
*/
void delete(NoticeAnnouncementIdDTO dto);
/**
* 调整公告优先级。
*/
NoticeAnnouncementDetailVO updatePriority(
NoticeAnnouncementPriorityDTO dto);
/**
* 分页查询公告接收人和阅读明细。
*/
PageResult<NoticeAnnouncementRecipientVO> recipients(
NoticeAnnouncementRecipientQueryDTO dto);
/**
* 查询公告接收和阅读统计。
*/
NoticeAnnouncementStatisticsVO statistics(
NoticeAnnouncementIdDTO dto);
/**
* 分页查询当前登录账户可见的公告。
*/
PageResult<UserNoticeAnnouncementVO> userList(
UserNoticeAnnouncementQueryDTO dto);
/**
* 当前登录账户打开公告并完成阅读记录。
*/
UserNoticeAnnouncementDetailVO read(
NoticeAnnouncementIdDTO dto);
}
@@ -0,0 +1,67 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.dto.notificationlog.NotificationLogExportDTO;
import com.roomroot.jwgl.dto.notificationlog.NotificationLogIdDTO;
import com.roomroot.jwgl.dto.notificationlog.NotificationLogQueryDTO;
import com.roomroot.jwgl.dto.notificationlog.NotificationLogRecipientQueryDTO;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.vo.notificationlog.NotificationLogDetailVO;
import com.roomroot.jwgl.vo.notificationlog.NotificationLogRecipientVO;
import com.roomroot.jwgl.vo.notificationlog.NotificationLogStatisticsVO;
import com.roomroot.jwgl.vo.notificationlog.NotificationLogVO;
import java.util.List;
/**
* 通知日志管理服务。
*
* <p>同一个业务服务将逐步维护通知发送历史、详情、接收阅读明细、
* 汇总统计和导出能力,不为每个小功能重复创建 Service 类。</p>
*/
public interface NotificationLogService {
/**
* 分页查询已经实际发送的通知日志。
*
* @param dto 通知日志查询条件
* @return 经过筛选、统计补全和稳定排序的分页结果
*/
PageResult<NotificationLogVO> list(
NotificationLogQueryDTO dto);
/**
* 根据通知编号查询单条发送日志详情。
*
* @param dto 通知日志编号参数
* @return 通知主体和完整发送、阅读统计
*/
NotificationLogDetailVO get(
NotificationLogIdDTO dto);
/**
* 分页查询指定通知的接收对象和阅读明细。
*
* @param dto 通知编号、分页和接收阅读筛选条件
* @return 按账户去重的接收与阅读明细
*/
PageResult<NotificationLogRecipientVO> recipients(
NotificationLogRecipientQueryDTO dto);
/**
* 按当前筛选条件汇总通知发送和阅读情况。
*
* @param dto 通知日志筛选条件
* @return 通知数量、发送结果和阅读情况汇总
*/
NotificationLogStatisticsVO statistics(
NotificationLogExportDTO dto);
/**
* 按当前筛选条件查询全部待导出的通知日志。
*
* @param dto 通知日志导出筛选条件
* @return 与列表筛选和排序规则一致的全部通知日志
*/
List<NotificationLogVO> export(
NotificationLogExportDTO dto);
}
@@ -0,0 +1,74 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.entity.JYSRWS;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import java.util.List;
/**
* 教研室任务书服务接口
*/
public interface OfficeTaskBookService {
/**
* 新增
*
* @param jysrws 实体
*/
void add(JYSRWS jysrws);
/**
* 删除
*
* @param bh 编号
*/
void delete(String bh);
/**
* 批量删除
*
* @param bhList 编号列表
*/
void batchDelete(List<String> bhList);
/**
* 更新
*
* @param jysrws 实体
*/
void update(JYSRWS jysrws);
/**
* 根据编号查询
*
* @param bh 编号
* @return 实体
*/
JYSRWS getByBh(String bh);
/**
* 分页查询列表
*
* @param query 分页参数
* @param jysrws 查询条件
* @return 分页结果
*/
PageResult<JYSRWS> pageList(PageQuery query, JYSRWS jysrws);
/**
* 根据教学任务编号查询
*
* @param jxrwbh 教学任务编号
* @return 教研室任务书列表
*/
List<JYSRWS> listByJxrwbh(String jxrwbh);
/**
* 根据教研室代号查询
*
* @param jysdh 教研室代号
* @return 教研室任务书列表
*/
List<JYSRWS> listByJysdh(String jysdh);
}
@@ -0,0 +1,59 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.dto.operationlog.OperationLogExportDTO;
import com.roomroot.jwgl.dto.operationlog.OperationLogIdDTO;
import com.roomroot.jwgl.dto.operationlog.OperationLogQueryDTO;
import com.roomroot.jwgl.dto.operationlog.OperationLogRecordDTO;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.vo.operationlog.OperationLogDetailVO;
import com.roomroot.jwgl.vo.operationlog.OperationLogModuleVO;
import com.roomroot.jwgl.vo.operationlog.OperationLogVO;
import java.util.List;
/**
* 操作日志管理服务。
*
* <p>统一维护操作日志的查询、详情、导出和内部写入能力,
* 不为同一业务功能重复创建多个业务服务类。</p>
*/
public interface OperationLogService {
/**
* 分页查询操作日志。
*
* @param dto 查询条件
* @return 经过筛选和稳定排序的操作日志分页结果
*/
PageResult<OperationLogVO> list(OperationLogQueryDTO dto);
/**
* 查询操作日志页面可使用的模块选项。
*
* @return 与模块筛选校验规则保持一致的模块选项
*/
List<OperationLogModuleVO> modules();
/**
* 根据编号查询操作日志详情。
*
* @param dto 操作日志编号参数
* @return 操作日志、登录记录及操作账户的完整审计信息
*/
OperationLogDetailVO get(OperationLogIdDTO dto);
/**
* 按筛选条件查询全部待导出的操作日志。
*
* @param dto 导出筛选条件
* @return 经过统一筛选、排序和模块识别的操作日志列表
*/
List<OperationLogVO> export(OperationLogExportDTO dto);
/**
* 写入一条由服务端自动采集的操作日志。
*
* @param dto 当前登录记录编号、操作内容和请求地址
*/
void record(OperationLogRecordDTO dto);
}
@@ -0,0 +1,58 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.entity.XQXLB;
import java.util.List;
/**
* 学期校历表服务接口
*/
public interface SemesterCalendarService {
/**
* 新增学期校历
*
* @param startDate 学期起始日期
* @param endDate 学期结束日期
*/
void add(String startDate,String endDate);
/**
* 删除学期校历
*
* @param bh 编号
*/
void delete(String bh);
/**
* 更新学期校历
*
* @param xqxlb 学期校历
*/
void update(XQXLB xqxlb);
/**
* 根据编号查询学期校历
*
* @param bh 编号
* @return 学期校历
*/
XQXLB getById(String bh);
/**
* 根据年度查询学期校历列表(全量)
*
* @param nd 年度
* @return 学期校历列表
*/
List<XQXLB> listByNd(Integer nd);
/**
* 导入Excel文件解析
*
* @param file Excel文件
* @return 导入数量
*/
int importExcel(org.springframework.web.multipart.MultipartFile file);
}
@@ -0,0 +1,59 @@
package com.roomroot.jwgl.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.roomroot.jwgl.entity.XQ;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import java.util.List;
/**
* 学期服务接口
*/
public interface SemesterService extends IService<XQ> {
/**
* 新增学期
*
* @param xq 学期
*/
void add(XQ xq);
/**
* 删除学期
*
* @param nd 年度(主键)
*/
void delete(Integer nd);
/**
* 更新学期
*
* @param xq 学期
*/
void update(XQ xq);
/**
* 根据年度查询
*
* @param nd 年度(主键)
* @return 学期
*/
XQ getByNd(Integer nd);
/**
* 分页查询学期列表
*
* @param query 分页参数
* @param xq 查询条件
* @return 分页结果
*/
PageResult<XQ> pageList(PageQuery query, XQ xq);
/**
* 查询所有学期列表
*
* @return 学期列表
*/
List<XQ> list();
}
@@ -0,0 +1,57 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.entity.XYXX;
import com.roomroot.jwgl.entity.XYKCCJ;
import com.roomroot.jwgl.entity.XYKCGCCJ;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 学员档案管理服务接口
* <p>
* 提供学员信息的增删改查、学籍异动管理,
* 以及学员课程、成绩、课程表查询和 Excel 导出功能。
* </p>
*/
public interface StudentRecordsService {
/** 新增学员 */
void add(XYXX xyxx);
/** 删除学员(退学处理) */
void delete(String bh);
/** 更新学员信息 */
void update(XYXX xyxx);
/** 查询学员详情 */
XYXX getById(String bh);
/** 分页查询学员列表 */
PageResult<XYXX> pageList(PageQuery query, XYXX xyxx);
/** 学籍异动——留级 */
void retainGrade(String bh);
/** 学籍异动——降级 */
void downgrade(String bh);
/** 学籍异动——退学 */
void dropOut(String bh);
// ========== 学员课程成绩查询 ==========
/** 查询学员的所有课程成绩 */
List<XYKCCJ> getCourseGrades(String xybh);
/** 查询学员的所有课程过程成绩 */
List<XYKCGCCJ> getCourseProcessGrades(String xybh);
// ========== Excel 导出 ==========
/** 导出学员课程成绩 Excel */
void exportGradesExcel(String xybh, HttpServletResponse response);
}
@@ -0,0 +1,67 @@
package com.roomroot.jwgl.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.XYDSCKCB;
import java.util.List;
/**
* 学员队输出课程表 Service
*/
public interface StudentTeamOutputScheduleService {
/**
* 新增
*
* @param xydsckcb 学员队输出课程表
*/
void add(XYDSCKCB xydsckcb);
/**
* 删除
*
* @param bh 编号
*/
void delete(String bh);
/**
* 更新
*
* @param xydsckcb 学员队输出课程表
*/
void update(XYDSCKCB xydsckcb);
/**
* 根据编号查询
*
* @param bh 编号
* @return 学员队输出课程表
*/
XYDSCKCB getByBh(String bh);
/**
* 根据年度和学员队编号查询
*
* @param nd 年度
* @param xydbh 学员队编号
* @return 学员队输出课程表
*/
XYDSCKCB getByNdAndXydbh(Integer nd, String xydbh);
/**
* 分页查询
*
* @param pageNum 页码
* @param pageSize 每页数量
* @param xydsckcb 查询条件
* @return 分页结果
*/
Page<XYDSCKCB> list(int pageNum, int pageSize, XYDSCKCB xydsckcb);
/**
* 查询所有
*
* @return 所有学员队输出课程表
*/
List<XYDSCKCB> all();
}
@@ -0,0 +1,117 @@
package com.roomroot.jwgl.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.XYDRWB;
import java.util.List;
/**
* 学员队任务表 Service
*/
public interface StudentTeamTaskService {
/**
* 新增
*
* @param xydrwb 学员队任务
*/
void add(XYDRWB xydrwb);
/**
* 删除
*
* @param bh 编号
*/
void delete(String bh);
/**
* 更新
*
* @param xydrwb 学员队任务
*/
void update(XYDRWB xydrwb);
/**
* 根据编号查询
*
* @param bh 编号
* @return 学员队任务
*/
XYDRWB getByBh(String bh);
/**
* 分页查询(按序号标识排序)
*
* @param pageNum 页码
* @param pageSize 每页数量
* @param xydrwb 查询条件
* @return 分页结果
*/
Page<XYDRWB> list(int pageNum, int pageSize, XYDRWB xydrwb);
/**
* 分页查询(按计划课次序号排序)
*
* @param pageNum 页码
* @param pageSize 每页数量
* @param xydrwb 查询条件
* @return 分页结果
*/
Page<XYDRWB> groupList(int pageNum, int pageSize, XYDRWB xydrwb);
/**
* 批量预设合班
* <p>
* 根据编号列表,按年度排序生成统一的计划课次序号
* </p>
*
* @param bhList 编号列表
* @return 更新数量
*/
int batchPresetMerge(List<String> bhList);
/**
* 批量预设拆班
* <p>
* 根据编号列表,清空计划课次序号
* </p>
*
* @param bhList 编号列表
* @return 更新数量
*/
int batchPresetSplit(List<String> bhList);
/**
* 查询所有
*
* @return 所有学员队任务
*/
List<XYDRWB> all();
/**
* 自动生成必修课程
* <p>
* 根据学员队编号从学员队表获取专业代号,
* 再根据专业代号和学期第次查询专业教学计划表,
* 为当前班次自动生成必修课程,插入到学员队任务表。
* 已存在的课程(按课编号判断)不会重复添加。
* </p>
*
* @param xydbh 学员队编号
* @param xqdc 学期第次
* @return 新增数量
*/
int autoGenerateRequiredCourses(String xydbh, Integer xqdc);
/**
* 批量复制课程
* <p>
* 根据传入的编号集合,复制课程信息,替换学员队编号为新的值
* </p>
*
* @param bhList 源编号集合
* @param newXydbh 新学员队编号
* @return 新生成的编号集合
*/
List<String> copy(List<String> bhList, String newXydbh);
}
@@ -0,0 +1,78 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.dto.systeminitialization.SemesterInitializationSaveDTO;
import com.roomroot.jwgl.dto.systeminitialization.SystemInitializationConfigurationSaveDTO;
import com.roomroot.jwgl.dto.systeminitialization.SystemInitializationExecutionDTO;
import com.roomroot.jwgl.dto.systeminitialization.SystemInitializationHistoricalImportDTO;
import com.roomroot.jwgl.vo.systeminitialization.SemesterInitializationVO;
import com.roomroot.jwgl.vo.systeminitialization.SystemInitializationConfigurationVO;
import com.roomroot.jwgl.vo.systeminitialization.SystemInitializationExecutionResultVO;
import com.roomroot.jwgl.vo.systeminitialization.SystemInitializationHistoricalImportResultVO;
import com.roomroot.jwgl.vo.systeminitialization.SystemInitializationPrecheckVO;
/**
* 系统初始化管理服务。
*
* <p>同一个业务服务统一维护系统基础参数、学期初始化、
* 历史基础数据导入、预检查和最终执行确认。</p>
*/
public interface SystemInitializationService {
/**
* 查询当前生效的系统基础参数。
*
* @return 当前系统基础参数
*/
SystemInitializationConfigurationVO getCurrentConfiguration();
/**
* 保存新的系统基础参数版本。
*
* @param dto 系统基础参数
* @return 新的当前配置
*/
SystemInitializationConfigurationVO saveConfiguration(
SystemInitializationConfigurationSaveDTO dto);
/**
* 查询当前学期。
*
* @return 当前学期
*/
SemesterInitializationVO getCurrentSemester();
/**
* 保存指定年度和序号的学期。
*
* @param dto 学期初始化参数
* @return 保存后的学期
*/
SemesterInitializationVO saveSemester(
SemesterInitializationSaveDTO dto);
/**
* 导入历史基础数据。
*
* @param dto 历史数据 Excel 文件
* @return 导入结果
*/
SystemInitializationHistoricalImportResultVO
importHistoricalData(
SystemInitializationHistoricalImportDTO dto);
/**
* 执行系统初始化预检查。
*
* @return 预检查汇总结果
*/
SystemInitializationPrecheckVO precheck();
/**
* 完成系统初始化最终确认。
*
* @param dto 页面预检查快照和确认标志
* @return 初始化执行结果
*/
SystemInitializationExecutionResultVO execute(
SystemInitializationExecutionDTO dto);
}
@@ -0,0 +1,84 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.entity.JCXX;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 教材信息服务接口
*/
public interface TeachingMaterialService {
/**
* 新增教材信息
*
* @param jcxx 教材信息
*/
void add(JCXX jcxx);
/**
* 删除教材信息
*
* @param id 编号
*/
void delete(String id);
/**
* 更新教材信息
*
* @param jcxx 教材信息
*/
void update(JCXX jcxx);
/**
* 根据编号查询
*
* @param id 唯一标识码
* @return 教材信息
*/
JCXX getById(String id);
/**
* 根据编号查询
*
* @param bh 编号
* @return 教材信息
*/
JCXX getByBh(String bh);
/**
* 分页查询教材信息列表
*
* @param query 分页参数
* @param jcxx 查询条件
* @return 分页结果
*/
PageResult<JCXX> pageList(PageQuery query, JCXX jcxx);
/**
* 查询所有教材信息列表
*
* @param jcxx 查询条件
* @return 教材信息列表
*/
List<JCXX> list(JCXX jcxx);
/**
* 导入Excel文件
*
* @param file Excel文件
* @return 导入数量
*/
int importExcel(MultipartFile file);
/**
* 下载模板文件
*
* @param response HTTP响应
*/
void downloadTemplate(HttpServletResponse response);
}
@@ -0,0 +1,70 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.entity.JXSSJH;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 导入数据表-教学实施计划服务接口
*/
public interface TeachingPlanService {
/**
* 新增
*
* @param drsjbjxssjh 实体
*/
void add(JXSSJH drsjbjxssjh);
/**
* 删除
*
* @param bsh 标识号
*/
void delete(String bsh);
/**
* 更新
*
* @param drsjbjxssjh 实体
*/
void update(JXSSJH drsjbjxssjh);
/**
* 根据标识号查询
*
* @param bsh 标识号
* @return 实体
*/
JXSSJH getByBsh(String bsh);
/**
* 分页查询列表
*
* @param query 分页参数
* @param drsjbjxssjh 查询条件
* @return 分页结果
*/
PageResult<JXSSJH> pageList(PageQuery query, JXSSJH drsjbjxssjh);
/**
* 查询所有列表
*
* @param drsjbjxssjh 查询条件
* @return 列表
*/
List<JXSSJH> list(JXSSJH drsjbjxssjh);
/**
* 从Excel导入教学实施计划数据。
* 标识号和登录编号自动生成,其他字段从Excel读取。
*
* @param file Excel文件
* @return 导入的记录数
* @throws Exception 解析或导入异常
*/
int importFromExcel(MultipartFile file) throws Exception;
}
@@ -0,0 +1,75 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.entity.JCKCMXX;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import java.util.List;
/**
* 教材库存明细项服务接口
*/
public interface TeachingStockDetailService {
/**
* 新增教材库存明细项
*
* @param jckcmxx 教材库存明细项
*/
void add(JCKCMXX jckcmxx);
/**
* 删除教材库存明细项
*
* @param id 主键
*/
void delete(String id);
/**
* 更新教材库存明细项
*
* @param jckcmxx 教材库存明细项
*/
void update(JCKCMXX jckcmxx);
/**
* 根据ID查询
*
* @param id 主键
* @return 教材库存明细项
*/
JCKCMXX getById(String id);
/**
* 根据编号查询
*
* @param bh 编号
* @return 教材库存明细项
*/
JCKCMXX getByBh(String bh);
/**
* 根据教材库存单编号查询
*
* @param jckcdbbh 教材库存单编号
* @return 教材库存明细项列表
*/
List<JCKCMXX> getByJckcdbbh(String jckcdbbh);
/**
* 分页查询教材库存明细项列表
*
* @param query 分页参数
* @param jckcmxx 查询条件
* @return 分页结果
*/
PageResult<JCKCMXX> pageList(PageQuery query, JCKCMXX jckcmxx);
/**
* 查询所有教材库存明细项列表
*
* @param jckcmxx 查询条件
* @return 教材库存明细项列表
*/
List<JCKCMXX> list(JCKCMXX jckcmxx);
}
@@ -0,0 +1,67 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.entity.JCKCD;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import java.util.List;
/**
* 教材库存单服务接口
*/
public interface TeachingStockService {
/**
* 新增教材库存单
*
* @param jckcd 教材库存单
*/
void add(JCKCD jckcd);
/**
* 删除教材库存单
*
* @param id 主键
*/
void delete(String id);
/**
* 更新教材库存单
*
* @param jckcd 教材库存单
*/
void update(JCKCD jckcd);
/**
* 根据ID查询
*
* @param id 主键
* @return 教材库存单
*/
JCKCD getById(String id);
/**
* 根据编号查询
*
* @param bh 编号
* @return 教材库存单
*/
JCKCD getByBh(String bh);
/**
* 分页查询教材库存单列表
*
* @param query 分页参数
* @param jckcd 查询条件
* @return 分页结果
*/
PageResult<JCKCD> pageList(PageQuery query, JCKCD jckcd);
/**
* 查询所有教材库存单列表
*
* @param jckcd 查询条件
* @return 教材库存单列表
*/
List<JCKCD> list(JCKCD jckcd);
}
@@ -0,0 +1,74 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.entity.JXRW;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import java.util.List;
/**
* 教学任务服务接口
*/
public interface TeachingTaskService {
/**
* 新增
*
* @param jxrw 实体
*/
void add(JXRW jxrw);
/**
* 删除
*
* @param bh 编号
*/
void delete(String bh);
/**
* 批量删除
*
* @param bhList 编号列表
*/
void batchDelete(List<String> bhList);
/**
* 更新
*
* @param jxrw 实体
*/
void update(JXRW jxrw);
/**
* 根据编号查询
*
* @param bh 编号
* @return 实体
*/
JXRW getByBh(String bh);
/**
* 分页查询列表
*
* @param query 分页参数
* @param jxrw 查询条件
* @return 分页结果
*/
PageResult<JXRW> pageList(PageQuery query, JXRW jxrw);
/**
* 查询所有有效数据(DEL_FLAG = 0)
*
* @return 所有有效数据列表
*/
List<JXRW> listAllValid();
/**
* 发布教学任务及教研室任务书
* 根据教学任务编号修改状态为"发布",并批量更新所有关联的教研室任务书状态为"发布"
*
* @param bh 教学任务编号
* @return 更新数量
*/
int batchPublish(String bh);
}
@@ -0,0 +1,56 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.entity.ZYB;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import org.springframework.stereotype.Service;
/**
* 培养方案管理服务接口
* <p>
* 提供专业/培养方案的增加、删除(逻辑删除)、更新、分页查询等功能。
* 对应数据表:专业表
* 多版本通过培训类型(培训类型)字段区分。
* </p>
*/
@Service
public interface TrainingProgramService {
/**
* 新增培养方案(专业)
*
* @param entity 专业表实体
*/
void add(ZYB entity);
/**
* 逻辑删除(停用)培养方案
*
* @param zYDH 专业代号
*/
void disable(String zYDH);
/**
* 更新培养方案
*
* @param entity 专业表实体
*/
void update(ZYB entity);
/**
* 根据专业代号查询培养方案详情
*
* @param zYDH 专业代号
* @return 专业表实体
*/
ZYB getById(String zYDH);
/**
* 分页条件查询培养方案列表
*
* @param query 分页参数
* @param cond 查询条件
* @return 分页结果
*/
PageResult<ZYB> pageList(PageQuery query, ZYB cond);
}
@@ -0,0 +1,63 @@
package com.roomroot.jwgl.service;
import com.roomroot.jwgl.entity.ZYJXJHB;
import java.util.List;
/**
* 专业教学计划表服务接口
*/
public interface ZYJXJHBService {
/**
* 新增
*
* @param zyjxjhb 实体
*/
void add(ZYJXJHB zyjxjhb);
/**
* 删除(设置停用标识为1)
*
* @param bh 编号
*/
void delete(String bh);
/**
* 批量删除(设置停用标识为1)
*
* @param bhList 编号列表
*/
void batchDelete(List<String> bhList);
/**
* 更新
*
* @param zyjxjhb 实体
*/
void update(ZYJXJHB zyjxjhb);
/**
* 根据编号查询
*
* @param bh 编号
* @return 实体
*/
ZYJXJHB getByBh(String bh);
/**
* 查询所有数据
*
* @return 所有数据列表
*/
List<ZYJXJHB> list();
/**
* 根据专业代号和停用标识查询
*
* @param zydh 专业代号
* @param ty 停用标识(0-正常,1-停用)
* @return 列表
*/
List<ZYJXJHB> listByZydhAndTy(String zydh, Integer ty);
}
@@ -0,0 +1,66 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.BZLB;
import com.roomroot.jwgl.mapper.BZLBMapper;
import com.roomroot.jwgl.service.BZLBService;
import com.roomroot.jwgl.utils.UuidUtil;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import java.util.List;
/**
* 保障类别 Service 实现
*/
@Service
public class BZLBServiceImpl implements BZLBService {
@Resource
private BZLBMapper bzlbMapper;
@Override
public void add(BZLB bzlb) {
bzlb.setBh(UuidUtil.getUUID());
if (bzlb.getTy() == null) {
bzlb.setTy(0);
}
bzlbMapper.insert(bzlb);
}
@Override
public void delete(String bh) {
bzlbMapper.deleteById(bh);
}
@Override
public void update(BZLB bzlb) {
bzlbMapper.updateById(bzlb);
}
@Override
public BZLB getByBh(String bh) {
return bzlbMapper.selectById(bh);
}
@Override
public Page<BZLB> list(int pageNum, int pageSize, BZLB bzlb) {
Page<BZLB> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<BZLB> wrapper = new LambdaQueryWrapper<>();
if (bzlb != null) {
wrapper.like(bzlb.getMc() != null, BZLB::getMc, bzlb.getMc());
wrapper.eq(bzlb.getBmbh() != null, BZLB::getBmbh, bzlb.getBmbh());
wrapper.eq(bzlb.getTy() != null, BZLB::getTy, bzlb.getTy());
}
wrapper.orderByDesc(BZLB::getBh);
return bzlbMapper.selectPage(page, wrapper);
}
@Override
public List<BZLB> all() {
LambdaQueryWrapper<BZLB> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(BZLB::getTy, 0);
return bzlbMapper.selectList(wrapper);
}
}
@@ -0,0 +1,211 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.JXLB;
import com.roomroot.jwgl.mapper.BuildMapper;
import com.roomroot.jwgl.service.BuildService;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.utils.UuidUtil;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.List;
/**
* 教学楼表服务实现类
*/
@Service
public class BuildServiceImpl implements BuildService {
@Resource
private BuildMapper buildMapper;
@Override
public void add(JXLB jxlb) {
String uuid = UuidUtil.getUUID();
jxlb.setId(uuid);
jxlb.setDelFlag(0);
buildMapper.insert(jxlb);
}
@Override
public void delete(String id) {
JXLB jxlb = getById(id);
jxlb.setDelFlag(1);
buildMapper.updateById(jxlb);
}
@Override
public void update(JXLB jxlb) {
buildMapper.updateById(jxlb);
}
@Override
public JXLB getById(String id) {
return buildMapper.selectById(id);
}
@Override
public PageResult<JXLB> pageList(PageQuery query, JXLB jxlb) {
jxlb.setDelFlag(0);
Page<JXLB> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<JXLB> result = buildMapper.selectPageList(page, jxlb);
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
@Override
public List<JXLB> list(JXLB jxlb) {
LambdaQueryWrapper<JXLB> wrapper = new LambdaQueryWrapper<>();
if (jxlb != null) {
if (jxlb.getJxldh() != null && !jxlb.getJxldh().isEmpty()) {
wrapper.like(JXLB::getJxldh, jxlb.getJxldh());
}
if (jxlb.getJxlmc() != null && !jxlb.getJxlmc().isEmpty()) {
wrapper.like(JXLB::getJxlmc, jxlb.getJxlmc());
}
if (jxlb.getDd() != null && !jxlb.getDd().isEmpty()) {
wrapper.like(JXLB::getDd, jxlb.getDd());
}
}
wrapper.orderByAsc(JXLB::getXh);
return buildMapper.selectList(wrapper);
}
@Override
public int importExcel(MultipartFile file) {
try (InputStream is = file.getInputStream();
Workbook workbook = file.getOriginalFilename().endsWith(".xlsx")
? new XSSFWorkbook(is) : new HSSFWorkbook(is)) {
Sheet sheet = workbook.getSheetAt(0);
int count = 0;
// 在循环外获取起始序号
int nextNum = getNextNumber();
// 从第二行开始读取数据(跳过表头)
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row == null) continue;
JXLB jxlb = new JXLB();
// 生成序号(数字字符串)和教学楼代号(三位补零)
jxlb.setXh(String.valueOf(nextNum));
jxlb.setJxldh(String.format("%03d", nextNum));
nextNum++; // 递增序号
// 教学楼名称
jxlb.setJxlmc(getCellValue(row, 2));
// 地点
jxlb.setDd(getCellValue(row, 3));
// 备注
jxlb.setBz(getCellValue(row, 4));
jxlb.setId(UuidUtil.getUUID());
jxlb.setDelFlag(0);
buildMapper.insert(jxlb);
count++;
}
return count;
} catch (Exception e) {
throw new RuntimeException("Excel解析失败: " + e.getMessage(), e);
}
}
@Override
public void downloadTemplate(HttpServletResponse response) {
try {
// 从resources/template目录读取模板文件
ClassPathResource resource = new ClassPathResource("template/template_jxcdgl.xls");
InputStream is = resource.getInputStream();
// 设置响应头
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment;filename=" +
URLEncoder.encode("教学场地管理模板.xls", "UTF-8"));
// 输出文件
OutputStream os = response.getOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
os.flush();
is.close();
os.close();
} catch (Exception e) {
throw new RuntimeException("下载模板失败: " + e.getMessage(), e);
}
}
@Override
public String generateNextXh() {
int nextNum = getNextNumber();
return String.format("%03d", nextNum);
}
/**
* 获取下一个序号(数字)
*/
private int getNextNumber() {
String maxXh = buildMapper.selectMaxXh();
if (maxXh == null || maxXh.isEmpty()) {
return 1;
}
try {
return Integer.parseInt(maxXh) + 1;
} catch (NumberFormatException e) {
return 1;
}
}
/**
* 获取单元格字符串值
*/
private String getCellValue(Row row, int index) {
Cell cell = row.getCell(index, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
if (cell == null) {
return null;
}
CellType type = cell.getCellType();
// 处理公式单元格
if (type == CellType.FORMULA) {
type = cell.getCachedFormulaResultType();
}
switch (type) {
case NUMERIC:
double num = cell.getNumericCellValue();
// 整数就去掉小数点后多余的 .0
if (num == Math.floor(num)) {
return String.valueOf((long) num);
} else {
return String.valueOf(num);
}
case STRING:
return cell.getStringCellValue().trim();
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
default:
return null;
}
}
}
@@ -0,0 +1,101 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.JXCDL;
import com.roomroot.jwgl.mapper.ClassRoomCalendarMapper;
import com.roomroot.jwgl.service.ClassRoomCalendarService;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.utils.UuidUtil;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
/**
* 教学场地历服务实现类
*/
@Service
public class ClassRoomCalendarServiceImpl implements ClassRoomCalendarService {
@Resource
private ClassRoomCalendarMapper jxcdlMapper;
@Override
public void add(JXCDL jxcdl) {
jxcdl.setId(UuidUtil.getUUID());
jxcdl.setDelFlag(0);
jxcdl.setCjsj(LocalDateTime.now());
jxcdlMapper.insert(jxcdl);
}
@Override
public void delete(String id) {
JXCDL jxcdl = getById(id);
jxcdl.setDelFlag(1);
jxcdlMapper.updateById(jxcdl);
}
@Override
public void update(JXCDL jxcdl) {
jxcdlMapper.updateById(jxcdl);
}
@Override
public JXCDL getById(String id) {
return jxcdlMapper.selectById(id);
}
@Override
public PageResult<JXCDL> pageList(PageQuery query, JXCDL jxcdl) {
jxcdl.setDelFlag(0);
Page<JXCDL> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<JXCDL> result = jxcdlMapper.selectPageList(page, jxcdl);
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
@Override
public List<JXCDL> list(JXCDL jxcdl) {
LambdaQueryWrapper<JXCDL> wrapper = buildQueryWrapper(jxcdl);
return jxcdlMapper.selectList(wrapper);
}
@Override
public List<JXCDL> listByJsbh(String jsbh) {
return jxcdlMapper.selectByJsbh(jsbh);
}
@Override
public List<JXCDL> listByRq(String rq) {
return jxcdlMapper.selectByRq(rq);
}
/**
* 构建查询条件
*/
private LambdaQueryWrapper<JXCDL> buildQueryWrapper(JXCDL jxcdl) {
LambdaQueryWrapper<JXCDL> wrapper = new LambdaQueryWrapper<>();
if (jxcdl != null) {
if (jxcdl.getBh() != null && !jxcdl.getBh().isEmpty()) {
wrapper.like(JXCDL::getBh, jxcdl.getBh());
}
if (jxcdl.getMc() != null && !jxcdl.getMc().isEmpty()) {
wrapper.like(JXCDL::getMc, jxcdl.getMc());
}
if (jxcdl.getJsbh() != null && !jxcdl.getJsbh().isEmpty()) {
wrapper.eq(JXCDL::getJsbh, jxcdl.getJsbh());
}
if (jxcdl.getKpk() != null) {
wrapper.eq(JXCDL::getKpk, jxcdl.getKpk());
}
if (jxcdl.getJc() != null) {
wrapper.eq(JXCDL::getJc, jxcdl.getJc());
}
}
wrapper.orderByAsc(JXCDL::getRq, JXCDL::getJc);
return wrapper;
}
}
@@ -0,0 +1,90 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.JSBKCB;
import com.roomroot.jwgl.mapper.ClassRoomLessonMapper;
import com.roomroot.jwgl.service.ClassRoomLessonService;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.utils.UuidUtil;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
/**
* 教室输出课程表服务实现类
*/
@Service
public class ClassRoomJSBKCBServiceImpl implements ClassRoomLessonService {
@Resource
private ClassRoomLessonMapper jsbkcbMapper;
@Override
public void add(JSBKCB jsbkcb) {
jsbkcb.setDelFlag(0);
jsbkcb.setId(UuidUtil.getUUID());
jsbkcb.setGxsj(LocalDateTime.now());
jsbkcbMapper.insert(jsbkcb);
}
@Override
public void delete(String id) {
JSBKCB jsbkcb = getById(id);
jsbkcb.setDelFlag(1);
jsbkcbMapper.updateById(jsbkcb);
}
@Override
public void update(JSBKCB jsbkcb) {
jsbkcbMapper.updateById(jsbkcb);
}
@Override
public JSBKCB getById(String id) {
return jsbkcbMapper.selectById(id);
}
@Override
public JSBKCB getByJsbhAndNd(String jsbh, Integer nd) {
return jsbkcbMapper.selectByJsbhAndNd(jsbh, nd);
}
@Override
public PageResult<JSBKCB> pageList(PageQuery query, JSBKCB jsbkcb) {
jsbkcb.setDelFlag(0);
Page<JSBKCB> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<JSBKCB> result = jsbkcbMapper.selectPageList(page, jsbkcb);
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
@Override
public List<JSBKCB> list(JSBKCB jsbkcb) {
LambdaQueryWrapper<JSBKCB> wrapper = buildQueryWrapper(jsbkcb);
return jsbkcbMapper.selectList(wrapper);
}
/**
* 构建查询条件
*/
private LambdaQueryWrapper<JSBKCB> buildQueryWrapper(JSBKCB jsbkcb) {
LambdaQueryWrapper<JSBKCB> wrapper = new LambdaQueryWrapper<>();
if (jsbkcb != null) {
if (jsbkcb.getNd() != null) {
wrapper.eq(JSBKCB::getNd, jsbkcb.getNd());
}
if (jsbkcb.getJsbh() != null && !jsbkcb.getJsbh().isEmpty()) {
wrapper.like(JSBKCB::getJsbh, jsbkcb.getJsbh());
}
if (jsbkcb.getYkb() != null) {
wrapper.eq(JSBKCB::getYkb, jsbkcb.getYkb());
}
}
wrapper.orderByDesc(JSBKCB::getGxsj);
return wrapper;
}
}
@@ -0,0 +1,97 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.JSB;
import com.roomroot.jwgl.mapper.ClassRoomMapper;
import com.roomroot.jwgl.service.ClassRoomService;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.utils.UuidUtil;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import java.util.List;
/**
* 教室表服务实现类
*/
@Service
public class ClassRoomServiceImpl implements ClassRoomService {
@Resource
private ClassRoomMapper jsbMapper;
@Override
public void add(JSB jsb) {
jsb.setId(UuidUtil.getUUID());
jsb.setDelFlag(0);
jsbMapper.insert(jsb);
}
@Override
public void delete(String id) {
JSB jsb = getById(id);
jsb.setDelFlag(1);
jsbMapper.updateById(jsb);
}
@Override
public void update(JSB jsb) {
jsbMapper.updateById(jsb);
}
@Override
public JSB getById(String id) {
return jsbMapper.selectById(id);
}
@Override
public PageResult<JSB> pageList(PageQuery query, JSB jsb) {
Page<JSB> page = new Page<>(query.getPageNum(), query.getPageSize());
jsb.setDelFlag(0);
Page<JSB> result = jsbMapper.selectPageList(page, jsb);
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
@Override
public List<JSB> list(JSB jsb) {
LambdaQueryWrapper<JSB> wrapper = buildQueryWrapper(jsb);
return jsbMapper.selectList(wrapper);
}
@Override
public List<JSB> listByJxldh(String jxldh) {
LambdaQueryWrapper<JSB> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(JSB::getJxldh, jxldh);
wrapper.orderByAsc(JSB::getXh);
return jsbMapper.selectList(wrapper);
}
/**
* 构建查询条件
*/
private LambdaQueryWrapper<JSB> buildQueryWrapper(JSB jsb) {
LambdaQueryWrapper<JSB> wrapper = new LambdaQueryWrapper<>();
if (jsb != null) {
if (jsb.getJsbh() != null && !jsb.getJsbh().isEmpty()) {
wrapper.like(JSB::getJsbh, jsb.getJsbh());
}
if (jsb.getJsmc() != null && !jsb.getJsmc().isEmpty()) {
wrapper.like(JSB::getJsmc, jsb.getJsmc());
}
if (jsb.getJxldh() != null && !jsb.getJxldh().isEmpty()) {
wrapper.eq(JSB::getJxldh, jsb.getJxldh());
}
if (jsb.getJxcdlx() != null && !jsb.getJxcdlx().isEmpty()) {
wrapper.eq(JSB::getJxcdlx, jsb.getJxcdlx());
}
if (jsb.getTy() != null) {
wrapper.eq(JSB::getTy, jsb.getTy());
}
}
wrapper.orderByAsc(JSB::getXh);
return wrapper;
}
}
@@ -0,0 +1,206 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.roomroot.jwgl.entity.XYDNDXQJBXXB;
import com.roomroot.jwgl.entity.XYDRWB;
import com.roomroot.jwgl.mapper.ClassSemesterCalendarMapper;
import com.roomroot.jwgl.mapper.ClassSemesterMapper;
import com.roomroot.jwgl.service.ClassSemesterCalendarService;
import com.roomroot.jwgl.utils.UuidUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import jakarta.annotation.Resource;
import java.util.*;
/**
* 学员队任务表服务实现类
*/
@Service
public class ClassSemesterCalendarServiceImpl implements ClassSemesterCalendarService {
@Resource
private ClassSemesterCalendarMapper classSemesterCalendarMapper;
@Resource
private ClassSemesterMapper classSemesterMapper;
@Override
public void update(XYDRWB xydrwb) {
classSemesterCalendarMapper.updateById(xydrwb);
}
@Override
public XYDRWB getByBh(String bh) {
return classSemesterCalendarMapper.selectById(bh);
}
@Transactional(rollbackFor = Exception.class)
@Override
public Map<String, Object> batchCopy(List<String> sourceBhList, List<String> targetBhList) {
List<String> failedSourceBhs = new ArrayList<>();
List<String> failedTargetBhs = new ArrayList<>();
int count = 0;
for (String targetBh : targetBhList) {
// 根据目标学员队期表编号,获取学员队期表的学员队编号
XYDNDXQJBXXB targetSemester = classSemesterMapper.selectById(targetBh);
if (targetSemester == null) {
failedTargetBhs.add(targetBh);
continue;
}
String newXydbh = targetSemester.getXydbh();
for (String sourceBh : sourceBhList) {
XYDRWB source = classSemesterCalendarMapper.selectById(sourceBh);
if (source == null) {
if (!failedSourceBhs.contains(sourceBh)) {
failedSourceBhs.add(sourceBh);
}
continue;
}
// 复制源记录,替换编号、学员队学期编号和学员队编号
XYDRWB newRecord = new XYDRWB();
newRecord.setBh(UuidUtil.getUUID());
newRecord.setNd(source.getNd());
newRecord.setXydbh(newXydbh);
newRecord.setKbh(source.getKbh());
newRecord.setXqdc(source.getXqdc());
newRecord.setJybh(source.getJybh());
newRecord.setKcxh(source.getKcxh());
newRecord.setJsbh(source.getJsbh());
newRecord.setRs(source.getRs());
newRecord.setZks(source.getZks());
newRecord.setXs(source.getXs());
newRecord.setKlx(source.getKlx());
newRecord.setJc(source.getJc());
newRecord.setKsdd(source.getKsdd());
newRecord.setKsbh(source.getKsbh());
newRecord.setXz(source.getXz());
newRecord.setJysdh(source.getJysdh());
newRecord.setJysjhjybh(source.getJysjhjybh());
newRecord.setJysjhbz(source.getJysjhbz());
newRecord.setJhkcxh(source.getJhkcxh());
newRecord.setXhbs(source.getXhbs());
newRecord.setXydxqbh(targetBh);
newRecord.setBz(source.getBz());
newRecord.setXf(source.getXf());
newRecord.setKsks(source.getKsks());
newRecord.setBz2(source.getBz2());
newRecord.setCjfz(source.getCjfz());
newRecord.setBjrxypjf(source.getBjrxypjf());
newRecord.setLlxs(source.getLlxs());
newRecord.setSjxs(source.getSjxs());
newRecord.setKsksbxs(source.getKsksbxs());
newRecord.setPdxh(source.getPdxh());
newRecord.setPdqsz(source.getPdqsz());
newRecord.setPdaz(source.getPdaz());
newRecord.setPdzzksj(source.getPdzzksj());
newRecord.setPdbz(source.getPdbz());
newRecord.setPdtbykxxbz(source.getPdtbykxxbz());
newRecord.setPdzdyxsfbsj(source.getPdzdyxsfbsj());
newRecord.setPdqyzdyxs(source.getPdqyzdyxs());
newRecord.setDelFlag(0);
classSemesterCalendarMapper.insert(newRecord);
count++;
}
}
Map<String, Object> result = new HashMap<>();
result.put("count", count);
result.put("failedSourceBhs", failedSourceBhs);
result.put("failedTargetBhs", failedTargetBhs);
return result;
}
@Transactional(rollbackFor = Exception.class)
@Override
public Map<String, Object> batchCopyBySemester(String sourceXydxqbh, List<String> targetBhList) {
List<String> failedTargetBhs = new ArrayList<>();
int count = 0;
// 查询源学员队期表编号下的所有任务条目
LambdaQueryWrapper<XYDRWB> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(XYDRWB::getXydxqbh, sourceXydxqbh)
.eq(XYDRWB::getDelFlag, 0);
List<XYDRWB> sourceList = classSemesterCalendarMapper.selectList(queryWrapper);
if (CollectionUtils.isEmpty(sourceList) || CollectionUtils.isEmpty(targetBhList)) {
Map<String, Object> result = new HashMap<>();
result.put("count", 0);
result.put("failedTargetBhs", failedTargetBhs);
return result;
}
for (String targetBh : targetBhList) {
// 根据目标学员队期表编号,获取学员队期表的学员队编号
XYDNDXQJBXXB targetSemester = classSemesterMapper.selectById(targetBh);
if (targetSemester == null) {
failedTargetBhs.add(targetBh);
continue;
}
String newXydbh = targetSemester.getXydbh();
for (XYDRWB source : sourceList) {
XYDRWB newRecord = new XYDRWB();
newRecord.setBh(UuidUtil.getUUID());
newRecord.setNd(source.getNd());
newRecord.setXydbh(newXydbh);
newRecord.setKbh(source.getKbh());
newRecord.setXqdc(source.getXqdc());
newRecord.setJybh(source.getJybh());
newRecord.setKcxh(source.getKcxh());
newRecord.setJsbh(source.getJsbh());
newRecord.setRs(source.getRs());
newRecord.setZks(source.getZks());
newRecord.setXs(source.getXs());
newRecord.setKlx(source.getKlx());
newRecord.setJc(source.getJc());
newRecord.setKsdd(source.getKsdd());
newRecord.setKsbh(source.getKsbh());
newRecord.setXz(source.getXz());
newRecord.setJysdh(source.getJysdh());
newRecord.setJysjhjybh(source.getJysjhjybh());
newRecord.setJysjhbz(source.getJysjhbz());
newRecord.setJhkcxh(source.getJhkcxh());
newRecord.setXhbs(source.getXhbs());
newRecord.setXydxqbh(targetBh);
newRecord.setBz(source.getBz());
newRecord.setXf(source.getXf());
newRecord.setKsks(source.getKsks());
newRecord.setBz2(source.getBz2());
newRecord.setCjfz(source.getCjfz());
newRecord.setBjrxypjf(source.getBjrxypjf());
newRecord.setLlxs(source.getLlxs());
newRecord.setSjxs(source.getSjxs());
newRecord.setKsksbxs(source.getKsksbxs());
newRecord.setPdxh(source.getPdxh());
newRecord.setPdqsz(source.getPdqsz());
newRecord.setPdaz(source.getPdaz());
newRecord.setPdzzksj(source.getPdzzksj());
newRecord.setPdbz(source.getPdbz());
newRecord.setPdtbykxxbz(source.getPdtbykxxbz());
newRecord.setPdzdyxsfbsj(source.getPdzdyxsfbsj());
newRecord.setPdqyzdyxs(source.getPdqyzdyxs());
newRecord.setDelFlag(0);
classSemesterCalendarMapper.insert(newRecord);
count++;
}
}
Map<String, Object> result = new HashMap<>();
result.put("count", count);
result.put("failedTargetBhs", failedTargetBhs);
return result;
}
@Override
public List<XYDRWB> listAllValid(XYDRWB xydrwb) {
xydrwb.setDelFlag(0);
return classSemesterCalendarMapper.selectAllValid(xydrwb);
}
}
@@ -0,0 +1,253 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.XYDNDXQJBXXB;
import com.roomroot.jwgl.entity.XYDB;
import com.roomroot.jwgl.mapper.ClassSemesterCalendarMapper;
import com.roomroot.jwgl.mapper.ClassSemesterMapper;
import com.roomroot.jwgl.mapper.SemesterCalendarMapper;
import com.roomroot.jwgl.service.ClassSemesterService;
import com.roomroot.jwgl.service.ElectiveService;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.utils.UuidUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import jakarta.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
/**
* 班次学期服务实现类
*/
@Service
public class ClassSemesterServiceImpl implements ClassSemesterService {
@Resource
private ClassSemesterMapper classSemesterMapper;
@Resource
SemesterCalendarMapper semesterCalendarMapper;
@Resource
ClassSemesterCalendarMapper classSemesterCalendarMapper;
@Resource
private ElectiveService electiveService;
@Transactional(rollbackFor = Exception.class)
@Override
public void add(XYDNDXQJBXXB xydndxqjbxxb) {
String uuid = UuidUtil.getUUID();
xydndxqjbxxb.setBh(uuid);
xydndxqjbxxb.setDelFlag(0);
int insert = classSemesterMapper.insert(xydndxqjbxxb);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void delete(String bh) {
XYDNDXQJBXXB xydndxqjbxxb = getByBh(bh);
xydndxqjbxxb.setDelFlag(1);
classSemesterMapper.updateById(xydndxqjbxxb);
classSemesterCalendarMapper.deleteByBcxqBh(bh);
}
@Override
public void batchDelete(List<String> bhList) {
for (String bh : bhList) {
XYDNDXQJBXXB xydndxqjbxxb = getByBh(bh);
if (xydndxqjbxxb != null) {
xydndxqjbxxb.setDelFlag(1);
classSemesterMapper.updateById(xydndxqjbxxb);
}
}
}
@Override
public void update(XYDNDXQJBXXB xydndxqjbxxb) {
classSemesterMapper.updateById(xydndxqjbxxb);
}
@Override
public XYDNDXQJBXXB getByBh(String bh) {
return classSemesterMapper.selectById(bh);
}
@Override
public PageResult<XYDNDXQJBXXB> pageList(PageQuery query, XYDNDXQJBXXB xydndxqjbxxb) {
xydndxqjbxxb.setDelFlag(0);
Page<XYDNDXQJBXXB> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<XYDNDXQJBXXB> result = classSemesterMapper.selectPageList(page, xydndxqjbxxb);
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
@Override
public List<XYDNDXQJBXXB> listAllValid() {
return classSemesterMapper.selectAllValid();
}
@Transactional(rollbackFor = Exception.class)
@Override
public int batchAddFromElective(LocalDateTime startTime, LocalDateTime endTime,
String nd, String xqdc, String xqkssj, String xqjssj) {
// 调用filterByDateRange获取符合条件的学员队列表
List<XYDB> xydbList = electiveService.filterByDateRange(startTime, endTime);
if (CollectionUtils.isEmpty(xydbList)) {
return 0;
}
int count = 0;
for (XYDB xydb : xydbList) {
// 校验是否已存在:根据班次编号、年度、学期第次查询
XYDNDXQJBXXB existing = classSemesterMapper.selectByBcAndNdAndXqdc(xydb.getXydbh(), nd, xqdc);
if (existing != null) {
// 已存在,跳过
continue;
}
// 创建学员队年度学期基本信息
XYDNDXQJBXXB xydndxqjbxxb = new XYDNDXQJBXXB();
String uuid = UuidUtil.getUUID();
xydndxqjbxxb.setBh(uuid);
xydndxqjbxxb.setXydbh(xydb.getXydbh());
xydndxqjbxxb.setNd(Integer.valueOf(nd));
if("春季学期".equals(xqdc)){
xqdc = "1";
} else if("夏季学期".equals(xqdc)){
xqdc = "2";
} else if("秋季学期".equals(xqdc)){
xqdc = "3";
}
xydndxqjbxxb.setXhbs(0);
xydndxqjbxxb.setXqdc(Integer.valueOf(xqdc));
xydndxqjbxxb.setKxrq(LocalDate.parse(xqkssj));
xydndxqjbxxb.setJsrq(LocalDate.parse(xqjssj));
xydndxqjbxxb.setYsbbh(0);
xydndxqjbxxb.setJclb("双节次");
xydndxqjbxxb.setXtms("");
xydndxqjbxxb.setKfjypk(0);
xydndxqjbxxb.setDelFlag(0);
int insert = classSemesterMapper.insert(xydndxqjbxxb);
}
return count;
}
@Override
public int batchUpdateKfjypk(List<String> bhList, Integer kfjypk) {
if (CollectionUtils.isEmpty(bhList)) {
return 0;
}
int count = 0;
for (String bh : bhList) {
XYDNDXQJBXXB xydndxqjbxxb = classSemesterMapper.selectById(bh);
if (xydndxqjbxxb != null) {
xydndxqjbxxb.setKfjypk(kfjypk);
classSemesterMapper.updateById(xydndxqjbxxb);
count++;
}
}
return count;
}
@Override
public int batchUpdateXqdc(List<String> bhList, Integer xqdc) {
if (CollectionUtils.isEmpty(bhList)) {
return 0;
}
int count = 0;
for (String bh : bhList) {
XYDNDXQJBXXB xydndxqjbxxb = classSemesterMapper.selectById(bh);
if (xydndxqjbxxb != null) {
xydndxqjbxxb.setXqdc(xqdc);
classSemesterMapper.updateById(xydndxqjbxxb);
count++;
}
}
return count;
}
@Override
public int batchUpdateJxrwbh(List<String> bhList, String jxrwbh) {
if (CollectionUtils.isEmpty(bhList)) {
return 0;
}
int count = 0;
for (String bh : bhList) {
XYDNDXQJBXXB xydndxqjbxxb = classSemesterMapper.selectById(bh);
if (xydndxqjbxxb != null) {
xydndxqjbxxb.setJxrwbh(jxrwbh);
classSemesterMapper.updateById(xydndxqjbxxb);
count++;
}
}
return count;
}
@Transactional(rollbackFor = Exception.class)
@Override
public int batchUpdateDateRange(List<String> bhList, String xqkssj, String xqjssj) {
if (CollectionUtils.isEmpty(bhList)) {
return 0;
}
int count = 0;
for (String bh : bhList) {
// 查询学员队年度学期基本信息
XYDNDXQJBXXB xydndxqjbxxb = classSemesterMapper.selectById(bh);
if (xydndxqjbxxb == null) {
continue;
}
// 更新日期范围
xydndxqjbxxb.setKxrq(LocalDate.parse(xqkssj));
xydndxqjbxxb.setJsrq(LocalDate.parse(xqjssj));
classSemesterMapper.updateById(xydndxqjbxxb);
// // 删除日期范围之外的日历记录
// classSemesterCalendarMapper.deleteOutsideDateRange(bh, xqkssj, xqjssj);
//
// // 查询现有任务记录
// List<XYDRWB> existingCalendar = classSemesterCalendarMapper.selectByBcxqbhAndDateRange(bh, xqkssj, xqjssj);
//
// // 查询学期校历,用于补充缺少的记录
// String kxrqStr = xydndxqjbxxb.getKxrq() != null ? xydndxqjbxxb.getKxrq().toString() : xqkssj;
// String jsrqStr = xydndxqjbxxb.getJsrq() != null ? xydndxqjbxxb.getJsrq().toString() : xqjssj;
// List<XQXLB> xqxlbs = semesterCalendarMapper.selectByNdAndJqsjRange(xydndxqjbxxb.getNd(), kxrqStr, jsrqStr);
//
// if (!CollectionUtils.isEmpty(xqxlbs)) {
// // 提取已有编号
// java.util.Set<String> existingBhs = new java.util.HashSet<>();
// for (XYDRWB cal : existingCalendar) {
// existingBhs.add(cal.getBh());
// }
//
// // 补充缺少的记录
// for (XQXLB xqxlb : xqxlbs) {
// XYDRWB xydrwb = new XYDRWB();
// String newBh = UuidUtil.getUUID();
// if (!existingBhs.contains(newBh)) {
// xydrwb.setBh(newBh);
// xydrwb.setNd(xydndxqjbxxb.getNd());
// xydrwb.setXydbh(xydndxqjbxxb.getXydbh());
// xydrwb.setXqdc(xydndxqjbxxb.getXqdc());
// xydrwb.setXydxqbh(bh);
// xydrwb.setDelFlag(0);
// classSemesterCalendarMapper.insert(xydrwb);
// }
// }
// }
//
// count++;
}
return count;
}
}
@@ -0,0 +1,111 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.KCBZ;
import com.roomroot.jwgl.entity.KTBZ;
import com.roomroot.jwgl.mapper.KCBZMapper;
import com.roomroot.jwgl.mapper.KTBZMapper;
import com.roomroot.jwgl.service.CourseTeachingService;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jakarta.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* 课程教学管理服务实现类
* <p>
* 实现课程标准的增删改查、多版本管理及自动生成。
* 删除逻辑:填写撤回原因(非空=已撤回),不物理删除。
* 查询仅返回未撤回(撤回原因为空)的数据。
* </p>
*/
@Service
public class CourseTeachingServiceImpl implements CourseTeachingService {
@Resource
private KCBZMapper kcbzMapper;
@Resource
private KTBZMapper ktbzMapper;
/** 填充课堂列表到课程标准 */
private void fillKtList(KCBZ kcbz) {
if (kcbz == null) return;
List<KTBZ> ktList = ktbzMapper.selectByKcbh(kcbz.getBh());
kcbz.setKtList(ktList != null ? ktList : new ArrayList<>());
}
@Override
@Transactional
public void add(KCBZ kcbz) {
kcbz.setChyy(null);
kcbzMapper.insert(kcbz);
List<KTBZ> ktList = kcbz.getKtList();
if (ktList != null && !ktList.isEmpty()) {
for (KTBZ kt : ktList) {
kt.setKcbh(kcbz.getBh());
ktbzMapper.insert(kt);
}
}
}
@Override
@Transactional
public void delete(String bh, String chyy) {
KCBZ kcbz = new KCBZ();
kcbz.setBh(bh);
kcbz.setChyy(chyy);
kcbzMapper.updateById(kcbz);
}
@Override
@Transactional
public void update(KCBZ kcbz) {
kcbzMapper.updateById(kcbz);
List<KTBZ> ktList = kcbz.getKtList();
if (ktList != null && !ktList.isEmpty()) {
for (KTBZ kt : ktList) {
ktbzMapper.updateById(kt);
}
}
}
@Override
public KCBZ getById(String bh) {
KCBZ kcbz = kcbzMapper.selectById(bh);
fillKtList(kcbz);
return kcbz;
}
@Override
public PageResult<KCBZ> pageList(PageQuery query, KCBZ kcbz) {
Page<KCBZ> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<KCBZ> result = kcbzMapper.selectPageList(page, kcbz);
// 填充每项的课堂列表
for (KCBZ item : result.getRecords()) {
fillKtList(item);
}
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
@Override
public List<KCBZ> getVersionsByCourse(String kbh) {
return kcbzMapper.selectVersionsByCourse(kbh);
}
@Override
@Transactional
public String generateFromCourse(String kbh) {
KCBZ kcbz = new KCBZ();
kcbz.setBh(kbh + "-STD");
kcbz.setKbh(kbh);
kcbz.setMc("课程标准-" + kbh);
kcbzMapper.insert(kcbz);
return kcbz.getBh();
}
}
@@ -0,0 +1,942 @@
package com.roomroot.jwgl.service.impl;
import com.roomroot.jwgl.dto.department.DepartmentCreateDTO;
import com.roomroot.jwgl.dto.department.DepartmentIdDTO;
import com.roomroot.jwgl.dto.department.DepartmentQueryDTO;
import com.roomroot.jwgl.dto.department.DepartmentUpdateDTO;
import com.roomroot.jwgl.entity.SSJG;
import com.roomroot.jwgl.mapper.SSJGMapper;
import com.roomroot.jwgl.service.DepartmentManagementService;
import com.roomroot.jwgl.unit.BusinessException;
import com.roomroot.jwgl.utils.UuidUtil;
import com.roomroot.jwgl.vo.department.DepartmentTreeVO;
import com.roomroot.jwgl.vo.department.DepartmentVO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.*;
import static com.roomroot.jwgl.utils.BasicManagementConstants.*;
/**
* 部别管理服务实现。
*/
@Service
public class DepartmentManagementServiceImpl implements DepartmentManagementService {
/** 部别管理数据访问对象。 */
@Resource
private SSJGMapper ssjgMapper;
/**
* 新增部别。
*
* <p>处理顺序:校验参数、校验上级关系、校验同级重名、组装实体、
* 写入数据库、重新查询并返回 VO。</p>
*
* @param dto 新增参数
* @return 新增后的部别信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public DepartmentVO add(DepartmentCreateDTO dto) {
// 第一步:防止业务层被直接调用时传入 null。
if (dto == null) {
throw new BusinessException(BAD_REQUEST, "新增参数不能为空");
}
// 第二步:清理并校验数据库必填字段。
String name = requireText(dto.getName(), "部别名称不能为空");
String category = requireText(dto.getCategory(), "机关类别不能为空");
// 第三步:将空字符串形式的上级编号统一转换为 null,表示根节点。
String parentId = normalizeText(dto.getParentId());
// 第四步:加载完整组织数据,用于检查上级节点和层级链路。
List<SSJG> departments = selectAllDepartments();
// 第五步:确认上级存在、已启用,并且组织链路中不存在循环。
validateParentChain(parentId, null, departments);
// 第六步:同一上级下不允许出现同名部别。
ensureSiblingNameUnique(parentId, name, null);
/*
* 第七步:将新增 DTO 转换为数据库实体。
* Controller 和 Service 对外只使用 DTO/VO,SSJG 仅在持久层内部流转。
*/
SSJG department = new SSJG();
department.setBH(UuidUtil.getOriginalUUID());
department.setJGMC(name);
department.setJGLB(category);
department.setFLH(defaultClassificationCode(dto.getClassificationCode()));
department.setFJH(defaultLevelNumber(dto.getLevelNumber()));
department.setSJJGBH(parentId);
department.setTY(false);
department.setTYSJ(null);
department.setJC(normalizeText(dto.getAbbreviation()));
// 第八步:执行新增,并校验数据库实际影响行数。
if (ssjgMapper.insertDepartment(department) != 1) {
throw new BusinessException("新增部别失败");
}
// 第九步:重新查询数据库,返回包含上级名称等扩展字段的 VO。
return loadDepartmentView(department.getBH());
}
/**
* 修改部别。
*
* <p>处理顺序:校验编号和必填字段、校验新的上级关系、
* 校验同级重名、组装更新实体、更新数据库、返回最新 VO。</p>
*
* @param dto 修改参数
* @return 修改后的部别信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public DepartmentVO update(DepartmentUpdateDTO dto) {
// 第一步:防止业务层被直接调用时传入 null。
if (dto == null) {
throw new BusinessException(BAD_REQUEST, "修改参数不能为空");
}
// 第二步:校验部别编号,并确认待修改数据真实存在。
String id = requireId(dto.getId());
requireDepartment(id);
// 第三步:清理并校验数据库必填字段。
String name = requireText(dto.getName(), "部别名称不能为空");
String category = requireText(dto.getCategory(), "机关类别不能为空");
// 第四步:统一空上级编号的表达方式。
String parentId = normalizeText(dto.getParentId());
// 第五步:读取完整组织结构,为移动节点校验提供数据。
List<SSJG> departments = selectAllDepartments();
// 第六步:禁止选择自身或自身下级作为新的上级部别。
validateParentChain(parentId, id, departments);
// 第七步:修改时排除自身,检查同一上级下是否存在其他同名部别。
ensureSiblingNameUnique(parentId, name, id);
/*
* 第八步:将修改 DTO 转换为更新实体。
* 此处不设置停用字段,避免修改基本信息时意外改变启用状态。
*/
SSJG department = new SSJG();
department.setBH(id);
department.setJGMC(name);
department.setJGLB(category);
department.setFLH(defaultClassificationCode(dto.getClassificationCode()));
department.setFJH(defaultLevelNumber(dto.getLevelNumber()));
department.setSJJGBH(parentId);
department.setJC(normalizeText(dto.getAbbreviation()));
// 第九步:更新基本信息,并校验数据库实际影响行数。
if (ssjgMapper.updateDepartment(department) != 1) {
throw new BusinessException("修改部别失败");
}
// 第十步:重新查询并返回修改后的完整 VO。
return loadDepartmentView(id);
}
/**
* 查询部别详情。
*
* @param dto 部别编号参数
* @return 部别详情
*/
@Override
public DepartmentVO get(DepartmentIdDTO dto) {
// 第一步:兼容 dto 为 null 的直接调用场景,并校验编号。
String id = requireId(dto == null ? null : dto.getId());
// 第二步:查询并组装包含上级名称和子节点标识的 VO。
return loadDepartmentView(id);
}
/**
* 查询部别平铺列表。
*
* @param dto 查询条件
* @return 排序后的部别列表
*/
@Override
public List<DepartmentVO> list(DepartmentQueryDTO dto) {
// 第一步:一次性读取全部部别,避免逐条查询形成 N+1 问题。
List<SSJG> departments = selectAllDepartments();
// 第二步:建立编号索引,用于快速查找每个部别的上级名称。
Map<String, SSJG> departmentMap = indexDepartments(departments);
// 第三步:收集被引用为上级的编号,用于计算 hasChildren。
Set<String> parentIds = collectParentIds(departments);
// 第四步:创建最终返回列表。
List<DepartmentVO> result = new ArrayList<>();
// 第五步:逐条应用查询条件,并将匹配实体转换为 VO。
for (SSJG department : departments) {
if (matches(department, dto)) {
result.add(toDepartmentVO(department, departmentMap, parentIds));
}
}
// 第六步:按照分级号、分类号、名称依次排序。
Collections.sort(result, new Comparator<DepartmentVO>() {
@Override
public int compare(DepartmentVO left, DepartmentVO right) {
return compareDepartments(left, right);
}
});
// 第七步:返回平铺列表。
return result;
}
/**
* 查询部别树。
*
* <p>查询条件命中子节点时,会同时保留该节点的所有祖先节点,
* 从而保证前端仍能展示完整层级路径。</p>
*
* @param dto 查询条件
* @return 排序后的部别树
*/
@Override
public List<DepartmentTreeVO> tree(DepartmentQueryDTO dto) {
// 第一步:读取全部部别,并建立编号到实体的索引。
List<SSJG> departments = selectAllDepartments();
Map<String, SSJG> departmentMap = indexDepartments(departments);
// 第二步:在构建树前检测数据库中的异常循环关系,避免无限递归。
ensureTreeHasNoCycle(departments, departmentMap);
// 第三步:计算所有需要展示的节点编号,包括匹配节点及其祖先。
Set<String> includedIds = collectIncludedTreeIds(departments, departmentMap, dto);
// 第四步:收集全部上级编号,用于设置节点的 hasChildren 字段。
Set<String> parentIds = collectParentIds(departments);
// 第五步:建立树节点索引,后续可通过编号快速挂接父子关系。
Map<String, DepartmentTreeVO> nodeMap = new HashMap<>();
// 第六步:只为需要展示的实体创建树节点。
for (SSJG department : departments) {
if (includedIds.contains(department.getBH())) {
DepartmentVO departmentVO = toDepartmentVO(department, departmentMap, parentIds);
nodeMap.put(department.getBH(), toTreeVO(departmentVO));
}
}
// 第七步:创建根节点集合。
List<DepartmentTreeVO> roots = new ArrayList<>();
/*
* 第八步:逐个挂接节点。
* 展示集合中不存在上级节点时,将当前节点视为根节点;
* 否则加入对应上级节点的 children 集合。
*/
for (SSJG department : departments) {
DepartmentTreeVO node = nodeMap.get(department.getBH());
if (node == null) {
continue;
}
String parentId = normalizeText(department.getSJJGBH());
DepartmentTreeVO parent = nodeMap.get(parentId);
if (parent == null) {
roots.add(node);
} else {
parent.getChildren().add(node);
}
}
// 第九步:递归排序根节点及所有层级的子节点。
sortTree(roots);
// 第十步:返回完整树形结果。
return roots;
}
/**
* 停用部别。
*
* <p>停用前必须确认不存在启用中的直接下级部别,
* 并且不存在教研室、学员队、机关参谋或教学管理机构引用。</p>
*
* @param dto 部别编号参数
* @return 停用后的部别信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public DepartmentVO disable(DepartmentIdDTO dto) {
// 第一步:校验编号并查询待停用部别。
String id = requireId(dto == null ? null : dto.getId());
SSJG department = requireDepartment(id);
// 第二步:已停用时直接返回,保证接口幂等。
if (Boolean.TRUE.equals(department.getTY())) {
return loadDepartmentView(id);
}
// 第三步:存在启用中的直接下级部别时,不允许停用当前部别。
if (ssjgMapper.countActiveChildren(id) > 0) {
throw new BusinessException(CONFLICT, "该部别存在启用中的下级部别,不能停用");
}
// 第四步:存在其他业务数据引用时,不允许停用当前部别。
if (ssjgMapper.countDepartmentReferences(id) > 0) {
throw new BusinessException(CONFLICT,
"该部别已被人员、教研室、学员队或教学管理机构引用,不能停用");
}
// 第五步:将停用标志设为 true,并记录当前停用时间。
if (ssjgMapper.updateDepartmentStatus(id, true, LocalDateTime.now()) != 1) {
throw new BusinessException("停用部别失败");
}
// 第六步:重新查询并返回停用后的最新状态。
return loadDepartmentView(id);
}
/**
* 启用部别。
*
* <p>启用前会检查上级及所有祖先部别均存在且处于启用状态。</p>
*
* @param dto 部别编号参数
* @return 启用后的部别信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public DepartmentVO enable(DepartmentIdDTO dto) {
// 第一步:校验编号并查询待启用部别。
String id = requireId(dto == null ? null : dto.getId());
SSJG department = requireDepartment(id);
// 第二步:已启用时直接返回,保证接口幂等。
if (!Boolean.TRUE.equals(department.getTY())) {
return loadDepartmentView(id);
}
// 第三步:加载组织结构,检查上级链路的完整性和启用状态。
List<SSJG> departments = selectAllDepartments();
validateParentChain(normalizeText(department.getSJJGBH()), id, departments);
// 第四步:清除停用标志和停用时间。
if (ssjgMapper.updateDepartmentStatus(id, false, null) != 1) {
throw new BusinessException("启用部别失败");
}
// 第五步:重新查询并返回启用后的最新状态。
return loadDepartmentView(id);
}
/**
* 删除部别(逻辑删除)。
*
* <p>删除前必须确认不存在任何下级部别,
* 并且不存在教研室、学员队、机关参谋或教学管理机构引用。
* 逻辑删除后部别不再出现在列表和树形结构中。</p>
*
* @param dto 部别编号参数
* @return 被删除的部别信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public DepartmentVO delete(DepartmentIdDTO dto) {
// 第一步:校验编号并查询待删除部别。
String id = requireId(dto == null ? null : dto.getId());
SSJG department = requireDepartment(id);
// 第二步:删除前先构造返回 VO(逻辑删除后查不到数据)。
List<SSJG> departments = selectAllDepartments();
DepartmentVO vo = toDepartmentVO(
department, indexDepartments(departments), collectParentIds(departments));
// 第三步:存在任何下级部别时,不允许删除当前部别。
// 与停用不同,删除要求所有子部别先被删除,而非仅停用。
for (SSJG child : departments) {
if (id.equals(child.getSJJGBH())) {
throw new BusinessException(CONFLICT, "该部别存在下级部别,请先删除下级部别");
}
}
// 第四步:存在其他业务数据引用时,不允许删除当前部别。
if (ssjgMapper.countDepartmentReferences(id) > 0) {
throw new BusinessException(CONFLICT,
"该部别已被人员、教研室、学员队或教学管理机构引用,不能删除");
}
// 第五步:执行逻辑删除,将 DEL_FLAG 置为 1。
if (ssjgMapper.deleteDepartmentLogically(id) != 1) {
throw new BusinessException("删除部别失败");
}
// 第六步:返回删除前的部别信息。
return vo;
}
/**
* 查询部别并转换为详情 VO。
*
* @param id 部别编号
* @return 部别详情 VO
*/
private DepartmentVO loadDepartmentView(String id) {
// 第一步:查询目标部别,不存在时统一抛出业务异常。
SSJG department = requireDepartment(id);
// 第二步:读取组织数据,为上级名称和子节点标识提供上下文。
List<SSJG> departments = selectAllDepartments();
// 第三步:将实体转换为对外返回的 VO。
return toDepartmentVO(department, indexDepartments(departments), collectParentIds(departments));
}
/**
* 查询必然存在的部别。
*
* @param id 部别编号
* @return 部别实体
*/
private SSJG requireDepartment(String id) {
// 根据唯一编号查询数据库。
SSJG department = ssjgMapper.selectDepartmentById(id);
// 未查询到数据时返回明确的 404 业务错误。
if (department == null) {
throw new BusinessException(NOT_FOUND, "部别不存在");
}
// 返回已确认存在的部别实体。
return department;
}
/**
* 查询全部部别,并统一处理 Mapper 返回 null 的情况。
*
* @return 非 null 的部别集合
*/
private List<SSJG> selectAllDepartments() {
// 调用 Mapper 获取全部部别。
List<SSJG> departments = ssjgMapper.selectAllDepartments();
// Mapper 返回 null 时改为空集合,减少后续判空分支。
return departments == null ? Collections.<SSJG>emptyList() : departments;
}
/**
* 校验同一上级下的部别名称唯一性。
*
* @param parentId 上级部别编号
* @param name 部别名称
* @param excludeId 修改时需要排除的当前部别编号
*/
private void ensureSiblingNameUnique(String parentId, String name, String excludeId) {
// 数据库计数大于 0 表示存在冲突记录。
if (ssjgMapper.countSiblingName(parentId, name, excludeId) > 0) {
throw new BusinessException(CONFLICT, "同一上级部别下已存在同名部别");
}
}
/**
* 校验上级部别链路。
*
* <p>该方法同时检查:上级存在、上级启用、不能选择自身、
* 不能移动到自己的下级、已有组织数据不能形成循环。</p>
*
* @param parentId 待选择的上级部别编号
* @param currentId 当前部别编号;新增时为 null
* @param departments 全部部别
*/
private void validateParentChain(String parentId, String currentId, List<SSJG> departments) {
// 根节点没有上级,不需要继续校验。
if (parentId == null) {
return;
}
// 第一层快速判断:当前节点不能直接选择自己作为上级。
if (parentId.equals(currentId)) {
throw new BusinessException(CONFLICT, "上级部别不能选择当前部别");
}
// 建立编号索引,后续沿祖先链逐级查询时保持 O(1) 查找。
Map<String, SSJG> departmentMap = indexDepartments(departments);
// 记录已经访问的编号,用于识别数据库中的循环关系。
Set<String> visited = new HashSet<>();
// 从待选择的上级节点开始向根节点逐级检查。
String cursor = parentId;
while (cursor != null) {
// 遇到当前节点说明新上级位于当前节点的下级链路中。
if (cursor.equals(currentId)) {
throw new BusinessException(CONFLICT, "不能将部别移动到自己的下级部别中");
}
// 同一编号在当前链路中出现两次,说明已有组织数据存在循环。
if (!visited.add(cursor)) {
throw new BusinessException("组织结构数据存在循环,请联系管理员处理");
}
// 根据当前编号取得上级实体。
SSJG parent = departmentMap.get(cursor);
// 引用了不存在的上级编号时,拒绝继续保存。
if (parent == null) {
throw new BusinessException(NOT_FOUND, "上级部别不存在");
}
// 停用部别不能作为新增、移动或启用操作的有效上级。
if (Boolean.TRUE.equals(parent.getTY())) {
throw new BusinessException(CONFLICT, "上级部别已停用,不能选择");
}
// 继续检查当前上级的上一级,直至到达根节点。
cursor = normalizeText(parent.getSJJGBH());
}
}
/**
* 收集树形查询需要保留的部别编号。
*
* <p>命中查询条件的部别需要保留,同时保留它的全部祖先节点,
* 这样筛选后的结果仍然是一棵层级完整、可以正常展示的树。</p>
*
* @param departments 全部部别
* @param departmentMap 部别编号索引
* @param dto 查询条件
* @return 需要出现在查询结果树中的部别编号
*/
private Set<String> collectIncludedTreeIds(List<SSJG> departments,
Map<String, SSJG> departmentMap,
DepartmentQueryDTO dto) {
// 第一步:创建结果集合,并利用 Set 自动去除重复编号。
Set<String> includedIds = new HashSet<>();
// 第二步:逐个检查部别是否满足当前查询条件。
for (SSJG department : departments) {
// 未命中查询条件的部别不作为本次向上追溯的起点。
if (!matches(department, dto)) {
continue;
}
/*
* 第三步:从命中的部别开始沿上级链逐级向上追溯。
* 当前节点和全部祖先节点都加入结果,确保筛选后的树不丢失层级路径。
*/
String cursor = department.getBH();
while (cursor != null && includedIds.add(cursor)) {
// 根据当前编号取得实体,再移动到它的上级部别。
SSJG current = departmentMap.get(cursor);
cursor = current == null ? null : normalizeText(current.getSJJGBH());
}
}
// 第四步:返回命中节点及其祖先节点的编号集合。
return includedIds;
}
/**
* 检查完整部别树是否存在循环关系。
*
* <p>从每个部别分别沿上级链向根节点追溯;同一条路径中再次遇到相同编号,
* 即表示组织结构无法构成合法树,需要终止查询。</p>
*
* @param departments 全部部别
* @param departmentMap 部别编号索引
*/
private void ensureTreeHasNoCycle(List<SSJG> departments, Map<String, SSJG> departmentMap) {
// 第一步:以每个部别为起点,分别检查它所在的上级链路。
for (SSJG department : departments) {
// 第二步:为当前链路创建独立的访问记录。
Set<String> path = new HashSet<>();
SSJG cursor = department;
// 第三步:沿上级链持续追溯,直至到达根节点或断开的上级节点。
while (cursor != null) {
// 同一编号在当前链路重复出现,说明组织结构中存在循环。
if (!path.add(cursor.getBH())) {
throw new BusinessException("组织结构数据存在循环,请联系管理员处理");
}
// 读取当前部别的上级编号,并将游标移动到上级部别。
String parentId = normalizeText(cursor.getSJJGBH());
cursor = parentId == null ? null : departmentMap.get(parentId);
}
}
}
/**
* 按部别编号建立实体索引。
*
* @param departments 部别集合
* @return 部别编号与实体的映射
*/
private Map<String, SSJG> indexDepartments(List<SSJG> departments) {
// 第一步:创建用于快速查询部别的编号索引。
Map<String, SSJG> result = new HashMap<>();
// 第二步:将每个部别实体按唯一编号写入索引。
for (SSJG department : departments) {
result.put(department.getBH(), department);
}
// 第三步:返回构建完成的索引。
return result;
}
/**
* 收集所有被其他部别引用的上级编号。
*
* @param departments 全部部别
* @return 至少拥有一个下级部别的编号集合
*/
private Set<String> collectParentIds(List<SSJG> departments) {
// 第一步:创建上级编号集合,并利用 Set 自动去重。
Set<String> parentIds = new HashSet<>();
// 第二步:逐个读取部别的上级编号。
for (SSJG department : departments) {
String parentId = normalizeText(department.getSJJGBH());
// 根节点没有上级编号,不加入集合。
if (parentId != null) {
parentIds.add(parentId);
}
}
// 第三步:返回所有实际作为上级的部别编号。
return parentIds;
}
/**
* 将部别实体转换为对外返回的详情 VO。
*
* @param department 部别实体
* @param departmentMap 部别编号索引
* @param parentIds 实际拥有下级的部别编号集合
* @return 部别详情 VO
*/
private DepartmentVO toDepartmentVO(SSJG department,
Map<String, SSJG> departmentMap,
Set<String> parentIds) {
// 第一步:创建独立的返回对象,避免直接暴露数据库实体。
DepartmentVO vo = new DepartmentVO();
// 第二步:复制部别自身的基础信息。
vo.setId(department.getBH());
vo.setName(department.getJGMC());
vo.setCategory(department.getJGLB());
vo.setClassificationCode(department.getFLH());
vo.setLevelNumber(department.getFJH());
// 第三步:设置上级编号,并通过索引补充上级名称。
vo.setParentId(normalizeText(department.getSJJGBH()));
SSJG parent = departmentMap.get(vo.getParentId());
vo.setParentName(parent == null ? null : parent.getJGMC());
// 第四步:将实体的停用标志转换为更直观的启用状态,并保留停用时间。
vo.setEnabled(!Boolean.TRUE.equals(department.getTY()));
vo.setDisabledAt(department.getTYSJ());
// 第五步:补充简称和是否拥有下级节点等展示信息。
vo.setAbbreviation(department.getJC());
vo.setHasChildren(parentIds.contains(department.getBH()));
// 第六步:返回完成组装的详情 VO。
return vo;
}
/**
* 将部别详情 VO 转换为可挂载子节点的树形 VO。
*
* @param source 部别详情 VO
* @return 树形部别 VO
*/
private DepartmentTreeVO toTreeVO(DepartmentVO source) {
// 第一步:创建树形返回对象,子节点集合由该类型自行初始化。
DepartmentTreeVO target = new DepartmentTreeVO();
// 第二步:复制基础信息和层级信息。
target.setId(source.getId());
target.setName(source.getName());
target.setCategory(source.getCategory());
target.setClassificationCode(source.getClassificationCode());
target.setLevelNumber(source.getLevelNumber());
target.setParentId(source.getParentId());
target.setParentName(source.getParentName());
// 第三步:复制状态信息和展示辅助字段。
target.setEnabled(source.getEnabled());
target.setDisabledAt(source.getDisabledAt());
target.setAbbreviation(source.getAbbreviation());
target.setHasChildren(source.getHasChildren());
// 第四步:返回尚未挂载子节点的树形 VO。
return target;
}
/**
* 判断部别是否满足查询条件。
*
* @param department 待判断的部别
* @param dto 查询条件
* @return 满足全部条件时返回 true
*/
private boolean matches(SSJG department, DepartmentQueryDTO dto) {
// 未提供查询条件时,默认匹配全部部别。
if (dto == null) {
return true;
}
/*
* 第一步:处理关键字模糊查询。
* 关键字可匹配部别编号、名称、简称和分类号,且忽略英文字母大小写。
*/
String keyword = normalizeText(dto.getKeyword());
if (keyword != null
&& !containsIgnoreCase(department.getBH(), keyword)
&& !containsIgnoreCase(department.getJGMC(), keyword)
&& !containsIgnoreCase(department.getJC(), keyword)
&& !containsIgnoreCase(department.getFLH(), keyword)) {
return false;
}
// 第二步:存在部别类别条件时,按完整类别值忽略大小写匹配。
String category = normalizeText(dto.getCategory());
if (category != null && !category.equalsIgnoreCase(normalizeText(department.getJGLB()))) {
return false;
}
// 第三步:存在启用状态条件时,将实体停用标志转换后进行比较。
if (dto.getEnabled() != null) {
boolean enabled = !Boolean.TRUE.equals(department.getTY());
return dto.getEnabled().booleanValue() == enabled;
}
// 所有已提供的查询条件均通过时,当前部别匹配成功。
return true;
}
/**
* 判断源文本是否包含指定关键字,并忽略英文字母大小写。
*
* @param source 源文本
* @param keyword 关键字
* @return 包含关键字时返回 true
*/
private boolean containsIgnoreCase(String source, String keyword) {
// 源文本为空时不能匹配;否则统一按 ROOT 区域规则转为小写后比较。
return source != null
&& source.toLowerCase(Locale.ROOT).contains(keyword.toLowerCase(Locale.ROOT));
}
/**
* 递归排序树中每一层的部别节点。
*
* @param nodes 当前层级的树节点
*/
private void sortTree(List<DepartmentTreeVO> nodes) {
// 第一步:按照统一的部别排序规则排列当前层级。
Collections.sort(nodes, new Comparator<DepartmentTreeVO>() {
@Override
public int compare(DepartmentTreeVO left, DepartmentTreeVO right) {
return compareDepartments(left, right);
}
});
// 第二步:递归处理每个节点的子节点,保证整棵树的各层顺序一致。
for (DepartmentTreeVO node : nodes) {
sortTree(node.getChildren());
}
}
/**
* 按分级号、分类号、部别名称依次比较两个部别。
*
* @param left 左侧部别
* @param right 右侧部别
* @return 符合 Comparator 约定的比较结果
*/
private int compareDepartments(DepartmentVO left, DepartmentVO right) {
// 第一优先级:分级号较小的部别排在前面,空值排在最后。
int levelResult = compareNullableInteger(left.getLevelNumber(), right.getLevelNumber());
if (levelResult != 0) {
return levelResult;
}
// 第二优先级:分级号相同时按分类号排序,空值排在最后。
int classificationResult = compareNullableText(
left.getClassificationCode(), right.getClassificationCode());
if (classificationResult != 0) {
return classificationResult;
}
// 第三优先级:前两项相同时按部别名称排序。
return compareNullableText(left.getName(), right.getName());
}
/**
* 比较两个允许为空的整数,空值统一排在非空值之后。
*
* @param left 左侧整数
* @param right 右侧整数
* @return 符合 Comparator 约定的比较结果
*/
private int compareNullableInteger(Integer left, Integer right) {
// 两侧都为空时视为相等。
if (left == null && right == null) {
return 0;
}
// 仅左侧为空时,将左侧排在后面。
if (left == null) {
return 1;
}
// 仅右侧为空时,将右侧排在后面。
if (right == null) {
return -1;
}
// 两侧均有值时使用整数的自然顺序。
return left.compareTo(right);
}
/**
* 比较两个允许为空的文本,空值统一排在非空值之后。
*
* @param left 左侧文本
* @param right 右侧文本
* @return 符合 Comparator 约定的比较结果
*/
private int compareNullableText(String left, String right) {
// 两侧都为空时视为相等。
if (left == null && right == null) {
return 0;
}
// 仅左侧为空时,将左侧排在后面。
if (left == null) {
return 1;
}
// 仅右侧为空时,将右侧排在后面。
if (right == null) {
return -1;
}
// 优先忽略大小写比较,保证英文字母排序不受大小写直接影响。
int ignoreCaseResult = left.compareToIgnoreCase(right);
// 忽略大小写后相等时再按原文本比较,使排序结果保持稳定且明确。
return ignoreCaseResult == 0 ? left.compareTo(right) : ignoreCaseResult;
}
/**
* 校验并规范化部别编号。
*
* @param id 原始部别编号
* @return 去除首尾空白后的有效编号
*/
private String requireId(String id) {
// 第一步:去除编号首尾空白,并将空字符串统一转换为 null。
String normalizedId = normalizeText(id);
// 第二步:编号为空时返回参数错误。
if (normalizedId == null) {
throw new BusinessException(BAD_REQUEST, "部别编号不能为空");
}
// 第三步:限制编号最大长度,避免传入不符合存储定义的值。
if (normalizedId.length() > 36) {
throw new BusinessException(BAD_REQUEST, "部别编号格式不正确");
}
// 第四步:返回通过校验的规范化编号。
return normalizedId;
}
/**
* 校验必填文本并去除首尾空白。
*
* @param value 原始文本
* @param message 文本为空时使用的错误提示
* @return 规范化后的非空文本
*/
private String requireText(String value, String message) {
// 第一步:统一处理 null、空字符串和首尾空白。
String normalized = normalizeText(value);
// 第二步:规范化后为空时抛出调用方指定的参数错误。
if (normalized == null) {
throw new BusinessException(BAD_REQUEST, message);
}
// 第三步:返回通过必填校验的文本。
return normalized;
}
/**
* 规范化分类号,并为空值提供默认值。
*
* @param value 原始分类号
* @return 有效分类号;未填写时返回 0
*/
private String defaultClassificationCode(String value) {
// 第一步:去除首尾空白,并将空字符串转换为 null。
String normalized = normalizeText(value);
// 第二步:未填写分类号时使用系统默认分类号 0。
return normalized == null ? "0" : normalized;
}
/**
* 校验分级号,并为空值提供默认值。
*
* @param value 原始分级号
* @return 有效分级号;未填写时返回 0
*/
private Integer defaultLevelNumber(Integer value) {
// 第一步:未填写分级号时使用系统默认分级号 0。
if (value == null) {
return 0;
}
// 第二步:分级号不能为负数。
if (value < 0) {
throw new BusinessException(BAD_REQUEST, "分级号不能小于0");
}
// 第三步:返回通过校验的分级号。
return value;
}
/**
* 统一规范化可选文本。
*
* @param value 原始文本
* @return 去除首尾空白后的文本;null 或空字符串返回 null
*/
private String normalizeText(String value) {
// 原始值为 null 时直接返回 null。
if (value == null) {
return null;
}
// 去除用户输入或数据库文本中的首尾空白。
String normalized = value.trim();
// 去除空白后没有有效内容时统一返回 null。
return normalized.isEmpty() ? null : normalized;
}
}
@@ -0,0 +1,524 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.dto.dict.DictDataBatchQueryDTO;
import com.roomroot.jwgl.dto.dict.DictDataQueryDTO;
import com.roomroot.jwgl.dto.dict.DictDataSaveDTO;
import com.roomroot.jwgl.entity.SysDictData;
import com.roomroot.jwgl.entity.SysDictType;
import com.roomroot.jwgl.mapper.DictDataMapper;
import com.roomroot.jwgl.mapper.DictTypeMapper;
import com.roomroot.jwgl.service.DictDataService;
import com.roomroot.jwgl.unit.BusinessException;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.utils.PageResultUtil;
import com.roomroot.jwgl.vo.dict.DictDataVO;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 字典数据服务实现。
* <p>
* 实现字典数据的查询(单类型、分页、批量)、保存(新增/编辑)和删除业务逻辑。
* 保存时校验关联类型是否存在,处理默认值唯一性,保证数据一致性。
* </p>
*/
@Service
public class DictDataServiceImpl implements DictDataService {
/**
* 字典数据数据访问接口。
*/
@Resource
private DictDataMapper dictDataMapper;
/**
* 字典类型数据访问接口。
* <p>用于保存字典数据时校验关联的字典类型是否存在。</p>
*/
@Resource
private DictTypeMapper dictTypeMapper;
/**
* JSON序列化工具
*/
private final ObjectMapper objectMapper = new ObjectMapper();
/**
* 按类型编码查询全部启用的字典数据。
* <p>
* 构建查询条件:按类型编码精确匹配、状态为启用(1)、按排序号升序排列。
* 查询结果转换为 VO 列表返回,用于前端下拉框等场景。
* </p>
*
* @param dictTypeCode 字典类型编码
* @return 启用的字典数据 VO 列表
*/
@Override
public List<DictDataVO> listByType(String dictTypeCode) {
// 第一步:构建 Lambda 查询条件。
LambdaQueryWrapper<SysDictData> wrapper = new LambdaQueryWrapper<>();
// 按类型编码精确匹配。
wrapper.eq(SysDictData::getDictTypeCode, dictTypeCode);
// 仅查询状态为启用的数据。
wrapper.eq(SysDictData::getStatus, 1);
// 按排序号升序排列,保证前端展示顺序一致。
wrapper.orderByAsc(SysDictData::getDictSort);
// 第二步:执行查询。
List<SysDictData> list = dictDataMapper.selectList(wrapper);
// 第三步:将实体列表转换为 VO 列表返回。
return list.stream().map(this::toVO).collect(Collectors.toList());
}
/**
* 分页查询字典数据列表(管理页面用)。
* <p>
* 根据查询条件构建 MyBatis-Plus Lambda 查询包装器,
* 支持按字典类型编码精确匹配、关键字(模糊匹配标签或值)、状态筛选,
* 结果按排序号升序排列。分页查询完成后将实体列表转换为 VO 列表返回。
* </p>
*
* @param dto 查询条件,包含分页参数和筛选条件
* @return 字典数据分页结果
*/
@Override
public PageResult<DictDataVO> page(DictDataQueryDTO dto) {
// 第一步:创建 MyBatis-Plus 分页对象。
Page<SysDictData> page = new Page<>(dto.getPageNum(), dto.getPageSize());
// 第二步:构建 Lambda 查询条件包装器。
LambdaQueryWrapper<SysDictData> wrapper = new LambdaQueryWrapper<>();
// 第三步:按字典类型编码精确匹配(必填条件)。
wrapper.eq(SysDictData::getDictTypeCode, dto.getDictTypeCode());
// 第四步:若有关键字,同时模糊匹配字典标签和字典值。
if (dto.getKeyword() != null && !dto.getKeyword().isEmpty()) {
wrapper.and(w -> w.like(SysDictData::getDictLabel, dto.getKeyword())
.or()
.like(SysDictData::getDictValue, dto.getKeyword()));
}
// 第五步:若指定了状态,按状态精确匹配。
if (dto.getStatus() != null) {
wrapper.eq(SysDictData::getStatus, dto.getStatus());
}
// 第六步:按排序号升序排列。
wrapper.orderByAsc(SysDictData::getDictSort);
// 第七步:执行分页查询。
Page<SysDictData> result = dictDataMapper.selectPage(page, wrapper);
// 第八步:将查询结果的实体列表转换为 VO 列表。
List<DictDataVO> records = result.getRecords().stream()
.map(this::toVO)
.collect(Collectors.toList());
// 第九步:使用分页工具组装统一分页结果返回。
return PageResultUtil.fromRecords(
result,
records,
dto.getPageNum(),
dto.getPageSize());
}
/**
* 批量查询多个类型的字典数据。
* <p>
* 遍历传入的类型编码列表,依次调用 {@link #listByType} 查询每种类型的启用数据,
* 将结果按类型编码分组放入 Map 中返回。
* </p>
*
* @param dto 批量查询参数,包含字典类型编码列表
* @return 按类型编码分组的字典数据 VO 列表
*/
@Override
public Map<String, List<DictDataVO>> batchList(DictDataBatchQueryDTO dto) {
// 第一步:初始化结果 Map。
Map<String, List<DictDataVO>> result = new HashMap<>();
// 第二步:若类型编码列表为空,直接返回空 Map。
if (dto.getTypeCodes() == null || dto.getTypeCodes().isEmpty()) {
return result;
}
// 第三步:遍历每个类型编码,查询该类型的字典数据并放入 Map。
for (String typeCode : dto.getTypeCodes()) {
result.put(typeCode, listByType(typeCode));
}
return result;
}
/**
* 保存字典数据(新增或编辑)。
* <p>
* 根据 {@code dto.dictDataId} 判断是新增还是编辑:
* <ul>
* <li>保存前校验关联的字典类型是否存在。</li>
* <li>若 {@code isDefault} 设置为 1,先清除该类型下其他数据的默认标记,保证唯一性。</li>
* <li>新增:构建实体后插入数据库。</li>
* <li>编辑:查询原记录,更新字段后写入数据库。</li>
* </ul>
* 整个操作在事务中执行。
* </p>
*
* @param dto 保存参数
* @return 保存后的字典数据 VO
*/
@Override
@Transactional(rollbackFor = Exception.class)
public DictDataVO save(DictDataSaveDTO dto) {
// 第一步:校验关联的字典类型是否存在。
SysDictType type = dictTypeMapper.selectOne(
new LambdaQueryWrapper<SysDictType>()
.eq(SysDictType::getDictTypeCode, dto.getDictTypeCode()));
if (type == null) {
throw new BusinessException("字典类型不存在");
}
// 分支一:dictDataId 为空,执行新增操作。
if (dto.getDictDataId() == null) {
// 若设置为默认值,先清除该类型下其他数据的默认标记。
if (dto.getIsDefault() != null && dto.getIsDefault() == 1) {
clearDefault(dto.getDictTypeCode());
}
// 构建字典数据实体并赋值。
SysDictData entity = new SysDictData();
entity.setDictTypeCode(dto.getDictTypeCode());
entity.setDictLabel(dto.getDictLabel());
entity.setDictValue(dto.getDictValue());
entity.setDictSort(dto.getDictSort());
entity.setCssClass(dto.getCssClass());
entity.setListClass(dto.getListClass());
entity.setIsDefault(dto.getIsDefault());
entity.setStatus(dto.getStatus());
entity.setRemark(dto.getRemark());
// --- 版本控制:新增时初始化版本号为1,历史快照为空数组 ---
// 后续每次编辑都会通过 saveSnapshot 方法追加历史快照并递增版本号
entity.setVersion(1);
entity.setPrevSnapshot("[]");
// --- 逻辑删除:新增时显式标记为未删除 ---
// @TableLogic 注解会使 MyBatis-Plus 自动在查询中追加 WHERE is_deleted=0 条件
// 此处显式赋值,确保即使数据库默认值未生效也能正确标记
entity.setIsDeleted(0);
// 插入数据库,create_time 和 create_by 由 MyBatis-Plus 自动填充。
dictDataMapper.insert(entity);
// 转换为 VO 返回。
return toVO(entity);
} else {
// 分支二:dictDataId 有值,执行编辑操作。
// 查询原记录。
SysDictData exist = dictDataMapper.selectById(dto.getDictDataId());
if (exist == null) {
throw new BusinessException("字典数据不存在");
}
// 若之前不是默认值,现在设置为默认值,则清除该类型下其他数据的默认标记。
if (dto.getIsDefault() != null && dto.getIsDefault() == 1
&& (exist.getIsDefault() == null || exist.getIsDefault() != 1)) {
clearDefault(exist.getDictTypeCode());
}
// --- 版本控制逻辑:将编辑前的旧数据存入历史快照 ---
// saveSnapshot 方法会把当前 exist 对象(旧数据)转为 JSON 快照追加到 prevSnapshot 中,
// 随后 exist 的字段会被新值覆盖,版本号递增后写回数据库
saveSnapshot(exist);
// 更新实体字段。
exist.setDictTypeCode(dto.getDictTypeCode());
exist.setDictLabel(dto.getDictLabel());
exist.setDictValue(dto.getDictValue());
exist.setDictSort(dto.getDictSort());
exist.setCssClass(dto.getCssClass());
exist.setListClass(dto.getListClass());
exist.setIsDefault(dto.getIsDefault());
exist.setStatus(dto.getStatus());
exist.setRemark(dto.getRemark());
// 版本号递增(每次编辑产生新版本,旧版本已在 saveSnapshot 中存入历史)
exist.setVersion(exist.getVersion() + 1);
// 更新数据库,update_time 和 update_by 由 MyBatis-Plus 自动填充。
dictDataMapper.updateById(exist);
// 转换为 VO 返回。
return toVO(exist);
}
}
/**
* 保存历史版本快照。
* <p>
* 将编辑前的实体对象构建为 JSON 快照,追加到 {@code prevSnapshot} 字段中。
* 快照 JSON 结构示例:
* <pre>
* [
* {
* "version": 1,
* "snapshotTime": "2026-08-01T10:00:00",
* "dictLabel": "考试",
* "dictValue": "1",
* "dictSort": 1,
* "cssClass": "danger",
* "listClass": "default",
* "isDefault": 1,
* "status": 1,
* "remark": "初始版本"
* }
* ]
* </pre>
* 历史版本数量上限为 20 个,超出后自动丢弃最早的版本。
* </p>
*
* @param exist 编辑前的实体对象(即当前数据库中的旧数据)
*/
private void saveSnapshot(SysDictData exist) {
try {
// 构建当前版本的快照对象,包含版本号和所有业务字段
Map<String, Object> snapshot = new HashMap<>();
// 记录当前版本号,用于后续版本回滚时定位
snapshot.put("version", exist.getVersion());
// 记录快照生成时间,便于版本比对
snapshot.put("snapshotTime", LocalDateTime.now().toString());
// 注意:此处简化处理,实际项目中可能需要从安全上下文中获取操作人ID
// snapshot.put("operatorId", SecurityUtils.getCurrentUserId());
// 保存业务字段(不含版本号、快照、删除标记等系统字段,仅记录业务数据)
snapshot.put("dictLabel", exist.getDictLabel());
snapshot.put("dictValue", exist.getDictValue());
snapshot.put("dictSort", exist.getDictSort());
snapshot.put("cssClass", exist.getCssClass());
snapshot.put("listClass", exist.getListClass());
snapshot.put("isDefault", exist.getIsDefault());
snapshot.put("status", exist.getStatus());
snapshot.put("remark", exist.getRemark());
// 解析现有的快照历史(prevSnapshot 为空时初始化为空列表)
List<Map<String, Object>> history = new ArrayList<>();
if (exist.getPrevSnapshot() != null && !exist.getPrevSnapshot().isEmpty()) {
history = objectMapper.readValue(exist.getPrevSnapshot(),
new TypeReference<List<Map<String, Object>>>() {});
}
// 将当前快照追加到历史列表末尾
history.add(snapshot);
// 限制历史版本数量(例如最多保留20个版本),超出则丢弃最早的版本
// 保证 prevSnapshot 字段不会无限增长
while (history.size() > 20) {
history.remove(0);
}
// 将更新后的历史记录序列化为 JSON 并写回实体
// 注意:此处仅修改了实体对象的 prevSnapshot 字段,
// 实际持久化由调用方(save/rollback 方法中的 updateById)完成
exist.setPrevSnapshot(objectMapper.writeValueAsString(history));
} catch (JsonProcessingException e) {
throw new BusinessException("保存版本快照失败: " + e.getMessage());
}
}
/**
* 删除字典数据。
* <p>
* 根据字典数据ID查询记录,校验数据是否存在,存在则执行逻辑删除(is_deleted置为1)。
* 整个操作在事务中执行。
* </p>
*
* @param dictDataId 字典数据ID
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(Long dictDataId) {
// 第一步:根据 ID 查询字典数据。
SysDictData exist = dictDataMapper.selectById(dictDataId);
if (exist == null) {
throw new BusinessException("字典数据不存在");
}
// 第二步:执行删除。
// 由于 SysDictData 实体类已添加 @TableLogic 注解,MyBatis-Plus 会自动将
// deleteById 转换为 UPDATE sys_dict_data SET is_deleted = 1 WHERE dict_data_id = ?
// 即逻辑删除(标记为已删除),而非物理删除,保留历史数据以便版本回溯
dictDataMapper.deleteById(dictDataId);
}
/**
* 清除指定类型下的默认标记。
* <p>
* 将指定字典类型下所有 {@code isDefault=1} 的数据项更新为 {@code isDefault=0},
* 保证同一类型下只有一个默认选项。
* </p>
*
* @param dictTypeCode 字典类型编码
*/
private void clearDefault(String dictTypeCode) {
// 构建更新实体,只设置 isDefault 字段。
SysDictData update = new SysDictData();
update.setIsDefault(0);
// 执行更新:按类型编码和默认标记匹配。
dictDataMapper.update(update,
new LambdaQueryWrapper<SysDictData>()
.eq(SysDictData::getDictTypeCode, dictTypeCode)
.eq(SysDictData::getIsDefault, 1));
}
/**
* 将字典数据实体转换为展示对象。
* <p>
* 提取实体中的有效字段赋值给 VO,用于对外返回统一的数据结构。
* </p>
*
* @param entity 字典数据实体
* @return 字典数据 VO
*/
private DictDataVO toVO(SysDictData entity) {
DictDataVO vo = new DictDataVO();
vo.setDictDataId(entity.getDictDataId());
vo.setDictTypeCode(entity.getDictTypeCode());
vo.setDictLabel(entity.getDictLabel());
vo.setDictValue(entity.getDictValue());
vo.setDictSort(entity.getDictSort());
vo.setCssClass(entity.getCssClass());
vo.setListClass(entity.getListClass());
vo.setIsDefault(entity.getIsDefault());
vo.setStatus(entity.getStatus());
vo.setRemark(entity.getRemark());
// 版本控制字段
vo.setVersion(entity.getVersion());
vo.setPrevSnapshot(entity.getPrevSnapshot());
vo.setCreateTime(entity.getCreateTime());
vo.setUpdateTime(entity.getUpdateTime());
return vo;
}
/**
* 获取字典数据的历史版本列表。
* <p>
* 根据字典数据ID查询其 {@code prevSnapshot} 字段中存储的所有历史快照,
* 解析为版本列表返回。每个版本快照包含版本号、快照时间和当时的业务字段值。
* 当 {@code prevSnapshot} 为空时返回空列表。
* </p>
*
* @param dictDataId 字典数据ID
* @return 历史版本快照列表,每个元素为一个 Map,包含 version、snapshotTime 及各业务字段
*/
@Override
public List<Map<String, Object>> getHistory(Long dictDataId) {
// 根据 ID 查询字典数据(@TableLogic 自动过滤已删除记录)
SysDictData exist = dictDataMapper.selectById(dictDataId);
if (exist == null) {
throw new BusinessException("字典数据不存在");
}
try {
// 如果 prevSnapshot 为空或空字符串,直接返回空列表
if (exist.getPrevSnapshot() == null || exist.getPrevSnapshot().isEmpty()) {
return new ArrayList<>();
}
// 将 prevSnapshot JSON 解析为版本列表返回
return objectMapper.readValue(exist.getPrevSnapshot(),
new TypeReference<List<Map<String, Object>>>() {});
} catch (JsonProcessingException e) {
throw new BusinessException("获取历史版本失败: " + e.getMessage());
}
}
/**
* 回滚字典数据到指定版本。
* <p>
* 根据目标版本号从历史快照 {@code prevSnapshot} 中查找对应的快照数据,
* 将当前数据恢复到该版本的状态。回滚操作本身也会生成新的版本快照,
* 保证版本链的完整性(回滚前的当前状态也被保留)。
* </p>
* <p>
* 执行步骤:
* <ol>
* <li>查询字典数据是否存在</li>
* <li>从 prevSnapshot 中解析历史版本列表</li>
* <li>查找目标版本号对应的快照</li>
* <li>将当前状态保存为新版本快照(回滚前的状态入历史)</li>
* <li>从目标快照中恢复业务字段值</li>
* <li>版本号递增并更新数据库</li>
* </ol>
* </p>
*
* @param dictDataId 字典数据ID
* @param targetVersion 目标版本号,必须存在于历史快照中
* @return 回滚后的字典数据详情
*/
@Override
@Transactional(rollbackFor = Exception.class)
public DictDataVO rollback(Long dictDataId, Integer targetVersion) {
// 第一步:查询字典数据是否存在(@TableLogic 自动过滤已删除记录)
SysDictData exist = dictDataMapper.selectById(dictDataId);
if (exist == null) {
throw new BusinessException("字典数据不存在");
}
try {
// 第二步:从 prevSnapshot 中解析历史版本列表
List<Map<String, Object>> history = new ArrayList<>();
if (exist.getPrevSnapshot() != null && !exist.getPrevSnapshot().isEmpty()) {
history = objectMapper.readValue(exist.getPrevSnapshot(),
new TypeReference<List<Map<String, Object>>>() {});
}
// 第三步:在历史列表中查找目标版本号对应的快照
Map<String, Object> targetSnapshot = null;
for (Map<String, Object> snapshot : history) {
if (targetVersion.equals(snapshot.get("version"))) {
targetSnapshot = snapshot;
break;
}
}
// 如果目标版本不存在,抛出异常
if (targetSnapshot == null) {
throw new BusinessException("目标版本不存在");
}
// 第四步:将当前状态保存为新版本快照
// (回滚操作本身也是一次编辑,回滚前的状态需要保留在历史中)
saveSnapshot(exist);
// 第五步:从目标快照中恢复业务字段值
exist.setDictLabel((String) targetSnapshot.get("dictLabel"));
exist.setDictValue((String) targetSnapshot.get("dictValue"));
if (targetSnapshot.get("dictSort") != null) {
exist.setDictSort(((Number) targetSnapshot.get("dictSort")).intValue());
}
exist.setCssClass((String) targetSnapshot.get("cssClass"));
exist.setListClass((String) targetSnapshot.get("listClass"));
if (targetSnapshot.get("isDefault") != null) {
exist.setIsDefault(((Number) targetSnapshot.get("isDefault")).intValue());
}
if (targetSnapshot.get("status") != null) {
exist.setStatus(((Number) targetSnapshot.get("status")).intValue());
}
exist.setRemark((String) targetSnapshot.get("remark"));
// 第六步:版本号递增(回滚也算一次编辑,生成新版本)
exist.setVersion(exist.getVersion() + 1);
// 第七步:更新数据库,update_time 和 update_by 由 MyBatis-Plus 自动填充
dictDataMapper.updateById(exist);
// 转换为 VO 返回
return toVO(exist);
} catch (JsonProcessingException e) {
throw new BusinessException("回滚版本失败: " + e.getMessage());
}
}
}
@@ -0,0 +1,430 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.dto.dict.DictTypeQueryDTO;
import com.roomroot.jwgl.dto.dict.DictTypeSaveDTO;
import com.roomroot.jwgl.entity.SysDictType;
import com.roomroot.jwgl.entity.SysDictData;
import com.roomroot.jwgl.mapper.DictDataMapper;
import com.roomroot.jwgl.mapper.DictTypeMapper;
import com.roomroot.jwgl.service.DictTypeService;
import com.roomroot.jwgl.unit.BusinessException;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.utils.PageResultUtil;
import com.roomroot.jwgl.vo.dict.DictTypeVO;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 字典类型服务实现。
* <p>
* 实现字典类型的分页查询、保存(新增/编辑)和删除业务逻辑。
* 保存时校验编码唯一性,删除时校验是否有关联数据,保证数据完整性。
* </p>
*/
@Service
public class DictTypeServiceImpl implements DictTypeService {
/**
* 字典类型数据访问接口。
*/
@Resource
private DictTypeMapper dictTypeMapper;
/**
* 字典数据数据访问接口。
* <p>用于删除字典类型时校验是否有关联数据。</p>
*/
@Resource
private DictDataMapper dictDataMapper;
/**
* JSON序列化工具
*/
private final ObjectMapper objectMapper = new ObjectMapper();
/**
* 分页查询字典类型列表。
* <p>
* 根据查询条件构建 MyBatis-Plus Lambda 查询包装器,
* 支持按关键字(模糊匹配类型编码或名称)、所属分组、状态筛选,
* 结果按创建时间降序排列。分页查询完成后将实体列表转换为 VO 列表返回。
* </p>
*
* @param dto 查询条件,包含分页参数和筛选条件
* @return 字典类型分页结果
*/
@Override
public PageResult<DictTypeVO> page(DictTypeQueryDTO dto) {
// 第一步:创建 MyBatis-Plus 分页对象,传入当前页码和每页大小。
Page<SysDictType> page = new Page<>(dto.getPageNum(), dto.getPageSize());
// 第二步:构建 Lambda 查询条件包装器。
LambdaQueryWrapper<SysDictType> wrapper = new LambdaQueryWrapper<>();
// 第三步:若有关键字,同时模糊匹配类型编码和类型名称。
if (dto.getKeyword() != null && !dto.getKeyword().isEmpty()) {
wrapper.and(w -> w.like(SysDictType::getDictTypeCode, dto.getKeyword())
.or()
.like(SysDictType::getDictTypeName, dto.getKeyword()));
}
// 第四步:若指定了所属分组,按分组精确匹配。
if (dto.getDictGroup() != null && !dto.getDictGroup().isEmpty()) {
wrapper.eq(SysDictType::getDictGroup, dto.getDictGroup());
}
// 第五步:若指定了状态,按状态精确匹配。
if (dto.getStatus() != null) {
wrapper.eq(SysDictType::getStatus, dto.getStatus());
}
// 第六步:按创建时间降序排列,新创建的类型排在前面。
wrapper.orderByDesc(SysDictType::getCreateTime);
// 第七步:执行分页查询。
Page<SysDictType> result = dictTypeMapper.selectPage(page, wrapper);
// 第八步:将查询结果的实体列表转换为 VO 列表。
List<DictTypeVO> records = result.getRecords().stream()
.map(this::toVO)
.collect(Collectors.toList());
// 第九步:使用分页工具组装统一分页结果返回。
return PageResultUtil.fromRecords(
result,
records,
dto.getPageNum(),
dto.getPageSize());
}
/**
* 保存字典类型(新增或编辑)。
* <p>
* 根据 {@code dto.dictTypeId} 判断是新增还是编辑:
* <ul>
* <li>新增:校验类型编码全局唯一,构建实体后插入数据库。</li>
* <li>编辑:查询原记录,若修改了编码则重新校验唯一性,更新字段后写入数据库。</li>
* </ul>
* 整个操作在事务中执行,保证数据一致性。
* </p>
*
* @param dto 保存参数
* @return 保存后的字典类型 VO
*/
@Override
@Transactional(rollbackFor = Exception.class)
public DictTypeVO save(DictTypeSaveDTO dto) {
// 分支一:dictTypeId 为空,执行新增操作。
if (dto.getDictTypeId() == null) {
// 第一步:校验字典类型编码是否已存在。
LambdaQueryWrapper<SysDictType> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysDictType::getDictTypeCode, dto.getDictTypeCode());
if (dictTypeMapper.selectCount(wrapper) > 0) {
throw new BusinessException("字典类型编码已存在");
}
// 第二步:构建字典类型实体并赋值。
SysDictType entity = new SysDictType();
entity.setDictTypeCode(dto.getDictTypeCode());
entity.setDictTypeName(dto.getDictTypeName());
entity.setDictGroup(dto.getDictGroup());
entity.setStatus(dto.getStatus());
entity.setRemark(dto.getRemark());
// --- 版本控制:新增时初始化版本号为1,历史快照为空数组 ---
// 后续每次编辑都会通过 saveSnapshot 方法追加历史快照并递增版本号
entity.setVersion(1);
entity.setPrevSnapshot("[]");
// --- 逻辑删除:新增时显式标记为未删除 ---
// @TableLogic 注解会使 MyBatis-Plus 自动在查询中追加 WHERE is_deleted=0 条件
// 此处显式赋值,确保即使数据库默认值未生效也能正确标记
entity.setIsDeleted(0);
// 第三步:插入数据库,create_time 和 create_by 由 MyBatis-Plus 自动填充。
dictTypeMapper.insert(entity);
// 第四步:转换为 VO 返回。
return toVO(entity);
} else {
// 分支二:dictTypeId 有值,执行编辑操作。
// 第一步:根据 ID 查询原记录。
SysDictType exist = dictTypeMapper.selectById(dto.getDictTypeId());
if (exist == null) {
throw new BusinessException("字典类型不存在");
}
// 第二步:若修改了类型编码,校验新编码是否与其他记录冲突。
if (!exist.getDictTypeCode().equals(dto.getDictTypeCode())) {
LambdaQueryWrapper<SysDictType> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysDictType::getDictTypeCode, dto.getDictTypeCode());
if (dictTypeMapper.selectCount(wrapper) > 0) {
throw new BusinessException("字典类型编码已存在");
}
}
// --- 版本控制逻辑:将编辑前的旧数据存入历史快照 ---
// saveSnapshot 方法会把当前 exist 对象(旧数据)转为 JSON 快照追加到 prevSnapshot 中,
// 随后 exist 的字段会被新值覆盖,版本号递增后写回数据库
saveSnapshot(exist);
// 第三步:更新实体字段。
exist.setDictTypeCode(dto.getDictTypeCode());
exist.setDictTypeName(dto.getDictTypeName());
exist.setDictGroup(dto.getDictGroup());
exist.setStatus(dto.getStatus());
exist.setRemark(dto.getRemark());
// 版本号递增(每次编辑产生新版本,旧版本已在 saveSnapshot 中存入历史)
exist.setVersion(exist.getVersion() + 1);
// 第四步:更新数据库,update_time 和 update_by 由 MyBatis-Plus 自动填充。
dictTypeMapper.updateById(exist);
// 第五步:转换为 VO 返回。
return toVO(exist);
}
}
/**
* 保存历史版本快照。
* <p>
* 将编辑前的实体对象构建为 JSON 快照,追加到 {@code prevSnapshot} 字段中。
* 快照 JSON 结构示例:
* <pre>
* [
* {
* "version": 1,
* "snapshotTime": "2026-08-01T10:00:00",
* "dictTypeCode": "course_type",
* "dictTypeName": "课程类型",
* "dictGroup": "教学数据",
* "status": 1,
* "remark": "初始版本"
* }
* ]
* </pre>
* 历史版本数量上限为 20 个,超出后自动丢弃最早的版本。
* </p>
*
* @param exist 编辑前的实体对象(即当前数据库中的旧数据)
*/
private void saveSnapshot(SysDictType exist) {
try {
// 构建当前版本的快照对象,包含版本号和所有业务字段
Map<String, Object> snapshot = new HashMap<>();
// 记录当前版本号,用于后续版本回滚时定位
snapshot.put("version", exist.getVersion());
// 记录快照生成时间,便于版本比对
snapshot.put("snapshotTime", LocalDateTime.now().toString());
// 注意:此处简化处理,实际项目中可能需要从安全上下文中获取操作人ID
// snapshot.put("operatorId", SecurityUtils.getCurrentUserId());
// 保存业务字段(不含版本号、快照、删除标记等系统字段,仅记录业务数据)
snapshot.put("dictTypeCode", exist.getDictTypeCode());
snapshot.put("dictTypeName", exist.getDictTypeName());
snapshot.put("dictGroup", exist.getDictGroup());
snapshot.put("status", exist.getStatus());
snapshot.put("remark", exist.getRemark());
// 解析现有的快照历史(prevSnapshot 为空时初始化为空列表)
List<Map<String, Object>> history = new ArrayList<>();
if (exist.getPrevSnapshot() != null && !exist.getPrevSnapshot().isEmpty()) {
history = objectMapper.readValue(exist.getPrevSnapshot(),
new TypeReference<List<Map<String, Object>>>() {});
}
// 将当前快照追加到历史列表末尾
history.add(snapshot);
// 限制历史版本数量(例如最多保留20个版本),超出则丢弃最早的版本
// 保证 prevSnapshot 字段不会无限增长
while (history.size() > 20) {
history.remove(0);
}
// 将更新后的历史记录序列化为 JSON 并写回实体
// 注意:此处仅修改了实体对象的 prevSnapshot 字段,
// 实际持久化由调用方(save/rollback 方法中的 updateById)完成
exist.setPrevSnapshot(objectMapper.writeValueAsString(history));
} catch (JsonProcessingException e) {
throw new BusinessException("保存版本快照失败: " + e.getMessage());
}
}
/**
* 删除字典类型。
* <p>
* 删除前先校验字典类型是否存在,再通过字典类型编码查询该类型下是否有关联的字典数据项。
* 若存在数据项,抛出业务异常禁止删除;若不存在,执行逻辑删除(is_deleted置为1)。
* 整个操作在事务中执行。
* </p>
*
* @param dictTypeId 字典类型ID
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(Long dictTypeId) {
// 第一步:根据 ID 查询字典类型。
SysDictType exist = dictTypeMapper.selectById(dictTypeId);
if (exist == null) {
throw new BusinessException("字典类型不存在");
}
// 第二步:通过类型编码查询该类型下是否有关联的字典数据项。
LambdaQueryWrapper<SysDictData> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysDictData::getDictTypeCode, exist.getDictTypeCode());
if (dictDataMapper.selectCount(wrapper) > 0) {
throw new BusinessException("该字典类型下存在数据项,无法删除");
}
// 第三步:执行删除。
// 由于 SysDictType 实体类已添加 @TableLogic 注解,MyBatis-Plus 会自动将
// deleteById 转换为 UPDATE sys_dict_type SET is_deleted = 1 WHERE dict_type_id = ?
// 即逻辑删除(标记为已删除),而非物理删除,保留历史数据以便版本回溯
dictTypeMapper.deleteById(dictTypeId);
}
/**
* 将字典类型实体转换为展示对象。
* <p>
* 提取实体中的有效字段赋值给 VO,用于对外返回统一的数据结构。
* </p>
*
* @param entity 字典类型实体
* @return 字典类型 VO
*/
private DictTypeVO toVO(SysDictType entity) {
DictTypeVO vo = new DictTypeVO();
vo.setDictTypeId(entity.getDictTypeId());
vo.setDictTypeCode(entity.getDictTypeCode());
vo.setDictTypeName(entity.getDictTypeName());
vo.setDictGroup(entity.getDictGroup());
vo.setStatus(entity.getStatus());
vo.setRemark(entity.getRemark());
// 版本控制字段
vo.setVersion(entity.getVersion());
vo.setPrevSnapshot(entity.getPrevSnapshot());
vo.setCreateTime(entity.getCreateTime());
vo.setUpdateTime(entity.getUpdateTime());
return vo;
}
/**
* 获取字典类型的历史版本列表。
* <p>
* 根据字典类型ID查询其 {@code prevSnapshot} 字段中存储的所有历史快照,
* 解析为版本列表返回。每个版本快照包含版本号、快照时间和当时的业务字段值。
* 当 {@code prevSnapshot} 为空时返回空列表。
* </p>
*
* @param dictTypeId 字典类型ID
* @return 历史版本快照列表,每个元素为一个 Map,包含 version、snapshotTime 及各业务字段
*/
@Override
public List<Map<String, Object>> getHistory(Long dictTypeId) {
// 根据 ID 查询字典类型(@TableLogic 自动过滤已删除记录)
SysDictType exist = dictTypeMapper.selectById(dictTypeId);
if (exist == null) {
throw new BusinessException("字典类型不存在");
}
try {
// 如果 prevSnapshot 为空或空字符串,直接返回空列表
if (exist.getPrevSnapshot() == null || exist.getPrevSnapshot().isEmpty()) {
return new ArrayList<>();
}
// 将 prevSnapshot JSON 解析为版本列表返回
return objectMapper.readValue(exist.getPrevSnapshot(),
new TypeReference<List<Map<String, Object>>>() {});
} catch (JsonProcessingException e) {
throw new BusinessException("获取历史版本失败: " + e.getMessage());
}
}
/**
* 回滚字典类型到指定版本。
* <p>
* 根据目标版本号从历史快照 {@code prevSnapshot} 中查找对应的快照数据,
* 将当前数据恢复到该版本的状态。回滚操作本身也会生成新的版本快照,
* 保证版本链的完整性(回滚前的当前状态也被保留)。
* </p>
* <p>
* 执行步骤:
* <ol>
* <li>查询字典类型是否存在</li>
* <li>从 prevSnapshot 中解析历史版本列表</li>
* <li>查找目标版本号对应的快照</li>
* <li>将当前状态保存为新版本快照(回滚前的状态入历史)</li>
* <li>从目标快照中恢复业务字段值</li>
* <li>版本号递增并更新数据库</li>
* </ol>
* </p>
*
* @param dictTypeId 字典类型ID
* @param targetVersion 目标版本号,必须存在于历史快照中
* @return 回滚后的字典类型详情
*/
@Override
@Transactional(rollbackFor = Exception.class)
public DictTypeVO rollback(Long dictTypeId, Integer targetVersion) {
// 第一步:查询字典类型是否存在(@TableLogic 自动过滤已删除记录)
SysDictType exist = dictTypeMapper.selectById(dictTypeId);
if (exist == null) {
throw new BusinessException("字典类型不存在");
}
try {
// 第二步:从 prevSnapshot 中解析历史版本列表
List<Map<String, Object>> history = new ArrayList<>();
if (exist.getPrevSnapshot() != null && !exist.getPrevSnapshot().isEmpty()) {
history = objectMapper.readValue(exist.getPrevSnapshot(),
new TypeReference<List<Map<String, Object>>>() {});
}
// 第三步:在历史列表中查找目标版本号对应的快照
Map<String, Object> targetSnapshot = null;
for (Map<String, Object> snapshot : history) {
if (targetVersion.equals(snapshot.get("version"))) {
targetSnapshot = snapshot;
break;
}
}
// 如果目标版本不存在,抛出异常
if (targetSnapshot == null) {
throw new BusinessException("目标版本不存在");
}
// 第四步:将当前状态保存为新版本快照
// (回滚操作本身也是一次编辑,回滚前的状态需要保留在历史中)
saveSnapshot(exist);
// 第五步:从目标快照中恢复业务字段值
exist.setDictTypeCode((String) targetSnapshot.get("dictTypeCode"));
exist.setDictTypeName((String) targetSnapshot.get("dictTypeName"));
exist.setDictGroup((String) targetSnapshot.get("dictGroup"));
if (targetSnapshot.get("status") != null) {
exist.setStatus(((Number) targetSnapshot.get("status")).intValue());
}
exist.setRemark((String) targetSnapshot.get("remark"));
// 第六步:版本号递增(回滚也算一次编辑,生成新版本)
exist.setVersion(exist.getVersion() + 1);
// 第七步:更新数据库,update_time 和 update_by 由 MyBatis-Plus 自动填充
dictTypeMapper.updateById(exist);
// 转换为 VO 返回
return toVO(exist);
} catch (JsonProcessingException e) {
throw new BusinessException("回滚版本失败: " + e.getMessage());
}
}
}
@@ -0,0 +1,69 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.XKZYXX;
import com.roomroot.jwgl.mapper.XKZYXXMapper;
import com.roomroot.jwgl.service.DisciplineService;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jakarta.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 学科专业管理服务实现类
* <p>
* 实现学科、专业的增加、删除(逻辑删除)、更新、分页查询等功能。
* 逻辑删除将 停用 字段置为 1(停用=0为启用,停用=1为已停用/删除)。
* </p>
*/
@Service
public class DisciplineServiceImpl implements DisciplineService {
@Resource
private XKZYXXMapper xkzyxxMapper;
@Override
@Transactional
public void add(XKZYXX entity) {
entity.setTy(0);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
entity.setTysj(sdf.format(new Date()));
xkzyxxMapper.insert(entity);
}
@Override
@Transactional
public void disable(String bsh) {
XKZYXX entity = new XKZYXX();
entity.setBsh(bsh);
entity.setTy(1);
xkzyxxMapper.updateById(entity);
}
@Override
@Transactional
public void update(XKZYXX entity) {
xkzyxxMapper.updateById(entity);
}
@Override
public XKZYXX getById(String bsh) {
LambdaQueryWrapper<XKZYXX> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(XKZYXX::getBsh, bsh)
.eq(XKZYXX::getTy, 0);
return xkzyxxMapper.selectOne(wrapper);
}
@Override
public PageResult<XKZYXX> pageList(PageQuery query, XKZYXX xkzyxx) {
Page<XKZYXX> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<XKZYXX> result = xkzyxxMapper.selectPageList(page, xkzyxx);
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
}
@@ -0,0 +1,435 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.dto.elective.AddClassDTO;
import com.roomroot.jwgl.entity.*;
import com.roomroot.jwgl.mapper.*;
import com.roomroot.jwgl.vo.elective.*;
import com.roomroot.jwgl.mapper.*;
import com.roomroot.jwgl.service.BuildService;
import com.roomroot.jwgl.service.ElectiveService;
import com.roomroot.jwgl.unit.BusinessException;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.utils.UuidUtil;
import com.roomroot.jwgl.vo.elective.*;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.List;
@Service
public class ElectiveServiceImpl implements ElectiveService {
@Resource
private XYDBMapper xydbMapper;
@Resource
private XYDQBMapper xydqbMapper;
@Resource
private SSKCBXYDMapper sskcbxydMapper;
@Resource
private XYXXMapper xyxxMapper;
@Resource
private XYKCCJMapper xykccjMapper;
@Resource
private XYJXQJBMapper xyjxqjbMapper;
@Resource
private BuildService buildService;
@Resource
private ClassRoomMapper classRoomMapper;
@Resource
private ClassSemesterMapper classSemesterMapper;
@Override
@Transactional
public void add(AddClassDTO dto) {
XYDB entity = dto.getClazz();
xydbMapper.insert(entity);
// 同时写入学员队期表(学号规则、注册设置)
XYDQB period = dto.getPeriod();
if (period != null) {
period.setXydbh(entity.getXydbh());
xydqbMapper.insert(period);
}
}
@Override
@Transactional
public void disable(String xydbh) {
// 删除关联的学员队期记录
LambdaQueryWrapper<XYDQB> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(XYDQB::getXydbh, xydbh);
xydqbMapper.delete(wrapper);
// 物理删除教学班次
xydbMapper.deleteById(xydbh);
}
@Override
@Transactional
public void update(XYDB entity) {
xydbMapper.updateById(entity);
}
@Override
public XYDB getById(String xydbh) {
return xydbMapper.selectById(xydbh);
}
@Override
public PageResult<XYDB> pageList(PageQuery query, XYDB cond) {
Page<XYDB> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<XYDB> result = xydbMapper.selectPageList(page, cond);
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
@Override
public PageResult<ElectiveClassVO> pageListWithPeriod(PageQuery query, ElectiveClassVO cond) {
Page<ElectiveClassVO> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<ElectiveClassVO> result = xydbMapper.selectPageListWithPeriod(page, cond);
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
@Override
public List<XYDB> filterByDateRange(LocalDateTime startTime, LocalDateTime endTime) {
// 将 LocalDateTime 转换为字符串格式(达梦数据库要求)
String startTimeStr = startTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
String endTimeStr = endTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
return xydbMapper.selectByDateRange(startTimeStr, endTimeStr);
}
@Override
public XYDQB getPeriodByXydbh(String xydbh) {
LambdaQueryWrapper<XYDQB> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(XYDQB::getXydbh, xydbh);
return xydqbMapper.selectOne(wrapper);
}
@Override
@Transactional
public void updatePeriod(XYDQB entity) {
xydqbMapper.updateById(entity);
}
@Override
@Transactional
public void setRegistrationEnabled(String xydbh, Integer enabled) {
XYDQB entity = getPeriodByXydbh(xydbh);
if (entity != null) {
entity.setYxzc(enabled);
xydqbMapper.updateById(entity);
}
}
@Override
public String generateNextStudentNumber(String xydbh) {
XYDB clazz = xydbMapper.selectById(xydbh);
if (clazz == null || clazz.getRxrq() == null) {
throw new RuntimeException("教学班次不存在或未设置入学日期");
}
// 入学日期前缀:yyyyMMdd
String prefix = clazz.getRxrq().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
// 查询该班次已生成的最大学号
LambdaQueryWrapper<XYDQB> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(XYDQB::getXydbh, xydbh);
XYDQB period = xydqbMapper.selectOne(wrapper);
// 取学号位数,默认3位
int digits = (period != null && period.getXhws() != null) ? period.getXhws() : 3;
// 查询该前缀下已有最大流水号(通过学员信息表或其他方式)
// 简单实现:从1开始,按prefix+流水号查询最大数
String studentNumber = prefix + String.format("%0" + digits + "d", 1);
return studentNumber;
}
// ========== 关联信息查询 ==========
@Override
public List<ClassScheduleVO> getClassCourses(String xydbh, Integer nd) {
return sskcbxydMapper.selectScheduleByXydbh(xydbh, nd);
}
@Override
public List<ClassScheduleVO> getClassSchedule(String xydbh, Integer nd) {
return sskcbxydMapper.selectScheduleByXydbh(xydbh, nd);
}
@Override
public List<XYXX> getClassStudents(String xydbh) {
// 先查学员队期编号
LambdaQueryWrapper<XYDQB> pw = new LambdaQueryWrapper<>();
pw.eq(XYDQB::getXydbh, xydbh);
XYDQB period = xydqbMapper.selectOne(pw);
if (period == null) return new java.util.ArrayList<>();
// 再查学员
LambdaQueryWrapper<XYXX> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(XYXX::getXydqbh, period.getBh());
return xyxxMapper.selectList(wrapper);
}
@Override
public List<StudentGradeVO> getClassGrades(String xydbh, String kmbh) {
// 学员课程成绩 + 学员信息表联查
return xykccjMapper.selectGradeByXydbh(xydbh, kmbh);
}
@Override
public List<StudentAbsenceVO> getClassAbsences(String xydbh, Integer nd) {
return xyjxqjbMapper.selectAbsenceByXydbh(xydbh, nd);
}
// ========== 教学班次学期信息设置 ==========
@Override
public List<JXLB> listBuildings() {
return buildService.list(null);
}
@Override
public List<JSB> listRooms(String jxldh, String py) {
LambdaQueryWrapper<JSB> wrapper = new LambdaQueryWrapper<>();
if (jxldh != null && !jxldh.isEmpty()) {
wrapper.eq(JSB::getJxldh, jxldh);
}
if (py != null && !py.isEmpty()) {
wrapper.like(JSB::getPy, py);
}
wrapper.orderByAsc(JSB::getXh);
return classRoomMapper.selectList(wrapper);
}
@Override
public SemesterInitVO getSemesterInit(String xydbh, String presetKxrq, String presetJsrq) {
XYDB clazz = xydbMapper.selectById(xydbh);
if (clazz == null) {
throw new BusinessException("教学班次不存在:" + xydbh);
}
LocalDate rx = clazz.getRxrq() != null ? clazz.getRxrq().toLocalDate() : null;
LocalDate by = clazz.getByrq() != null ? clazz.getByrq().toLocalDate() : null;
// 判断学制是否半年以内
boolean halfYear = isHalfYear(rx, by);
// 学期第次:半年以内固定第一学期;半年以上按入学日期智能推断
Integer xqdc = halfYear ? 1 : inferXqdc(rx, by, LocalDate.now());
// 人才培养方案总学期数(半年一学期,向上取整)
Integer totalXqdc = calcTotalXqdc(rx, by);
// 开学/结束日期:半年以内默认入学/毕业日期;半年以上默认窗口预设日期
LocalDate kxrq;
LocalDate jsrq;
if (halfYear) {
kxrq = rx;
jsrq = by;
} else {
kxrq = parseDate(presetKxrq);
jsrq = parseDate(presetJsrq);
}
// 第二以上学期:默认预设上学期的专用教室
String zyjsbh = getPrevRoom(xydbh, xqdc);
String zyjsmc = null;
if (zyjsbh != null && !zyjsbh.isEmpty()) {
JSB room = classRoomMapper.selectById(zyjsbh);
if (room != null) {
zyjsmc = room.getJsmc();
}
}
SemesterInitVO vo = new SemesterInitVO();
vo.setXydbh(xydbh);
vo.setXydmc(clazz.getXydmc());
vo.setHalfYear(halfYear);
vo.setXqdc(xqdc);
vo.setTotalXqdc(totalXqdc);
vo.setKxrq(kxrq);
vo.setJsrq(jsrq);
vo.setZyjsbh(zyjsbh);
vo.setZyjsmc(zyjsmc);
vo.setJclb(clazz.getJclb());
return vo;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void addSemester(XYDNDXQJBXXB xydndxqjbxxb) {
if (xydndxqjbxxb == null || xydndxqjbxxb.getXydbh() == null || xydndxqjbxxb.getXydbh().isEmpty()) {
throw new BusinessException("班次编号不能为空");
}
XYDB clazz = xydbMapper.selectById(xydndxqjbxxb.getXydbh());
if (clazz == null) {
throw new BusinessException("教学班次不存在:" + xydndxqjbxxb.getXydbh());
}
LocalDate rx = clazz.getRxrq() != null ? clazz.getRxrq().toLocalDate() : null;
LocalDate by = clazz.getByrq() != null ? clazz.getByrq().toLocalDate() : null;
boolean halfYear = isHalfYear(rx, by);
// 学期第次:半年以内只能第一学期;半年以上为空时智能推断
Integer xqdc = xydndxqjbxxb.getXqdc();
if (halfYear) {
if (xqdc != null && xqdc != 1) {
throw new BusinessException("学制半年以内的班次,学期第次只能设置为第一学期");
}
xqdc = 1;
} else if (xqdc == null) {
xqdc = inferXqdc(rx, by, LocalDate.now());
}
xydndxqjbxxb.setXqdc(xqdc);
// 开学/结束日期:半年以内为空时默认入学/毕业日期;半年以上不允许为空(取窗口预设值)
if (xydndxqjbxxb.getKxrq() == null) {
if (halfYear) {
xydndxqjbxxb.setKxrq(rx);
} else {
throw new BusinessException("学制半年以上的班次,开学日期不能为空,请使用窗口预设开学日期");
}
}
if (xydndxqjbxxb.getJsrq() == null) {
if (halfYear) {
xydndxqjbxxb.setJsrq(by);
} else {
throw new BusinessException("学制半年以上的班次,结束日期不能为空,请使用窗口预设结束日期");
}
}
// 开学/结束日期先后校验
if (xydndxqjbxxb.getKxrq() != null && xydndxqjbxxb.getJsrq() != null
&& xydndxqjbxxb.getKxrq().isAfter(xydndxqjbxxb.getJsrq())) {
throw new BusinessException("开学日期不能晚于结束日期");
}
// 年度为空时默认入学日期所在年度
if (xydndxqjbxxb.getNd() == null) {
xydndxqjbxxb.setNd(rx != null ? rx.getYear() : LocalDate.now().getYear());
}
// 第二以上学期:专用教室为空时默认预设上学期的专用教室
if (xqdc > 1 && (xydndxqjbxxb.getZyjsbh() == null || xydndxqjbxxb.getZyjsbh().isEmpty())) {
xydndxqjbxxb.setZyjsbh(getPrevRoom(xydndxqjbxxb.getXydbh(), xqdc));
}
// 重复校验:同一班次+年度+学期第次不允许重复创建
XYDNDXQJBXXB existing = classSemesterMapper.selectByBcAndNdAndXqdc(
xydndxqjbxxb.getXydbh(),
String.valueOf(xydndxqjbxxb.getNd()),
String.valueOf(xqdc));
if (existing != null) {
throw new BusinessException("该班次 " + xydndxqjbxxb.getNd() + " 年度第 " + xqdc + " 学期的班次学期信息已存在,请勿重复添加");
}
// 默认值填充
xydndxqjbxxb.setBh(xydndxqjbxxb.getBh() == null ? UuidUtil.getUUID() : xydndxqjbxxb.getBh());
xydndxqjbxxb.setBdsj(xydndxqjbxxb.getBdsj() == null ? LocalDateTime.now() : xydndxqjbxxb.getBdsj());
xydndxqjbxxb.setXhbs(xydndxqjbxxb.getXhbs() == null ? 0 : xydndxqjbxxb.getXhbs());
xydndxqjbxxb.setYsbbh(xydndxqjbxxb.getYsbbh() == null ? 0 : xydndxqjbxxb.getYsbbh());
xydndxqjbxxb.setJclb(xydndxqjbxxb.getJclb() == null
? (clazz.getJclb() != null ? clazz.getJclb() : "双节次") : xydndxqjbxxb.getJclb());
xydndxqjbxxb.setKfjypk(xydndxqjbxxb.getKfjypk() == null ? 0 : xydndxqjbxxb.getKfjypk());
xydndxqjbxxb.setXtms(xydndxqjbxxb.getXtms() == null ? "" : xydndxqjbxxb.getXtms());
xydndxqjbxxb.setDelFlag(0);
classSemesterMapper.insert(xydndxqjbxxb);
}
/**
* 判断学制是否半年以内(入学日期到毕业日期不超过 183 天)
*/
private boolean isHalfYear(LocalDate rx, LocalDate by) {
if (rx == null || by == null) {
return false;
}
return ChronoUnit.DAYS.between(rx, by) <= 183;
}
/**
* 计算人才培养方案总学期数(半年一学期,向上取整;入学日期缺失时按 1 处理)
*/
private Integer calcTotalXqdc(LocalDate rx, LocalDate by) {
if (rx == null || by == null || !by.isAfter(rx)) {
return 1;
}
long totalMonths = ChronoUnit.MONTHS.between(rx, by);
return Math.max(1, (int) ((totalMonths + 5) / 6));
}
/**
* 根据班次入学日期智能推断指定日期所在的学期第次(每半年为一个学期)
* <p>
* 半年以内的班次返回 1;半年以上的班次按 入学日期 到 指定日期 经过的月份
* 每 6 个月为 1 个学期递增,且不超过人才培养方案总学期数。
* </p>
*/
private Integer inferXqdc(LocalDate rx, LocalDate by, LocalDate target) {
if (rx == null) {
return 1;
}
if (target.isBefore(rx)) {
return 1;
}
// 半年以内固定第一学期
if (by != null && isHalfYear(rx, by)) {
return 1;
}
long months = ChronoUnit.MONTHS.between(rx, target);
int xqdc = (int) (months / 6) + 1;
// 不超过总学期数
Integer total = calcTotalXqdc(rx, by);
return Math.min(xqdc, total);
}
/**
* 获取某班次指定学期第次的上一学期专用教室(用于第二以上学期的默认预设)
*
* @param xydbh 班次编号
* @param xqdc 当前学期第次(<=1 时返回 null)
* @return 上学期专用教室编号,不存在返回 null
*/
private String getPrevRoom(String xydbh, Integer xqdc) {
if (xqdc == null || xqdc <= 1) {
return null;
}
LambdaQueryWrapper<XYDNDXQJBXXB> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(XYDNDXQJBXXB::getXydbh, xydbh)
.eq(XYDNDXQJBXXB::getXqdc, xqdc - 1)
.eq(XYDNDXQJBXXB::getDelFlag, 0)
.orderByDesc(XYDNDXQJBXXB::getNd)
.last("LIMIT 1");
XYDNDXQJBXXB prev = classSemesterMapper.selectOne(wrapper);
return prev == null ? null : prev.getZyjsbh();
}
/**
* 解析 yyyy-MM-dd 格式日期字符串,为空或格式错误返回 null
*/
private LocalDate parseDate(String date) {
if (date == null || date.trim().isEmpty()) {
return null;
}
try {
return LocalDate.parse(date.trim(), DateTimeFormatter.ofPattern("yyyy-MM-dd"));
} catch (Exception e) {
return null;
}
}
}
@@ -0,0 +1,159 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.dto.faculty.AddTeacherDTO;
import com.roomroot.jwgl.entity.*;
import com.roomroot.jwgl.mapper.*;
import com.roomroot.jwgl.mapper.*;
import com.roomroot.jwgl.service.FacultyService;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.vo.faculty.TeacherListVO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
/**
* 教研室与教员管理服务实现类
* <p>
* 实现教研室和教员的增删改查,以及关联属性、综合评定和课程表查询。
* </p>
*/
@Service
public class FacultyServiceImpl implements FacultyService {
@Resource
private JYSBMapper jysbMapper;
@Resource
private JYBMapper jybMapper;
@Resource
private JYSXMapper jysxMapper;
@Resource
private NDZHPDJYMapper ndzhpdyMapper;
@Resource
private JYSCKCBMapper jysckcbMapper;
// ==================== 教研室管理 ====================
@Override
@Transactional
public void addJYSB(JYSB entity) {
if (entity.getTy() == null) {
entity.setTy(false);
}
jysbMapper.insert(entity);
}
@Override
@Transactional
public void disableJYSB(String jysdh) {
JYSB entity = new JYSB();
entity.setJysdh(jysdh);
entity.setTy(true);
entity.setTysj(LocalDateTime.now());
jysbMapper.updateById(entity);
}
@Override
@Transactional
public void updateJYSB(JYSB entity) {
jysbMapper.updateById(entity);
}
@Override
public JYSB getJYSBById(String jysdh) {
return jysbMapper.selectById(jysdh);
}
@Override
public PageResult<JYSB> pageJYSB(PageQuery query, JYSB cond) {
Page<JYSB> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<JYSB> result = jysbMapper.selectPageList(page, cond);
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
// ==================== 教员管理 ====================
@Override
@Transactional
public void addJYB(AddTeacherDTO dto) {
JYB entity = dto.getTeacher();
//离职状态默认为0
entity.setLzzt(0);
jybMapper.insert(entity);
// 使用前端传入的教员属性数据
JYSX jysx = dto.getAttributes();
if (jysx != null) {
jysx.setJybh(entity.getJybh());
jysxMapper.insert(jysx);
}
}
@Override
@Transactional
public void disableJYB(String jybh) {
JYB entity = new JYB();
entity.setJybh(jybh);
entity.setLzzt(1);
jybMapper.updateById(entity);
}
@Override
@Transactional
public void updateJYB(JYB entity) {
jybMapper.updateById(entity);
}
@Override
public JYB getJYBById(String jybh) {
return jybMapper.selectById(jybh);
}
@Override
public PageResult<TeacherListVO> pageJYBDetail(PageQuery query, JYB cond) {
Page<TeacherListVO> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<TeacherListVO> result = jybMapper.selectTeacherPageList(page, cond);
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
// ==================== 教员属性管理 ====================
@Override
public JYSX getJYSXByJYBH(String jybh) {
return jysxMapper.selectById(jybh);
}
@Override
@Transactional
public void updateJYSX(JYSX entity) {
jysxMapper.updateById(entity);
}
// ==================== 年度综合评定查询 ====================
@Override
public PageResult<NDZHPDJY> pageNDZHPDJY(PageQuery query, NDZHPDJY cond) {
Page<NDZHPDJY> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<NDZHPDJY> result = ndzhpdyMapper.selectPageList(page, cond);
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
// ==================== 教员课程表查询 ====================
@Override
public PageResult<JYSCKCB> pageJYSCKCB(PageQuery query, JYSCKCB cond) {
Page<JYSCKCB> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<JYSCKCB> result = jysckcbMapper.selectPageList(page, cond);
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
}
@@ -0,0 +1,59 @@
package com.roomroot.jwgl.service.impl;
import com.roomroot.jwgl.entity.KCB;
import com.roomroot.jwgl.mapper.KCBMapper;
import com.roomroot.jwgl.service.KCBService;
import com.roomroot.jwgl.utils.UuidUtil;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
/**
* 课程表服务实现
*/
@Service
public class KCBServiceImpl implements KCBService {
@Resource
private KCBMapper kcbMapper;
@Override
public void add(KCB kcb) {
if (kcb.getCjsj() == null) {
kcb.setCjsj(LocalDateTime.now());
}
if (kcb.getBdsj() == null) {
kcb.setBdsj(LocalDateTime.now());
}
kcb.setBh(UuidUtil.getUUID());
kcbMapper.insert(kcb);
}
@Override
public void delete(String kbh) {
kcbMapper.deleteById(kbh);
}
@Override
public void batchDelete(List<String> kbhList) {
kcbMapper.deleteBatchIds(kbhList);
}
@Override
public void update(KCB kcb) {
kcbMapper.updateById(kcb);
}
@Override
public KCB getByKbh(String kbh) {
return kcbMapper.selectById(kbh);
}
@Override
public List<KCB> list() {
return kcbMapper.selectList(null);
}
}
@@ -0,0 +1,205 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.roomroot.jwgl.dto.LeaveDTO;
import com.roomroot.jwgl.dto.LeaveQueryDTO;
import com.roomroot.jwgl.entity.QJKTB;
import com.roomroot.jwgl.entity.QJXYB;
import com.roomroot.jwgl.entity.XYJXQJB;
import com.roomroot.jwgl.entity.XYQJSPB;
import com.roomroot.jwgl.mapper.QJKTBMapper;
import com.roomroot.jwgl.mapper.QJXYBMapper;
import com.roomroot.jwgl.mapper.XYJXQJBMapper;
import com.roomroot.jwgl.mapper.XYQJSPBMapper;
import com.roomroot.jwgl.service.LeaveService;
import com.roomroot.jwgl.utils.UuidUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
/**
* 请假服务实现
*/
@Service
public class LeaveServiceImpl implements LeaveService {
@Resource
private XYJXQJBMapper xyjxqjbMapper;
@Resource
private QJKTBMapper qjktbMapper;
@Resource
private QJXYBMapper qjxybMapper;
@Resource
private XYQJSPBMapper xyqjspbMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public void add(LeaveDTO dto) {
XYJXQJB leave = dto.getLeave();
if (leave.getCjsj() == null) {
leave.setCjsj(LocalDateTime.now());
}
// 设置课堂数和学员人数
if (dto.getSsktBhList() != null) {
leave.setKts(dto.getSsktBhList().size());
}
if (dto.getXybh() != null) {
leave.setXyrs(1);
}
leave.setBh(UuidUtil.getUUID());
leave.setZt("待审批");
// 插入主表
xyjxqjbMapper.insert(leave);
String qjbh = leave.getBh();
// 批量插入请假课堂表
if (dto.getSsktBhList() != null) {
for (String ssktBh : dto.getSsktBhList()) {
QJKTB qjktb = new QJKTB();
qjktb.setQjbh(qjbh);
qjktb.setSsktbh(ssktBh);
qjktb.setBh(UuidUtil.getUUID());
qjktbMapper.insert(qjktb);
}
}
// 插入请假学员表
if (dto.getXybh() != null) {
QJXYB qjxyb = new QJXYB();
qjxyb.setQjbh(qjbh);
qjxyb.setXybh(dto.getXybh());
qjxyb.setBh(UuidUtil.getUUID());
qjxybMapper.insert(qjxyb);
}
}
@Override
public boolean update(XYJXQJB xyjxqjb) {
// 检查审批表是否有内容
if (hasApproval(xyjxqjb.getBh())) {
return false;
}
xyjxqjbMapper.updateById(xyjxqjb);
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean delete(String bh) {
// 检查审批表是否有内容
if (hasApproval(bh)) {
return false;
}
// 删除主表
xyjxqjbMapper.deleteById(bh);
// 删除请假课堂表
LambdaQueryWrapper<QJKTB> qjktbWrapper = new LambdaQueryWrapper<>();
qjktbWrapper.eq(QJKTB::getQjbh, bh);
qjktbMapper.delete(qjktbWrapper);
// 删除请假学员表
LambdaQueryWrapper<QJXYB> qjxybWrapper = new LambdaQueryWrapper<>();
qjxybWrapper.eq(QJXYB::getQjbh, bh);
qjxybMapper.delete(qjxybWrapper);
// 删除审批表
LambdaQueryWrapper<XYQJSPB> xyqjspbWrapper = new LambdaQueryWrapper<>();
xyqjspbWrapper.eq(XYQJSPB::getQjbh, bh);
xyqjspbMapper.delete(xyqjspbWrapper);
return true;
}
/**
* 检查请假编号是否存在审批记录
*/
private boolean hasApproval(String qjbh) {
LambdaQueryWrapper<XYQJSPB> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(XYQJSPB::getQjbh, qjbh);
return xyqjspbMapper.selectCount(wrapper) > 0;
}
@Override
public XYJXQJB getByBh(String bh) {
return xyjxqjbMapper.selectById(bh);
}
@Override
public List<XYJXQJB> list() {
return xyjxqjbMapper.selectList(null);
}
@Override
public List<XYJXQJB> list(LeaveQueryDTO queryDTO) {
LambdaQueryWrapper<XYJXQJB> wrapper = new LambdaQueryWrapper<>();
// 精确匹配
wrapper.eq(queryDTO.getBh() != null, XYJXQJB::getBh, queryDTO.getBh());
wrapper.eq(queryDTO.getMc() != null, XYJXQJB::getMc, queryDTO.getMc());
wrapper.eq(queryDTO.getZt() != null, XYJXQJB::getZt, queryDTO.getZt());
wrapper.eq(queryDTO.getXydbh() != null, XYJXQJB::getXydbh, queryDTO.getXydbh());
// 起始时间范围查询
wrapper.ge(queryDTO.getQssjStart() != null, XYJXQJB::getQssj, queryDTO.getQssjStart());
wrapper.le(queryDTO.getQssjEnd() != null, XYJXQJB::getQssj, queryDTO.getQssjEnd());
// 结束时间范围查询
wrapper.ge(queryDTO.getJssjStart() != null, XYJXQJB::getJssj, queryDTO.getJssjStart());
wrapper.le(queryDTO.getJssjEnd() != null, XYJXQJB::getJssj, queryDTO.getJssjEnd());
// 创建时间范围查询
wrapper.ge(queryDTO.getCjsjStart() != null, XYJXQJB::getCjsj, queryDTO.getCjsjStart());
wrapper.le(queryDTO.getCjsjEnd() != null, XYJXQJB::getCjsj, queryDTO.getCjsjEnd());
return xyjxqjbMapper.selectList(wrapper);
}
@Override
public List<String> listClassroomBhsByQjbh(String qjbh) {
LambdaQueryWrapper<QJKTB> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(QJKTB::getQjbh, qjbh);
List<QJKTB> list = qjktbMapper.selectList(wrapper);
return list.stream().map(QJKTB::getSsktbh).collect(Collectors.toList());
}
@Override
public List<String> listStudentBhsByQjbh(String qjbh) {
LambdaQueryWrapper<QJXYB> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(QJXYB::getQjbh, qjbh);
List<QJXYB> list = qjxybMapper.selectList(wrapper);
return list.stream().map(QJXYB::getXybh).collect(Collectors.toList());
}
// ===== 审批表 CRUD =====
@Override
public void addApproval(XYQJSPB xyqjspb) {
if (xyqjspb.getSpsj() == null) {
xyqjspb.setSpsj(LocalDateTime.now());
}
xyqjspbMapper.insert(xyqjspb);
}
@Override
public void deleteApproval(String bh) {
xyqjspbMapper.deleteById(bh);
}
@Override
public void updateApproval(XYQJSPB xyqjspb) {
xyqjspbMapper.updateById(xyqjspb);
}
@Override
public XYQJSPB getApprovalByBh(String bh) {
return xyqjspbMapper.selectById(bh);
}
@Override
public List<XYQJSPB> listApprovalsByQjbh(String qjbh) {
LambdaQueryWrapper<XYQJSPB> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(XYQJSPB::getQjbh, qjbh);
return xyqjspbMapper.selectList(wrapper);
}
}
@@ -0,0 +1,766 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.*;
import com.roomroot.jwgl.mapper.*;
import com.roomroot.jwgl.mapper.*;
import com.roomroot.jwgl.service.LogService;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.vo.log.TeacherHoursVO;
import com.roomroot.jwgl.vo.log.TeachingLogListVO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jakarta.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
/**
* 教学日志管理服务实现
*/
@Slf4j
@Service
public class LogServiceImpl implements LogService {
@Resource
private BCKTJXRZMapper bcktjxrzMapper;
@Resource
private SSKCB_RZMapper sskcbRzMapper;
@Resource
private BCKTJXRZSKJYMapper bcktjxrzskyMapper;
@Resource
private JXRZJYKSBZMapper jxrzjyksbzMapper;
@Resource
private JXRZKSBZSPBMapper jxrzksbzspbMapper;
@Resource
private JYKSBZMapper jyksbzMapper;
@Resource
private KSFBZMapper ksfbzMapper;
@Resource
private JSZWKSFBZMapper jszwksfbzMapper;
@Resource
private JYNZKSFMapper jynzksfMapper;
@Resource
private NZKSFMapper nzksfMapper;
@Resource
private KSBZXMMapper ksbzxmMapper;
@Resource
private SSKCBMapper sskcbMapper;
@Resource
private JXSSJHTJBMapper jxssjhtjbMapper;
@Resource
private KSHBZMapper kshbzMapper;
@Resource
private KSXSFAMapper ksxsfaMapper;
@Resource
private JZGZLMapper jzgzlMapper;
@Resource
private JXRZXYXXQKMapper jxrzxyxxqkMapper;
@Resource
private TeacherHoursMapper teacherHoursMapper;
// ==================== 教学日志核心 ====================
@Override
@Transactional
public void addTeachingLog(BCKTJXRZ entity) {
if (entity.getZt() == null) entity.setZt("0");
bcktjxrzMapper.insert(entity);
}
@Override
@Transactional
public void updateTeachingLog(BCKTJXRZ entity) {
bcktjxrzMapper.updateById(entity);
}
@Override
public BCKTJXRZ getTeachingLogById(String bh) {
return bcktjxrzMapper.selectById(bh);
}
@Override
public PageResult<BCKTJXRZ> pageTeachingLog(PageQuery query, BCKTJXRZ cond) {
Page<BCKTJXRZ> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<BCKTJXRZ> result = bcktjxrzMapper.selectPageList(page, cond);
return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize());
}
@Override
public PageResult<TeachingLogListVO> pageTeachingLogDetail(PageQuery query, TeachingLogListVO cond) {
Page<TeachingLogListVO> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<TeachingLogListVO> result = sskcbRzMapper.selectTeachingLogList(page, cond);
return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize());
}
// ==================== 教学日志(实施_课程表_日志 36字段表) ====================
@Override
@Transactional
public void addLog(SSKCB_RZ entity) {
fillNotNullDefaults(entity);
sskcbRzMapper.insert(entity);
}
/**
* 为 NOT NULL 字段填充默认值
*/
private void fillNotNullDefaults(SSKCB_RZ entity) {
if (entity.getTybh() == null) entity.setTybh(java.util.UUID.randomUUID().toString());
if (entity.getJybh() == null) entity.setJybh(java.util.UUID.randomUUID().toString().substring(0, 8));
if (entity.getZczt() == null) entity.setZczt(0);
if (entity.getSdrs() == null) entity.setSdrs(0);
if (entity.getJcks() == null) entity.setJcks(0);
if (entity.getAtj() == null) entity.setAtj(0);
if (entity.getRq() == null) entity.setRq(LocalDateTime.now());
if (entity.getQsjc() == null) entity.setQsjc(0);
if (entity.getJsjc() == null) entity.setJsjc(0);
if (entity.getZt() == null) entity.setZt("系统创建");
if (entity.getJxrzcjfs() == null) entity.setJxrzcjfs("手动");
if (entity.getKckmbh() == null) entity.setKckmbh(java.util.UUID.randomUUID().toString().substring(0, 8));
if (entity.getRwlb() == null) entity.setRwlb("其他");
if (entity.getYdrs() == null) entity.setYdrs(0);
if (entity.getSksjbd() == null) entity.setSksjbd(0);
if (entity.getSkddbd() == null) entity.setSkddbd(0);
if (entity.getKcbd() == null) entity.setKcbd(0);
if (entity.getSkktbd() == null) entity.setSkktbd(0);
if (entity.getRwlbbd() == null) entity.setRwlbbd(0);
if (entity.getPxqbbd() == null) entity.setPxqbbd(0);
if (entity.getSkfsbd() == null) entity.setSkfsbd(0);
if (entity.getFzyzdjybd() == null) entity.setFzyzdjybd(0);
if (entity.getCjsj() == null) entity.setCjsj(LocalDateTime.now());
}
@Override
@Transactional
public void rejectLog(String bh, String thyj) {
SSKCB_RZ entity = new SSKCB_RZ();
entity.setBh(bh);
entity.setZt("已退回");
entity.setThyj(thyj);
entity.setXgsj(LocalDateTime.now());
sskcbRzMapper.updateById(entity);
}
@Override
@Transactional
public SSKCB_RZ autoCreateLog(String tybh) {
SSKCB schedule = sskcbMapper.selectById(tybh);
SSKCB_RZ log = new SSKCB_RZ();
log.setTybh(tybh);
if (schedule != null) {
log.setRq(schedule.getRq());
log.setQsjc(schedule.getJc());
log.setJsjc(schedule.getJc());
log.setJsfs(schedule.getJxff());
log.setSkkt(schedule.getJxnr());
}
log.setZt("系统创建");
log.setCjsj(LocalDateTime.now());
log.setJxrzcjfs("自动创建");
sskcbRzMapper.insert(log);
return log;
}
@Override
@Transactional
public void importLog(java.util.List<SSKCB_RZ> list) {
for (SSKCB_RZ item : list) {
if (item.getZt() == null) item.setZt("系统创建");
if (item.getCjsj() == null) item.setCjsj(LocalDateTime.now());
sskcbRzMapper.insert(item);
}
}
@Override
@Transactional
public void importLogFromExcel(org.springframework.web.multipart.MultipartFile file) {
try {
org.apache.poi.ss.usermodel.Workbook wb = org.apache.poi.ss.usermodel.WorkbookFactory.create(file.getInputStream());
try {
org.apache.poi.ss.usermodel.Sheet sheet = wb.getSheetAt(0);
int lastRow = sheet.getLastRowNum();
for (int i = 1; i <= lastRow; i++) {
org.apache.poi.ss.usermodel.Row row = sheet.getRow(i);
if (row == null) continue;
SSKCB_RZ entity = new SSKCB_RZ();
entity.setRq(parseDate(getCellValue(row.getCell(3))));
entity.setQsjc(toInt(getCellValue(row.getCell(4))));
entity.setJsjc(toInt(getCellValue(row.getCell(5))));
entity.setAtj("是".equals(getCellValue(row.getCell(6))) ? 1 : 0);
entity.setSkdd(getCellValue(row.getCell(7)));
entity.setKckmbh(getCellValue(row.getCell(8)));
entity.setSkkt(getCellValue(row.getCell(10)));
entity.setRwlb(getCellValue(row.getCell(11)));
entity.setPxqbxx(getCellValue(row.getCell(12)));
entity.setYdrs(toInt(getCellValue(row.getCell(13))));
entity.setSdrs(toInt(getCellValue(row.getCell(14))));
entity.setJsfs(getCellValue(row.getCell(15)));
fillNotNullDefaults(entity);
sskcbRzMapper.insert(entity);
}
} finally {
wb.close();
}
} catch (Exception e) {
log.error("导入教学日志失败", e);
throw new RuntimeException("导入教学日志失败:" + e.getMessage());
}
}
private String getCellValue(org.apache.poi.ss.usermodel.Cell cell) {
if (cell == null) return "";
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue().trim();
case NUMERIC:
if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) {
return new java.text.SimpleDateFormat("yyyy-MM-dd").format(cell.getDateCellValue());
}
double val = cell.getNumericCellValue();
if (val == Math.floor(val) && !Double.isInfinite(val)) {
return String.valueOf((long) val);
}
return String.valueOf(val);
default:
return "";
}
}
private LocalDateTime parseDate(String str) {
if (str == null || str.isEmpty()) return null;
try {
return LocalDateTime.parse(str + "T00:00:00");
} catch (Exception e) {
}
try {
return java.time.LocalDate.parse(str).atStartOfDay();
} catch (Exception e) {
}
return null;
}
private Integer toInt(String str) {
if (str == null || str.isEmpty()) return null;
try {
return Integer.parseInt(str.trim());
} catch (Exception e) {
return null;
}
}
@Override
@Transactional
public int batchReportLogs(java.util.List<String> bhList) {
if (bhList == null || bhList.isEmpty()) return 0;
return sskcbRzMapper.update(null, new LambdaUpdateWrapper<SSKCB_RZ>()
.in(SSKCB_RZ::getBh, bhList)
.and(w -> w.isNull(SSKCB_RZ::getZt).or().notIn(SSKCB_RZ::getZt, "已上报", "已审核"))
.set(SSKCB_RZ::getZt, "已上报")
.set(SSKCB_RZ::getXgsj, LocalDateTime.now()));
}
@Override
@Transactional
public int batchAuditLogs(java.util.List<String> bhList) {
if (bhList == null || bhList.isEmpty()) return 0;
return sskcbRzMapper.update(null, new LambdaUpdateWrapper<SSKCB_RZ>()
.in(SSKCB_RZ::getBh, bhList)
.eq(SSKCB_RZ::getZt, "已上报")
.set(SSKCB_RZ::getZt, "已审核")
.set(SSKCB_RZ::getXgsj, LocalDateTime.now()));
}
@Override
public void exportLogExcel(TeachingLogListVO cond, jakarta.servlet.http.HttpServletResponse response) {
List<TeachingLogListVO> list = sskcbRzMapper.selectTeachingLogList(
new Page<>(1, Integer.MAX_VALUE), cond).getRecords();
try (org.apache.poi.xssf.usermodel.XSSFWorkbook wb = new org.apache.poi.xssf.usermodel.XSSFWorkbook()) {
org.apache.poi.ss.usermodel.Sheet sheet = wb.createSheet("教学日志");
String[] headers = {"序号", "主讲教员标识号", "主讲教员姓名", "授课日期", "起始节次",
"结束节次", "按天记", "授课地点", "课标识号", "课名称", "授课课题",
"任务类别", "培训期班", "应到人数", "实到人数", "授课方式", "分组指导教员"};
org.apache.poi.ss.usermodel.Row headerRow = sheet.createRow(0);
for (int i = 0; i < headers.length; i++) {
headerRow.createCell(i).setCellValue(headers[i]);
}
int rowIdx = 1;
for (TeachingLogListVO v : list) {
org.apache.poi.ss.usermodel.Row row = sheet.createRow(rowIdx++);
row.createCell(0).setCellValue(rowIdx - 1); // 序号
row.createCell(1).setCellValue(v.getJybh() != null ? v.getJybh() : "");
row.createCell(2).setCellValue(v.getJyxm() != null ? v.getJyxm() : "");
row.createCell(3).setCellValue(v.getRq() != null ? v.getRq().toLocalDate().toString() : "");
row.createCell(4).setCellValue(v.getQsjc() != null ? v.getQsjc() : 0);
row.createCell(5).setCellValue(v.getJsjc() != null ? v.getJsjc() : 0);
row.createCell(6).setCellValue(v.getAtj() != null && v.getAtj() == 1 ? "是" : "否");
row.createCell(7).setCellValue(v.getSkdd() != null ? v.getSkdd() : "");
row.createCell(8).setCellValue(v.getKckmbh() != null ? v.getKckmbh() : "");
row.createCell(9).setCellValue(v.getKcmc() != null ? v.getKcmc() : "");
row.createCell(10).setCellValue(v.getSkkt() != null ? v.getSkkt() : "");
row.createCell(11).setCellValue(v.getRwlb() != null ? v.getRwlb() : "");
row.createCell(12).setCellValue(v.getPxqbxx() != null ? v.getPxqbxx() : "");
row.createCell(13).setCellValue(v.getYdrs() != null ? v.getYdrs() : 0);
row.createCell(14).setCellValue(v.getSdrs() != null ? v.getSdrs() : 0);
row.createCell(15).setCellValue(v.getJsfs() != null ? v.getJsfs() : "");
row.createCell(16).setCellValue(""); // 分组指导教员(需扩展关联查询)
}
for (int i = 0; i < headers.length; i++) sheet.autoSizeColumn(i);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename=teaching_log.xlsx");
wb.write(response.getOutputStream());
response.getOutputStream().flush();
} catch (Exception e) {
throw new RuntimeException("导出失败", e);
}
}
@Override
@Transactional
public void submitApproval(String bh, String targetStatus) {
BCKTJXRZ entity = new BCKTJXRZ();
entity.setBh(bh);
entity.setZt(targetStatus);
bcktjxrzMapper.updateById(entity);
}
@Override
@Transactional
public void approve(String bh, String feedback) {
BCKTJXRZ entity = new BCKTJXRZ();
entity.setBh(bh);
entity.setZt("5"); // 已审核通过
entity.setJwbmfkyj(feedback);
bcktjxrzMapper.updateById(entity);
}
@Override
@Transactional
public void reject(String bh, String feedback) {
BCKTJXRZ entity = new BCKTJXRZ();
entity.setBh(bh);
entity.setZt("1"); // 退回至填写中
entity.setJwbmfkyj(feedback);
bcktjxrzMapper.updateById(entity);
}
@Override
@Transactional
public BCKTJXRZ autoCreateFromSchedule(String sskcbh) {
SSKCB schedule = sskcbMapper.selectById(sskcbh);
if (schedule == null) return null;
BCKTJXRZ log = new BCKTJXRZ();
log.setSskcbh(schedule.getSskcbh());
log.setSskcbbh(schedule.getBh());
log.setRq(schedule.getRq());
log.setJc(schedule.getJc());
log.setJxff(schedule.getJxff());
log.setNd(schedule.getNd());
log.setZt("0"); // 未填写
bcktjxrzMapper.insert(log);
return log;
}
@Override
public PageResult<?> checkLogStatusBySchedule(PageQuery query, String jybh, Integer nd) {
// 查询某教员的课表及对应教学日志状态
Map<String, Object> result = new HashMap<>();
// 此处实现对照查询逻辑
Page<SSKCB> page = new Page<>(query.getPageNum(), query.getPageSize());
LambdaQueryWrapper<SSKCB> wrapper = new LambdaQueryWrapper<>();
if (jybh != null) wrapper.eq(SSKCB::getSskcbh, jybh);
if (nd != null) wrapper.eq(SSKCB::getNd, nd);
Page<SSKCB> schedulePage = sskcbMapper.selectPage(page, wrapper);
return new PageResult<>(schedulePage.getRecords(), schedulePage.getTotal(), query.getPageNum(), query.getPageSize());
}
// ==================== 教学日志授课教员 ====================
@Override
@Transactional
public void addLogTeacher(BCKTJXRZSKJY entity) {
bcktjxrzskyMapper.insert(entity);
}
@Override
public PageResult<BCKTJXRZSKJY> pageLogTeacher(PageQuery query, BCKTJXRZSKJY cond) {
Page<BCKTJXRZSKJY> page = new Page<>(query.getPageNum(), query.getPageSize());
LambdaQueryWrapper<BCKTJXRZSKJY> wrapper = new LambdaQueryWrapper<>();
if (cond.getBcktjxrzbh() != null) wrapper.eq(BCKTJXRZSKJY::getBcktjxrzbh, cond.getBcktjxrzbh());
if (cond.getFzjybh() != null) wrapper.eq(BCKTJXRZSKJY::getFzjybh, cond.getFzjybh());
Page<BCKTJXRZSKJY> result = bcktjxrzskyMapper.selectPage(page, wrapper);
return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize());
}
// ==================== 教学日志学员学习情况 ====================
@Override
@Transactional
public void addStudentLearningStatus(JXRZXYXXQK entity) {
jxrzxyxxqkMapper.insert(entity);
}
@Override
public PageResult<JXRZXYXXQK> pageStudentLearningStatus(PageQuery query, String jxrzbh) {
Page<JXRZXYXXQK> page = new Page<>(query.getPageNum(), query.getPageSize());
LambdaQueryWrapper<JXRZXYXXQK> wrapper = new LambdaQueryWrapper<>();
if (jxrzbh != null) wrapper.eq(JXRZXYXXQK::getJxrzbh, jxrzbh);
Page<JXRZXYXXQK> result = jxrzxyxxqkMapper.selectPage(page, wrapper);
return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize());
}
// ==================== 课时核算标准 ====================
@Override
@Transactional
public void addCalculationStandard(KSHBZ entity) {
kshbzMapper.insert(entity);
}
@Override
@Transactional
public void updateCalculationStandard(KSHBZ entity) {
kshbzMapper.updateById(entity);
}
@Override
public KSHBZ getCalculationStandardById(String bh) {
return kshbzMapper.selectById(bh);
}
@Override
public PageResult<KSHBZ> pageCalculationStandard(PageQuery query, KSHBZ cond) {
Page<KSHBZ> page = new Page<>(query.getPageNum(), query.getPageSize());
LambdaQueryWrapper<KSHBZ> wrapper = new LambdaQueryWrapper<>();
if (cond.getMc() != null) wrapper.like(KSHBZ::getMc, cond.getMc());
Page<KSHBZ> result = kshbzMapper.selectPage(page, wrapper);
return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize());
}
// ==================== 课时系数方案 ====================
@Override
@Transactional
public void addCoefficientPlan(KSXSFA entity) {
ksxsfaMapper.insert(entity);
}
@Override
@Transactional
public void updateCoefficientPlan(KSXSFA entity) {
ksxsfaMapper.updateById(entity);
}
@Override
public KSXSFA getCoefficientPlanById(String bh) {
return ksxsfaMapper.selectById(bh);
}
@Override
public PageResult<KSXSFA> pageCoefficientPlan(PageQuery query, KSXSFA cond) {
Page<KSXSFA> page = new Page<>(query.getPageNum(), query.getPageSize());
LambdaQueryWrapper<KSXSFA> wrapper = new LambdaQueryWrapper<>();
if (cond != null && cond.getMc() != null && !cond.getMc().isEmpty()) {
wrapper.like(KSXSFA::getMc, cond.getMc());
}
wrapper.orderByDesc(KSXSFA::getCjsj);
Page<KSXSFA> result = ksxsfaMapper.selectPage(page, wrapper);
return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize());
}
// ==================== 课时费标准 ====================
@Override
@Transactional
public void addFeeStandard(KSFBZ entity) {
entity.setCjsj(LocalDateTime.now());
ksfbzMapper.insert(entity);
}
@Override
@Transactional
public void updateFeeStandard(KSFBZ entity) {
entity.setXgsj(LocalDateTime.now());
ksfbzMapper.updateById(entity);
}
@Override
public KSFBZ getFeeStandardById(String bh) {
return ksfbzMapper.selectById(bh);
}
@Override
public PageResult<KSFBZ> pageFeeStandard(PageQuery query, KSFBZ cond) {
Page<KSFBZ> page = new Page<>(query.getPageNum(), query.getPageSize());
LambdaQueryWrapper<KSFBZ> wrapper = new LambdaQueryWrapper<>();
if (cond.getMc() != null) wrapper.like(KSFBZ::getMc, cond.getMc());
Page<KSFBZ> result = ksfbzMapper.selectPage(page, wrapper);
return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize());
}
// ==================== 技术职务课时费标准 ====================
@Override
@Transactional
public void addTechPosFeeStandard(JSZWKSFBZ entity) {
jszwksfbzMapper.insert(entity);
}
@Override
@Transactional
public void updateTechPosFeeStandard(JSZWKSFBZ entity) {
jszwksfbzMapper.updateById(entity);
}
@Override
public JSZWKSFBZ getTechPosFeeStandardById(String bh) {
return jszwksfbzMapper.selectById(bh);
}
@Override
public PageResult<JSZWKSFBZ> pageTechPosFeeStandard(PageQuery query, JSZWKSFBZ cond) {
Page<JSZWKSFBZ> page = new Page<>(query.getPageNum(), query.getPageSize());
LambdaQueryWrapper<JSZWKSFBZ> wrapper = new LambdaQueryWrapper<>();
if (cond.getJszwbh() != null) wrapper.eq(JSZWKSFBZ::getJszwbh, cond.getJszwbh());
Page<JSZWKSFBZ> result = jszwksfbzMapper.selectPage(page, wrapper);
return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize());
}
// ==================== 课时补助项目 ====================
@Override
@Transactional
public void addSubsidyProject(KSBZXM entity) {
ksbzxmMapper.insert(entity);
}
@Override
@Transactional
public void updateSubsidyProject(KSBZXM entity) {
ksbzxmMapper.updateById(entity);
}
@Override
public KSBZXM getSubsidyProjectById(String bh) {
return ksbzxmMapper.selectById(bh);
}
@Override
public PageResult<KSBZXM> pageSubsidyProject(PageQuery query, KSBZXM cond) {
Page<KSBZXM> page = new Page<>(query.getPageNum(), query.getPageSize());
LambdaQueryWrapper<KSBZXM> wrapper = new LambdaQueryWrapper<>();
if (cond.getMc() != null) wrapper.like(KSBZXM::getMc, cond.getMc());
Page<KSBZXM> result = ksbzxmMapper.selectPage(page, wrapper);
return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize());
}
// ==================== 教学日志课时补助审批表(汇总) ====================
@Override
@Transactional
public void addSubsidyApproval(JXRZKSBZSPB entity) {
jxrzksbzspbMapper.insert(entity);
}
@Override
@Transactional
public void updateSubsidyApproval(JXRZKSBZSPB entity) {
jxrzksbzspbMapper.updateById(entity);
}
@Override
public JXRZKSBZSPB getSubsidyApprovalById(String bh) {
return jxrzksbzspbMapper.selectById(bh);
}
@Override
public PageResult<JXRZKSBZSPB> pageSubsidyApproval(PageQuery query, JXRZKSBZSPB cond) {
Page<JXRZKSBZSPB> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<JXRZKSBZSPB> result = jxrzksbzspbMapper.selectPageList(page, cond);
return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize());
}
@Override
@Transactional
public void calculateSummary(String bh) {
JXRZKSBZSPB summary = jxrzksbzspbMapper.selectById(bh);
if (summary == null) return;
LambdaQueryWrapper<JXRZJYKSBZ> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(JXRZJYKSBZ::getJxrzksbzspbh, bh);
List<JXRZJYKSBZ> details = jxrzjyksbzMapper.selectList(wrapper);
float totalKsl = 0, totalYzksl = 0;
BigDecimal totalAmount = BigDecimal.ZERO;
for (JXRZJYKSBZ d : details) {
if (d.getKsl() != null) totalKsl += d.getKsl();
if (d.getSfyz() != null && d.getSfyz() == 1 && d.getKsl() != null) totalYzksl += d.getKsl();
if (d.getKsbzje() != null) totalAmount = totalAmount.add(d.getKsbzje());
}
summary.setZksl(totalKsl);
summary.setYzksl(totalYzksl);
summary.setZbzje(totalAmount.floatValue());
jxrzksbzspbMapper.updateById(summary);
}
// ==================== 教学日志教员课时补助明细 ====================
@Override
public PageResult<JXRZJYKSBZ> pageSubsidyDetail(PageQuery query, JXRZJYKSBZ cond) {
Page<JXRZJYKSBZ> page = new Page<>(query.getPageNum(), query.getPageSize());
LambdaQueryWrapper<JXRZJYKSBZ> wrapper = new LambdaQueryWrapper<>();
if (cond.getJxrzksbzspbh() != null) wrapper.eq(JXRZJYKSBZ::getJxrzksbzspbh, cond.getJxrzksbzspbh());
if (cond.getJybh() != null) wrapper.eq(JXRZJYKSBZ::getJybh, cond.getJybh());
Page<JXRZJYKSBZ> result = jxrzjyksbzMapper.selectPage(page, wrapper);
return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize());
}
// ==================== 教员课时补助 ====================
@Override
@Transactional
public void addTeacherSubsidy(JYKSBZ entity) {
jyksbzMapper.insert(entity);
}
@Override
public PageResult<JYKSBZ> pageTeacherSubsidy(PageQuery query, JYKSBZ cond) {
Page<JYKSBZ> page = new Page<>(query.getPageNum(), query.getPageSize());
LambdaQueryWrapper<JYKSBZ> wrapper = new LambdaQueryWrapper<>();
if (cond.getJybh() != null) wrapper.eq(JYKSBZ::getJybh, cond.getJybh());
Page<JYKSBZ> result = jyksbzMapper.selectPage(page, wrapper);
return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize());
}
// ==================== 年终课时费 ====================
@Override
@Transactional
public void addYearEndFee(NZKSF entity) {
nzksfMapper.insert(entity);
}
@Override
@Transactional
public void updateYearEndFee(NZKSF entity) {
nzksfMapper.updateById(entity);
}
@Override
public NZKSF getYearEndFeeById(String bh) {
return nzksfMapper.selectById(bh);
}
@Override
public PageResult<NZKSF> pageYearEndFee(PageQuery query, NZKSF cond) {
Page<NZKSF> page = new Page<>(query.getPageNum(), query.getPageSize());
LambdaQueryWrapper<NZKSF> wrapper = new LambdaQueryWrapper<>();
if (cond.getMc() != null) wrapper.like(NZKSF::getMc, cond.getMc());
Page<NZKSF> result = nzksfMapper.selectPage(page, wrapper);
return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize());
}
// ==================== 教员年终课时费 ====================
@Override
public PageResult<JYNZKSF> pageTeacherYearEndFee(PageQuery query, JYNZKSF cond) {
Page<JYNZKSF> page = new Page<>(query.getPageNum(), query.getPageSize());
LambdaQueryWrapper<JYNZKSF> wrapper = new LambdaQueryWrapper<>();
if (cond.getJybh() != null) wrapper.eq(JYNZKSF::getJybh, cond.getJybh());
if (cond.getNzksfbh() != null) wrapper.eq(JYNZKSF::getNzksfbh, cond.getNzksfbh());
Page<JYNZKSF> result = jynzksfMapper.selectPage(page, wrapper);
return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize());
}
// ==================== 教师工作量 ====================
@Override
@Transactional
public void addWorkload(JZGZL entity) {
entity.setCjsj(LocalDateTime.now());
entity.setXgsj(LocalDateTime.now());
jzgzlMapper.insert(entity);
}
@Override
@Transactional
public void updateWorkload(JZGZL entity) {
entity.setXgsj(LocalDateTime.now());
jzgzlMapper.updateById(entity);
}
@Override
public JZGZL getWorkloadById(String bh) {
return jzgzlMapper.selectById(bh);
}
@Override
public PageResult<JZGZL> pageWorkload(PageQuery query, JZGZL cond) {
Page<JZGZL> page = new Page<>(query.getPageNum(), query.getPageSize());
LambdaQueryWrapper<JZGZL> wrapper = new LambdaQueryWrapper<>();
if (cond.getJybh() != null) wrapper.eq(JZGZL::getJybh, cond.getJybh());
if (cond.getNd() != null) wrapper.eq(JZGZL::getNd, cond.getNd());
if (cond.getXq() != null) wrapper.eq(JZGZL::getXq, cond.getXq());
Page<JZGZL> result = jzgzlMapper.selectPage(page, wrapper);
return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize());
}
@Override
@Transactional
public void calcCourseWorkload(String jybh, Integer nd, String xq) {
// 统计课时量作为工作量
LambdaQueryWrapper<BCKTJXRZSKJY> tw = new LambdaQueryWrapper<>();
tw.eq(BCKTJXRZSKJY::getFzjybh, jybh).eq(BCKTJXRZSKJY::getNd, nd);
long count = bcktjxrzskyMapper.selectCount(tw);
JZGZL workload = new JZGZL();
workload.setJybh(jybh);
workload.setNd(nd);
workload.setXq(xq);
workload.setKcjxgzl((float) count);
workload.setZgzl((float) count);
jzgzlMapper.insert(workload);
}
@Override
public PageResult<?> compareWorkload(PageQuery query, Integer nd, String xq) {
Page<JZGZL> page = new Page<>(query.getPageNum(), query.getPageSize());
LambdaQueryWrapper<JZGZL> wrapper = new LambdaQueryWrapper<>();
if (nd != null) wrapper.eq(JZGZL::getNd, nd);
if (xq != null) wrapper.eq(JZGZL::getXq, xq);
wrapper.orderByDesc(JZGZL::getZgzl);
Page<JZGZL> result = jzgzlMapper.selectPage(page, wrapper);
return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize());
}
// ==================== 教学实施计划统计 ====================
@Override
public PageResult<JXSSJHTJB> pageTeachingStats(PageQuery query, JXSSJHTJB cond) {
Page<JXSSJHTJB> page = new Page<>(query.getPageNum(), query.getPageSize());
LambdaQueryWrapper<JXSSJHTJB> wrapper = new LambdaQueryWrapper<>();
if (cond.getRq() != null) wrapper.ge(JXSSJHTJB::getRq, cond.getRq());
wrapper.orderByDesc(JXSSJHTJB::getRq);
Page<JXSSJHTJB> result = jxssjhtjbMapper.selectPage(page, wrapper);
return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize());
}
// ==================== 教员课时查询管理 ====================
@Override
public PageResult<TeacherHoursVO> pageTeacherHours(PageQuery query, TeacherHoursVO cond) {
Page<TeacherHoursVO> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<TeacherHoursVO> result = teacherHoursMapper.selectTeacherHoursPage(page, cond);
return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize());
}
}
@@ -0,0 +1,597 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementCreateDTO;
import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementIdDTO;
import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementPriorityDTO;
import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementPublishDTO;
import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementQueryDTO;
import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementRecipientInsertDTO;
import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementRecipientQueryDTO;
import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementUpdateDTO;
import com.roomroot.jwgl.dto.noticeannouncement.UserNoticeAnnouncementQueryDTO;
import com.roomroot.jwgl.mapper.NoticeAnnouncementMapper;
import com.roomroot.jwgl.service.NoticeAnnouncementService;
import com.roomroot.common.exception.ServiceException;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.utils.PageResultUtil;
import com.roomroot.jwgl.utils.UuidUtil;
import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementDetailVO;
import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementPublishResultVO;
import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementRecipientVO;
import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementStatisticsVO;
import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementVO;
import com.roomroot.jwgl.vo.noticeannouncement.UserNoticeAnnouncementDetailVO;
import com.roomroot.jwgl.vo.noticeannouncement.UserNoticeAnnouncementVO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import static com.roomroot.jwgl.utils.BasicManagementConstants.BAD_REQUEST;
import static com.roomroot.jwgl.utils.BasicManagementConstants.NOT_FOUND;
import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.RECIPIENT_INSERT_BATCH_SIZE;
import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.SCOPE_ALL;
import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.SCOPE_ALL_TEACHERS;
import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.STATUS_DOWN;
import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.STATUS_DRAFT;
import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.STATUS_PUBLISHED;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.completeDetail;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.completeStatistics;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.createRecipientRecords;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.normalizeCreate;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.normalizeNoticeId;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.normalizePriority;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.normalizePublish;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.normalizeQuery;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.normalizeRecipientAccountIds;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.normalizeRecipientQuery;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.normalizeUpdate;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.normalizeUserQuery;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.requireCurrentAccountId;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.requireCurrentSessionId;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.requireDeletableStatus;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.requireDownableStatus;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.requireEditableStatus;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.requireExistingStatus;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.requirePublishableStatus;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.resolveScopeName;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.startOfDay;
import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.startOfNextDay;
/**
* 通知公告管理服务实现。
*
* <p>当前类只编排公告业务流程;DTO 复制、固定校验、状态规则、
* 会话读取、接收人去重和统计计算统一由通知公告工具类处理。</p>
*/
@Service
public class NoticeAnnouncementServiceImpl
implements NoticeAnnouncementService {
/**
* 通知公告管理数据访问对象。
*/
@Resource
private NoticeAnnouncementMapper noticeAnnouncementMapper;
/**
* 分页查询后台通知公告。
*/
@Override
@Transactional(readOnly = true)
public PageResult<NoticeAnnouncementVO> list(
NoticeAnnouncementQueryDTO dto) {
// 第一步:规范化并校验后台分页筛选条件。
NoticeAnnouncementQueryDTO query =
normalizeQuery(dto);
// 第二步:把页面日期转换为数据库时间查询边界。
LocalDateTime publishStartTime =
startOfDay(query.getPublishStartDate());
LocalDateTime publishEndExclusiveTime =
startOfNextDay(query.getPublishEndDate());
// 第三步:创建 MyBatis-Plus 分页对象。
Page<NoticeAnnouncementVO> page =
new Page<>(
query.getPageNum().longValue(),
query.getPageSize().longValue());
// 第四步:执行公告筛选、接收阅读统计和稳定排序。
Page<NoticeAnnouncementVO> selectedPage =
noticeAnnouncementMapper
.selectNoticeAnnouncementPage(
page,
query,
publishStartTime,
publishEndExclusiveTime);
// 第五步:返回项目统一分页结构。
return PageResultUtil.fromPage(
selectedPage,
page,
query.getPageNum(),
query.getPageSize());
}
/**
* 查询通知公告后台详情。
*/
@Override
@Transactional(readOnly = true)
public NoticeAnnouncementDetailVO get(
NoticeAnnouncementIdDTO dto) {
// 第一步:规范化公告编号。
String id = normalizeNoticeId(dto);
// 第二步:查询并返回包含发布范围和统计信息的公告详情。
return loadDetail(id);
}
/**
* 新增一条拟制公告。
*/
@Override
@Transactional(rollbackFor = Exception.class)
public NoticeAnnouncementDetailVO add(
NoticeAnnouncementCreateDTO dto) {
// 第一步:规范化并校验公告可编辑内容。
NoticeAnnouncementCreateDTO notice =
normalizeCreate(dto);
// 第二步:由服务端生成公告编号和创建时间。
String id = UuidUtil.getOriginalUUID();
LocalDateTime now = LocalDateTime.now();
// 第三步:固定按照拟制状态写入公告,不信任客户端状态数据。
noticeAnnouncementMapper.insertNoticeAnnouncement(
id,
notice,
now,
STATUS_DRAFT);
// 第四步:重新查询并返回新增后的公告详情。
return loadDetail(id);
}
/**
* 修改拟制或已下架公告。
*/
@Override
@Transactional(rollbackFor = Exception.class)
public NoticeAnnouncementDetailVO update(
NoticeAnnouncementUpdateDTO dto) {
// 第一步:规范化并校验公告编号和可编辑内容。
NoticeAnnouncementUpdateDTO notice =
normalizeUpdate(dto);
// 第二步:锁定公告行并取得当前状态。
String status =
noticeAnnouncementMapper
.selectNoticeStatusForUpdate(
notice.getId());
// 第三步:只允许修改拟制或已下架公告。
requireEditableStatus(status);
// 第四步:更新公告内容,不改变状态和接收人快照。
noticeAnnouncementMapper
.updateNoticeAnnouncement(notice);
// 第五步:返回修改后的公告详情。
return loadDetail(notice.getId());
}
/**
* 发布公告并生成接收人快照。
*/
@Override
@Transactional(rollbackFor = Exception.class)
public NoticeAnnouncementPublishResultVO publish(
NoticeAnnouncementPublishDTO dto) {
// 第一步:规范化公告编号、发布范围和指定部别编号。
NoticeAnnouncementPublishDTO publish =
normalizePublish(dto);
// 第二步:锁定公告行并校验拟制或已下架状态允许发布。
String status =
noticeAnnouncementMapper
.selectNoticeStatusForUpdate(
publish.getId());
requirePublishableStatus(status);
/*
* 第三步:按照全校、全体教员或指定部别解析有效账户。
* 指定部别范围会同时校验部别是否存在且处于启用状态。
*/
List<String> accountIds =
resolveRecipientAccountIds(publish);
// 第四步:去除空账户和重复账户。
List<String> uniqueAccountIds =
normalizeRecipientAccountIds(accountIds);
if (uniqueAccountIds.isEmpty()) {
throw new ServiceException("当前发布范围内没有可接收公告的有效账户",
BAD_REQUEST);
}
// 第五步:为每个接收账户生成独立的接收人快照记录。
List<NoticeAnnouncementRecipientInsertDTO> recipients =
createRecipientRecords(uniqueAccountIds);
LocalDateTime publishTime = LocalDateTime.now();
/*
* 第六步:删除上一轮发布的接收人快照。
* 拟制公告通常没有旧快照,已下架公告重新发布时会替换旧快照。
*/
noticeAnnouncementMapper
.deleteNoticeRecipients(publish.getId());
// 第七步:分批写入本轮发布的全部接收人快照。
insertRecipientSnapshots(
publish.getId(),
recipients,
publishTime);
// 第八步:更新公告发布状态、发布时间、全体标记并重置阅读次数。
boolean all =
SCOPE_ALL.equals(publish.getScopeType());
noticeAnnouncementMapper.updateNoticePublishState(
publish.getId(),
STATUS_PUBLISHED,
publishTime,
all);
// 第九步:封装并返回本次发布结果。
NoticeAnnouncementPublishResultVO result =
new NoticeAnnouncementPublishResultVO();
result.setId(publish.getId());
result.setScopeType(publish.getScopeType());
result.setScopeName(
resolveScopeName(publish.getScopeType()));
result.setRecipientCount(
Integer.valueOf(uniqueAccountIds.size()));
result.setPublishTime(publishTime);
return result;
}
/**
* 下架已发布公告。
*/
@Override
@Transactional(rollbackFor = Exception.class)
public NoticeAnnouncementDetailVO down(
NoticeAnnouncementIdDTO dto) {
// 第一步:规范化公告编号。
String id = normalizeNoticeId(dto);
// 第二步:锁定公告行并校验当前状态必须为已发布。
String status =
noticeAnnouncementMapper
.selectNoticeStatusForUpdate(id);
requireDownableStatus(status);
// 第三步:把公告状态修改为已下架,保留本轮接收和阅读历史。
noticeAnnouncementMapper.updateNoticeStatus(
id, STATUS_DOWN);
// 第四步:返回下架后的公告详情。
return loadDetail(id);
}
/**
* 删除拟制或已下架公告。
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(NoticeAnnouncementIdDTO dto) {
// 第一步:规范化公告编号。
String id = normalizeNoticeId(dto);
// 第二步:锁定公告行并校验当前状态允许删除。
String status =
noticeAnnouncementMapper
.selectNoticeStatusForUpdate(id);
requireDeletableStatus(status);
// 第三步:先删除接收人快照,避免外键引用阻止公告删除。
noticeAnnouncementMapper
.deleteNoticeRecipients(id);
// 第四步:删除公告主体。
noticeAnnouncementMapper
.deleteNoticeAnnouncement(id);
}
/**
* 调整公告优先级。
*/
@Override
@Transactional(rollbackFor = Exception.class)
public NoticeAnnouncementDetailVO updatePriority(
NoticeAnnouncementPriorityDTO dto) {
// 第一步:规范化公告编号并校验优先级范围。
NoticeAnnouncementPriorityDTO priority =
normalizePriority(dto);
// 第二步:锁定公告行并确认公告存在。
String status =
noticeAnnouncementMapper
.selectNoticeStatusForUpdate(
priority.getId());
requireExistingStatus(status);
// 第三步:更新优先级,设置为0时取消置顶效果。
noticeAnnouncementMapper.updateNoticePriority(
priority.getId(),
priority.getPriority());
// 第四步:返回调整后的公告详情。
return loadDetail(priority.getId());
}
/**
* 分页查询公告接收人和阅读明细。
*/
@Override
@Transactional(readOnly = true)
public PageResult<NoticeAnnouncementRecipientVO>
recipients(
NoticeAnnouncementRecipientQueryDTO dto) {
// 第一步:规范化公告编号、分页和接收人筛选条件。
NoticeAnnouncementRecipientQueryDTO query =
normalizeRecipientQuery(dto);
// 第二步:确认待查询公告存在。
loadDetail(query.getId());
// 第三步:创建接收人明细分页对象。
Page<NoticeAnnouncementRecipientVO> page =
new Page<>(
query.getPageNum().longValue(),
query.getPageSize().longValue());
// 第四步:查询按账户去重后的接收和阅读明细。
Page<NoticeAnnouncementRecipientVO> selectedPage =
noticeAnnouncementMapper
.selectNoticeRecipientPage(
page, query);
// 第五步:返回项目统一分页结构。
return PageResultUtil.fromPage(
selectedPage,
page,
query.getPageNum(),
query.getPageSize());
}
/**
* 查询公告接收和阅读统计。
*/
@Override
@Transactional(readOnly = true)
public NoticeAnnouncementStatisticsVO statistics(
NoticeAnnouncementIdDTO dto) {
// 第一步:规范化公告编号。
String id = normalizeNoticeId(dto);
// 第二步:确认待统计公告存在。
loadDetail(id);
// 第三步:汇总唯一接收账户的接收和阅读人数。
NoticeAnnouncementStatisticsVO statistics =
noticeAnnouncementMapper
.selectNoticeStatistics(id);
// 第四步:补全未读人数和阅读率并返回。
return completeStatistics(statistics, id);
}
/**
* 分页查询当前登录账户可见的公告。
*/
@Override
@Transactional(readOnly = true)
public PageResult<UserNoticeAnnouncementVO> userList(
UserNoticeAnnouncementQueryDTO dto) {
// 第一步:规范化用户公告分页和筛选条件。
UserNoticeAnnouncementQueryDTO query =
normalizeUserQuery(dto);
// 第二步:只根据当前服务端会话查询真实账户编号。
String accountId = currentAccountId();
// 第三步:创建用户公告分页对象。
Page<UserNoticeAnnouncementVO> page =
new Page<>(
query.getPageNum().longValue(),
query.getPageSize().longValue());
/*
* 第四步:只查询当前账户具有接收快照且仍为已发布状态的公告,
* 客户端不能通过提交其他账户编号越权查询。
*/
Page<UserNoticeAnnouncementVO> selectedPage =
noticeAnnouncementMapper
.selectUserNoticePage(
page,
accountId,
query,
STATUS_PUBLISHED);
// 第五步:返回项目统一分页结构。
return PageResultUtil.fromPage(
selectedPage,
page,
query.getPageNum(),
query.getPageSize());
}
/**
* 当前登录账户打开公告并完成阅读记录。
*/
@Override
@Transactional(rollbackFor = Exception.class)
public UserNoticeAnnouncementDetailVO read(
NoticeAnnouncementIdDTO dto) {
// 第一步:规范化公告编号并识别当前真实账户。
String id = normalizeNoticeId(dto);
String accountId = currentAccountId();
/*
* 第二步:锁定当前账户具有接收快照的已发布公告。
* 公告不存在、已经下架或不属于当前账户时统一不返回公告内容。
*/
String status =
noticeAnnouncementMapper
.selectUserNoticeStatusForUpdate(
id,
accountId,
STATUS_PUBLISHED);
if (status == null) {
throw new ServiceException("通知公告不存在或当前账户无权查看",
NOT_FOUND);
}
// 第三步:首次打开时标记已读和首次阅读时间,重复打开不会覆盖时间。
LocalDateTime readTime = LocalDateTime.now();
noticeAnnouncementMapper.markNoticeRecipientRead(
id,
accountId,
readTime);
// 第四步:每次有效打开都增加公告累计阅读次数。
noticeAnnouncementMapper
.incrementNoticeReadCount(id);
// 第五步:重新查询并返回更新后的用户公告详情。
UserNoticeAnnouncementDetailVO detail =
noticeAnnouncementMapper
.selectUserNoticeDetail(
id,
accountId,
STATUS_PUBLISHED);
if (detail == null) {
throw new ServiceException("通知公告不存在或当前账户无权查看",
NOT_FOUND);
}
return detail;
}
/**
* 按发布范围解析有效接收账户。
*
* @param publish 已完成规范化的发布参数
* @return 数据库查询到的接收账户编号
*/
private List<String> resolveRecipientAccountIds(
NoticeAnnouncementPublishDTO publish) {
// 第一步:全校范围查询全部有效账户。
if (SCOPE_ALL.equals(publish.getScopeType())) {
return noticeAnnouncementMapper
.selectAllActiveAccountIds();
}
// 第二步:全体教员范围查询全部有效教员账户。
if (SCOPE_ALL_TEACHERS.equals(
publish.getScopeType())) {
return noticeAnnouncementMapper
.selectAllActiveTeacherAccountIds();
}
/*
* 第三步:指定部别范围先确认全部部别存在且启用,
* 防止部分无效编号被静默忽略后产生错误发布范围。
*/
List<String> departmentIds =
publish.getDepartmentIds();
int activeDepartmentCount =
noticeAnnouncementMapper
.countActiveDepartments(
departmentIds);
if (activeDepartmentCount
!= departmentIds.size()) {
throw new ServiceException("选择的部别不存在或已经停用",
BAD_REQUEST);
}
// 第四步:返回指定部别关联的机关人员、教员、教研室和教学班次账户。
return noticeAnnouncementMapper
.selectActiveAccountIdsByDepartments(
departmentIds);
}
/**
* 分批写入接收人快照。
*
* @param noticeId 公告编号
* @param recipients 全部接收人记录
* @param receivedTime 统一接收时间
*/
private void insertRecipientSnapshots(
String noticeId,
List<NoticeAnnouncementRecipientInsertDTO>
recipients,
LocalDateTime receivedTime) {
// 第一步:按照固定批次大小切分接收人记录。
for (int start = 0;
start < recipients.size();
start += RECIPIENT_INSERT_BATCH_SIZE) {
int end = Math.min(
start + RECIPIENT_INSERT_BATCH_SIZE,
recipients.size());
// 第二步:使用单条批量 INSERT SELECT 写入当前批次。
noticeAnnouncementMapper.insertNoticeRecipients(
noticeId,
recipients.subList(start, end),
receivedTime);
}
}
/**
* 根据当前服务端会话取得真实账户编号。
*
* @return 当前登录账户编号
*/
private String currentAccountId() {
// 第一步:只从当前 HTTP 请求读取服务端 SessionId。
String sessionId = requireCurrentSessionId();
// 第二步:通过在线记录和登录信息表查询真实账户编号。
String accountId =
noticeAnnouncementMapper
.selectAccountIdBySessionId(
sessionId);
// 第三步:校验并返回当前会话对应的有效账户编号。
return requireCurrentAccountId(accountId);
}
/**
* 查询并补全公告后台详情。
*
* @param id 公告编号
* @return 公告后台详情
*/
private NoticeAnnouncementDetailVO loadDetail(
String id) {
// 第一步:查询公告主体、发布范围和接收阅读统计。
NoticeAnnouncementDetailVO detail =
noticeAnnouncementMapper
.selectNoticeAnnouncementDetail(id);
// 第二步:公告不存在时返回明确的资源不存在错误。
if (detail == null) {
throw new ServiceException("通知公告不存在",
NOT_FOUND);
}
// 第三步:补全范围说明和空统计值后返回。
return completeDetail(detail);
}
}
@@ -0,0 +1,244 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.dto.notificationlog.NotificationLogExportDTO;
import com.roomroot.jwgl.dto.notificationlog.NotificationLogIdDTO;
import com.roomroot.jwgl.dto.notificationlog.NotificationLogQueryDTO;
import com.roomroot.jwgl.dto.notificationlog.NotificationLogRecipientQueryDTO;
import com.roomroot.jwgl.mapper.NotificationLogMapper;
import com.roomroot.jwgl.service.NotificationLogService;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.utils.PageResultUtil;
import com.roomroot.jwgl.vo.notificationlog.NotificationLogDetailVO;
import com.roomroot.jwgl.vo.notificationlog.NotificationLogRecipientVO;
import com.roomroot.jwgl.vo.notificationlog.NotificationLogStatisticsVO;
import com.roomroot.jwgl.vo.notificationlog.NotificationLogVO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jakarta.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.roomroot.jwgl.utils.NotificationLogUtil.completeDetail;
import static com.roomroot.jwgl.utils.NotificationLogUtil.completeRecords;
import static com.roomroot.jwgl.utils.NotificationLogUtil.completeStatistics;
import static com.roomroot.jwgl.utils.NotificationLogUtil.normalizeExportQuery;
import static com.roomroot.jwgl.utils.NotificationLogUtil.normalizeId;
import static com.roomroot.jwgl.utils.NotificationLogUtil.normalizeQuery;
import static com.roomroot.jwgl.utils.NotificationLogUtil.normalizeRecipientQuery;
/**
* 通知日志管理服务实现。
*
* <p>当前类只编排通知日志业务流程,查询参数规范化和统计字段补全
* 统一由通知日志工具类处理。</p>
*/
@Service
public class NotificationLogServiceImpl
implements NotificationLogService {
/**
* 通知日志管理数据访问对象。
*/
@Resource
private NotificationLogMapper notificationLogMapper;
/**
* 分页查询已经实际发送的通知日志。
*
* @param dto 通知日志查询条件
* @return 通知日志分页结果
*/
@Override
@Transactional(readOnly = true)
public PageResult<NotificationLogVO> list(
NotificationLogQueryDTO dto) {
// 第一步:规范化并校验分页、文本、时间和发送状态条件。
NotificationLogQueryDTO query =
normalizeQuery(dto);
// 第二步:创建 MyBatis-Plus 通知日志分页对象。
Page<NotificationLogVO> page =
new Page<>(
query.getPageNum().longValue(),
query.getPageSize().longValue());
// 第三步:查询已经产生接收记录的通知发送历史。
Page<NotificationLogVO> selectedPage =
notificationLogMapper
.selectNotificationLogPage(
page, query);
// 第四步:兼容 Mapper 返回 null,并确定用于读取分页总数的结果对象。
Page<NotificationLogVO> resultPage =
selectedPage == null ? page : selectedPage;
// 第五步:统一补全发送失败数、未读数、发送状态和阅读率。
List<NotificationLogVO> records =
completeRecords(resultPage.getRecords());
// 第六步:复用项目公共分页工具返回统一分页结构。
return PageResultUtil.fromRecords(
resultPage,
records,
query.getPageNum(),
query.getPageSize());
}
/**
* 根据通知编号查询单条发送日志详情。
*
* @param dto 通知日志编号参数
* @return 通知主体和完整发送、阅读统计
*/
@Override
@Transactional(readOnly = true)
public NotificationLogDetailVO get(
NotificationLogIdDTO dto) {
// 第一步:规范化并校验通知日志编号。
String id = normalizeId(dto);
// 第二步:查询已经实际产生接收记录的通知发送日志详情。
NotificationLogDetailVO detail =
notificationLogMapper
.selectNotificationLogDetail(id);
// 第三步:校验日志存在并补全发送失败数、未读数和阅读率。
return completeDetail(detail);
}
/**
* 分页查询指定通知的接收对象和阅读明细。
*
* @param dto 通知编号、分页和接收阅读筛选条件
* @return 按账户去重的接收与阅读明细
*/
@Override
@Transactional(readOnly = true)
public PageResult<NotificationLogRecipientVO> recipients(
NotificationLogRecipientQueryDTO dto) {
// 第一步:规范化并校验通知编号、分页和接收阅读筛选条件。
NotificationLogRecipientQueryDTO query =
normalizeRecipientQuery(dto);
/*
* 第二步:查询通知发送日志主体并校验存在性。
* 即使明细筛选后为空,也能区分“没有匹配接收人”和“日志不存在”。
*/
completeDetail(
notificationLogMapper
.selectNotificationLogDetail(
query.getId()));
// 第三步:创建 MyBatis-Plus 接收与阅读明细分页对象。
Page<NotificationLogRecipientVO> page =
new Page<>(
query.getPageNum().longValue(),
query.getPageSize().longValue());
// 第四步:按账户去重查询指定通知的接收和阅读明细。
Page<NotificationLogRecipientVO> selectedPage =
notificationLogMapper
.selectNotificationLogRecipientPage(
page, query);
// 第五步:兼容 Mapper 返回 null,并确定分页总数来源。
Page<NotificationLogRecipientVO> resultPage =
selectedPage == null ? page : selectedPage;
// 第六步:将异常空明细集合统一转换为空集合。
List<NotificationLogRecipientVO> selectedRecords =
resultPage.getRecords();
List<NotificationLogRecipientVO> records =
selectedRecords == null
? Collections
.<NotificationLogRecipientVO>emptyList()
: selectedRecords;
// 第七步:复用项目公共分页工具返回统一分页结构。
return PageResultUtil.fromRecords(
resultPage,
records,
query.getPageNum(),
query.getPageSize());
}
/**
* 按当前筛选条件汇总通知发送和阅读情况。
*
* @param dto 通知日志筛选条件
* @return 通知数量、发送结果和阅读情况汇总
*/
@Override
@Transactional(readOnly = true)
public NotificationLogStatisticsVO statistics(
NotificationLogExportDTO dto) {
// 第一步:将统计条件转换为与列表一致的规范化查询条件。
NotificationLogQueryDTO query =
normalizeExportQuery(dto);
// 第二步:在数据库中汇总符合条件的通知和接收阅读数量。
NotificationLogStatisticsVO statistics =
notificationLogMapper
.selectNotificationLogStatistics(
query);
// 第三步:统一补全失败数、未读数和汇总阅读率。
return completeStatistics(statistics);
}
/**
* 按当前筛选条件查询全部待导出的通知日志。
*
* @param dto 通知日志导出筛选条件
* @return 与列表筛选和排序规则一致的全部通知日志
*/
@Override
@Transactional(readOnly = true)
public List<NotificationLogVO> export(
NotificationLogExportDTO dto) {
// 第一步:将导出条件转换为固定批次的列表查询参数。
NotificationLogQueryDTO query =
normalizeExportQuery(dto);
// 第二步:复用列表业务查询第一批通知日志。
PageResult<NotificationLogVO> firstPage =
list(query);
// 第三步:创建独立导出集合并加入第一批有效记录。
List<NotificationLogVO> records =
new ArrayList<>();
if (firstPage.getRecords() != null) {
records.addAll(firstPage.getRecords());
}
// 第四步:读取总页数;没有后续批次时直接返回当前结果。
Integer pageCount = firstPage.getPages();
if (pageCount == null
|| pageCount.intValue() <= 1) {
return records;
}
/*
* 第五步:按照固定页码依次查询剩余日志。
* 列表接口使用发送时间和通知编号双重倒序,跨页顺序保持稳定。
*/
for (int pageNum = 2;
pageNum <= pageCount.intValue();
pageNum++) {
query.setPageNum(Integer.valueOf(pageNum));
PageResult<NotificationLogVO> currentPage =
list(query);
// 第六步:忽略异常空集合,按分页顺序追加有效日志。
if (currentPage.getRecords() != null) {
records.addAll(currentPage.getRecords());
}
}
// 第七步:返回与内部分页结果相互独立的完整导出集合。
return records;
}
}
@@ -0,0 +1,80 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.JYSRWS;
import com.roomroot.jwgl.mapper.OfficeTaskBookMapper;
import com.roomroot.jwgl.service.OfficeTaskBookService;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.utils.UuidUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jakarta.annotation.Resource;
import java.util.List;
/**
* 教研室任务书服务实现类
*/
@Service
public class OfficeTaskBookServiceImpl implements OfficeTaskBookService {
@Resource
private OfficeTaskBookMapper officeTaskBookMapper;
@Override
public void add(JYSRWS jysrws) {
String uuid = UuidUtil.getUUID();
jysrws.setBh(uuid);
jysrws.setDelFlag(0);
// 状态默认为未上报
jysrws.setZt("未上报");
officeTaskBookMapper.insert(jysrws);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void delete(String bh) {
JYSRWS jysrws = getByBh(bh);
if (jysrws != null) {
jysrws.setDelFlag(1);
officeTaskBookMapper.updateById(jysrws);
}
}
@Override
public void batchDelete(List<String> bhList) {
for (String bh : bhList) {
delete(bh);
}
}
@Override
public void update(JYSRWS jysrws) {
officeTaskBookMapper.updateById(jysrws);
}
@Override
public JYSRWS getByBh(String bh) {
return officeTaskBookMapper.selectById(bh);
}
@Override
public PageResult<JYSRWS> pageList(PageQuery query, JYSRWS jysrws) {
jysrws.setDelFlag(0);
Page<JYSRWS> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<JYSRWS> result = officeTaskBookMapper.selectPageList(page, jysrws);
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
@Override
public List<JYSRWS> listByJxrwbh(String jxrwbh) {
return officeTaskBookMapper.selectByJxrwbh(jxrwbh);
}
@Override
public List<JYSRWS> listByJysdh(String jysdh) {
return officeTaskBookMapper.selectByJysdh(jysdh);
}
}
@@ -0,0 +1,354 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.dto.operationlog.OperationLogExportDTO;
import com.roomroot.jwgl.dto.operationlog.OperationLogIdDTO;
import com.roomroot.jwgl.dto.operationlog.OperationLogQueryDTO;
import com.roomroot.jwgl.dto.operationlog.OperationLogRecordDTO;
import com.roomroot.jwgl.mapper.OperationLogMapper;
import com.roomroot.jwgl.service.OperationLogService;
import com.roomroot.common.exception.ServiceException;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.utils.PageResultUtil;
import com.roomroot.jwgl.utils.UuidUtil;
import com.roomroot.jwgl.vo.operationlog.OperationLogDetailVO;
import com.roomroot.jwgl.vo.operationlog.OperationLogModuleVO;
import com.roomroot.jwgl.vo.operationlog.OperationLogVO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.roomroot.jwgl.utils.BasicManagementConstants.BAD_REQUEST;
import static com.roomroot.jwgl.utils.BasicManagementConstants.CONTENT_KEYWORD_MAX_LENGTH;
import static com.roomroot.jwgl.utils.BasicManagementConstants.CONFLICT;
import static com.roomroot.jwgl.utils.BasicManagementConstants.IDENTIFIER_MAX_LENGTH;
import static com.roomroot.jwgl.utils.BasicManagementConstants.NOT_FOUND;
import static com.roomroot.jwgl.utils.BasicManagementConstants.OPERATION_CONTENT_MAX_LENGTH;
import static com.roomroot.jwgl.utils.BasicManagementConstants.OPERATION_LOG_EXPORT_BATCH_SIZE;
import static com.roomroot.jwgl.utils.BasicManagementConstants.OPERATION_URL_MAX_LENGTH;
import static com.roomroot.jwgl.utils.BasicManagementConstants.OPERATOR_KEYWORD_MAX_LENGTH;
import static com.roomroot.jwgl.utils.BasicManagementConstants.URL_KEYWORD_MAX_LENGTH;
import static com.roomroot.jwgl.utils.OperationLogUtil.listOperationLogModules;
import static com.roomroot.jwgl.utils.OperationLogUtil.optionalModule;
import static com.roomroot.jwgl.utils.OperationLogUtil.optionalText;
import static com.roomroot.jwgl.utils.OperationLogUtil.requiredText;
import static com.roomroot.jwgl.utils.OperationLogUtil.resolveModule;
/**
* 操作日志管理服务实现。
*
* <p>当前类统一编排分页查询、详情查询、导出和内部写入流程,
* 参数辅助处理集中维护在操作日志工具类中。</p>
*/
@Service
public class OperationLogServiceImpl implements OperationLogService {
/**
* 操作日志数据访问对象。
*/
@Resource
private OperationLogMapper operationLogMapper;
/**
* 分页查询操作日志。
*
* <p>处理顺序:校验分页和时间范围、规范化查询条件、
* 数据库联表筛选、识别模块名称、封装统一分页结果。</p>
*
* @param dto 查询条件
* @return 经过筛选和稳定排序的操作日志分页结果
*/
@Override
@Transactional(readOnly = true)
public PageResult<OperationLogVO> list(OperationLogQueryDTO dto) {
// 第一步:兼容业务层直接传入 null,并使用 DTO 中的默认分页参数。
OperationLogQueryDTO query =
dto == null ? new OperationLogQueryDTO() : dto;
// 第二步:校验页码,防止绕过控制器校验后生成无效分页语句。
Integer pageNum = query.getPageNum();
if (pageNum == null || pageNum.intValue() < 1) {
throw new ServiceException("页码最小为1", BAD_REQUEST);
}
// 第三步:校验每页记录数,保持与项目其他分页接口相同的上限。
Integer pageSize = query.getPageSize();
if (pageSize == null
|| pageSize.intValue() < 1
|| pageSize.intValue() > 100) {
throw new ServiceException("每页大小必须在1到100之间",
BAD_REQUEST);
}
// 第四步:读取时间范围并校验开始时间不能晚于结束时间。
LocalDateTime startTime = query.getStartTime();
LocalDateTime endTime = query.getEndTime();
if (startTime != null
&& endTime != null
&& startTime.isAfter(endTime)) {
throw new ServiceException("开始时间不能晚于结束时间",
BAD_REQUEST);
}
// 第五步:规范化操作人关键字,空字符串按照未填写处理。
String operatorKeyword = optionalText(
query.getOperatorKeyword(),
OPERATOR_KEYWORD_MAX_LENGTH,
"操作人关键字");
// 第六步:规范化并校验模块名称,只允许查询系统已识别的模块。
String module = optionalModule(query.getModule());
// 第七步:规范化操作内容关键字,防止业务层直接调用时绕过长度校验。
String contentKeyword = optionalText(
query.getContentKeyword(),
CONTENT_KEYWORD_MAX_LENGTH,
"操作内容关键字");
// 第八步:规范化请求地址关键字,空字符串按照未填写处理。
String urlKeyword = optionalText(
query.getUrlKeyword(),
URL_KEYWORD_MAX_LENGTH,
"请求地址关键字");
// 第九步:创建分页对象,由 MyBatis-Plus 生成达梦分页和总数语句。
Page<OperationLogVO> page =
new Page<>(pageNum.longValue(), pageSize.longValue());
/*
* 第十步:在数据库中完成三表关联、组合筛选和稳定排序。
* 操作人身份来源于登录记录关联账户,不接收客户端提交的账户编号。
*/
Page<OperationLogVO> selectedPage =
operationLogMapper.selectOperationLogPage(
page,
startTime,
endTime,
operatorKeyword,
module,
contentKeyword,
urlKeyword);
// 第十一步:兼容模拟测试或异常 Mapper 返回 null 分页对象的情况。
Page<OperationLogVO> resultPage =
selectedPage == null ? page : selectedPage;
// 第十二步:取得当前页记录,并将 null 集合统一转换为空集合。
List<OperationLogVO> selectedRecords = resultPage.getRecords();
List<OperationLogVO> records = selectedRecords == null
? Collections.<OperationLogVO>emptyList()
: selectedRecords;
/*
* 第十三步:根据每条日志的请求地址补充模块名称。
* 旧数据库没有模块字段,因此模块只用于页面展示和查询分类。
*/
for (OperationLogVO record : records) {
if (record != null) {
record.setModule(resolveModule(record.getUrl()));
}
}
// 第十四步:返回项目统一分页结构,总数由分页拦截器统计。
return PageResultUtil.fromRecords(
resultPage,
records,
pageNum,
pageSize);
}
/**
* 查询操作日志页面可使用的模块选项。
*
* @return 与模块筛选白名单保持一致的模块选项
*/
@Override
@Transactional(readOnly = true)
public List<OperationLogModuleVO> modules() {
// 调用操作日志工具类统一转换模块配置,业务层不重复维护固定选项。
return listOperationLogModules();
}
/**
* 根据编号查询操作日志详情。
*
* <p>处理顺序:校验并规范化编号、查询三表关联信息、
* 校验日志是否存在、根据请求地址补充业务模块名称。</p>
*
* @param dto 操作日志编号参数
* @return 操作日志详情
*/
@Override
@Transactional(readOnly = true)
public OperationLogDetailVO get(OperationLogIdDTO dto) {
// 第一步:防止业务层被直接调用时传入空 DTO。
if (dto == null) {
throw new ServiceException("操作日志编号不能为空",
BAD_REQUEST);
}
// 第二步:去除日志编号首尾空白,并将空文本视为未填写。
String id = dto.getId() == null
? null
: dto.getId().trim();
if (id == null || id.isEmpty()) {
throw new ServiceException("操作日志编号不能为空",
BAD_REQUEST);
}
/*
* 第三步:使用公共标识符长度常量校验编号。
* 固定长度统一维护在常量工具类中,不在业务实现类重复声明。
*/
if (id.length() > IDENTIFIER_MAX_LENGTH) {
throw new ServiceException("操作日志编号格式不正确",
BAD_REQUEST);
}
// 第四步:根据日志编号联查操作日志、登录记录和账户信息。
OperationLogDetailVO detail =
operationLogMapper.selectOperationLogById(id);
// 第五步:日志不存在时返回明确的业务错误,不返回空成功结果。
if (detail == null) {
throw new ServiceException("操作日志不存在",
NOT_FOUND);
}
/*
* 第六步:旧操作日志表没有模块字段,
* 因此根据请求地址补充与列表接口一致的模块名称。
*/
detail.setModule(resolveModule(detail.getUrl()));
// 第七步:返回完成关联查询和模块补充的操作日志详情。
return detail;
}
/**
* 按筛选条件查询全部待导出的操作日志。
*
* <p>处理顺序:转换导出条件、按照列表接口允许的最大页大小分批查询、
* 汇总全部页面记录,并保持与页面列表完全一致的筛选和排序结果。</p>
*
* @param dto 导出筛选条件
* @return 全部符合条件的操作日志
*/
@Override
@Transactional(readOnly = true)
public List<OperationLogVO> export(OperationLogExportDTO dto) {
// 第一步:兼容业务层直接传入 null,并将其视为导出全部操作日志。
OperationLogExportDTO export =
dto == null ? new OperationLogExportDTO() : dto;
/*
* 第二步:创建列表查询 DTO,只复制导出允许使用的筛选条件。
* 导出接口不接收客户端分页参数,分页过程完全由服务端控制。
*/
OperationLogQueryDTO query = new OperationLogQueryDTO();
query.setStartTime(export.getStartTime());
query.setEndTime(export.getEndTime());
query.setOperatorKeyword(export.getOperatorKeyword());
query.setModule(export.getModule());
query.setContentKeyword(export.getContentKeyword());
query.setUrlKeyword(export.getUrlKeyword());
query.setPageNum(Integer.valueOf(1));
query.setPageSize(
Integer.valueOf(OPERATION_LOG_EXPORT_BATCH_SIZE));
/*
* 第三步:先查询第一页。
* 该调用会复用已有的时间、文本、模块校验以及数据库筛选和稳定排序规则。
*/
PageResult<OperationLogVO> firstPage = list(query);
// 第四步:创建独立导出集合,并加入第一页返回的日志记录。
List<OperationLogVO> exportedLogs = new ArrayList<>();
if (firstPage.getRecords() != null) {
exportedLogs.addAll(firstPage.getRecords());
}
// 第五步:读取总页数;没有更多页面时直接返回第一页汇总结果。
Integer pageCount = firstPage.getPages();
if (pageCount == null || pageCount.intValue() <= 1) {
return exportedLogs;
}
/*
* 第六步:按照固定页码依次查询剩余记录。
* 列表查询使用操作时间和日志编号双重倒序,可以保证跨页顺序稳定。
*/
for (int pageNum = 2;
pageNum <= pageCount.intValue();
pageNum++) {
query.setPageNum(Integer.valueOf(pageNum));
PageResult<OperationLogVO> currentPage = list(query);
// 第七步:忽略异常空集合,并按查询页顺序追加有效日志。
if (currentPage.getRecords() != null) {
exportedLogs.addAll(currentPage.getRecords());
}
}
// 第八步:返回与内部分页结果集合相互独立的完整导出数据。
return exportedLogs;
}
/**
* 写入一条由服务端自动采集的操作日志。
*
* <p>当前小功能只建立内部写入能力,登录记录编号由后续统一请求记录流程
* 从服务端会话取得,不提供客户端写入接口。</p>
*
* @param dto 当前登录记录编号、操作内容和请求地址
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void record(OperationLogRecordDTO dto) {
// 第一步:防止内部调用方传入空 DTO,避免产生缺少审计上下文的日志。
if (dto == null) {
throw new ServiceException("操作日志记录参数不能为空",
BAD_REQUEST);
}
// 第二步:校验并规范化当前请求关联的登录记录编号。
String loginRecordId = requiredText(
dto.getLoginRecordId(),
IDENTIFIER_MAX_LENGTH,
"登录记录编号");
// 第三步:校验并规范化服务端生成的业务操作内容。
String operationContent = requiredText(
dto.getOperationContent(),
OPERATION_CONTENT_MAX_LENGTH,
"操作内容");
// 第四步:校验并规范化当前请求的应用内访问地址。
String url = requiredText(
dto.getUrl(),
OPERATION_URL_MAX_LENGTH,
"请求地址");
/*
* 第五步:由服务端生成日志编号和操作时间并写入数据库。
* 调用方不能指定编号或时间,避免审计记录被伪造或覆盖。
*/
int affectedRows = operationLogMapper.insertOperationLog(
UuidUtil.getOriginalUUID(),
loginRecordId,
LocalDateTime.now(),
operationContent,
url);
// 第六步:数据库未写入唯一记录时返回明确冲突错误。
if (affectedRows != 1) {
throw new ServiceException("操作日志记录失败",
CONFLICT);
}
}
}
@@ -0,0 +1,200 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.XQXLB;
import com.roomroot.jwgl.mapper.SemesterCalendarMapper;
import com.roomroot.jwgl.service.SemesterCalendarService;
import com.roomroot.jwgl.utils.DateCourseUtil;
import com.roomroot.jwgl.utils.UuidUtil;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.Resource;
import java.io.InputStream;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 学期校历表服务实现类
*/
@Service
public class SemesterCalendarServiceImpl implements SemesterCalendarService {
@Resource
private SemesterCalendarMapper xqxlbMapper;
@Override
public void add(String startDate,String endDateb) {
Map<String, Object> dateWeekAndCourseMap = DateCourseUtil.getDateWeekAndCourseMap(startDate, endDateb);
for (Map.Entry<String, Object> entry : dateWeekAndCourseMap.entrySet()) {
String dateStr = entry.getKey();
Page<XQXLB> page = new Page<>(0, 10);
XQXLB query = new XQXLB();
//将日期分为年度和月日
LocalDate date = LocalDate.parse(dateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
int nd = date.getYear();
String md = String.format("%02d%02d", date.getMonthValue(), date.getDayOfMonth());
query.setJqsj(md);
query.setNd(nd);
Page<XQXLB> result = xqxlbMapper.selectPageList(page, query);
if(result.getTotal() > 0){
continue;
}
Map<String,Object> map = (Map<String, Object>) entry.getValue();
Object course = map.get("course");
if (course instanceof ArrayList<?>) {
ArrayList<String> list = (ArrayList<String>) course;
for (int i = 0; i < list.size(); i++) {
String courseClass = list.get(i);
XQXLB xqxlb = new XQXLB();
//编号,用时间戳
String bh = UuidUtil.getUUID();
xqxlb.setBh(bh);
//假期时间
xqxlb.setJqsj(dateStr);
//年度
xqxlb.setNd(Integer.valueOf(nd));
//节次
xqxlb.setCourseClass(courseClass);
xqxlb.setKpk(true);
xqxlb.setBzxs(true);
xqxlb.setZdpk(true);
xqxlbMapper.insert(xqxlb);
}
}
}
}
@Override
public void delete(String bh) {
xqxlbMapper.deleteById(bh);
}
@Override
public void update(XQXLB xqxlb) {
xqxlbMapper.updateById(xqxlb);
}
@Override
public XQXLB getById(String bh) {
return xqxlbMapper.selectById(bh);
}
@Override
public List<XQXLB> listByNd(Integer nd) {
LambdaQueryWrapper<XQXLB> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(XQXLB::getNd, nd);
return xqxlbMapper.selectList(wrapper);
}
@Override
public int importExcel(MultipartFile file) {
try (InputStream is = file.getInputStream();
Workbook workbook = file.getOriginalFilename().endsWith(".xlsx")
? new XSSFWorkbook(is) : new HSSFWorkbook(is)) {
Sheet sheet = workbook.getSheetAt(0);
int count = 0;
// 从第二行开始读取数据(跳过表头)
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row == null) continue;
XQXLB xqxlb = new XQXLB();
// 编号
xqxlb.setBh(UuidUtil.getUUID());
// 年度
String nd = getCellValue(row, 0);
if (nd != null && !nd.isEmpty()) {
xqxlb.setNd(Integer.parseInt(nd));
}
// 假期时间
String jqsj = getCellValue(row, 1);
xqxlb.setJqsj(jqsj);
Page<XQXLB> page = new Page<>(0, 10);
Page<XQXLB> result = xqxlbMapper.selectPageList(page, xqxlb);
if(result.getTotal() > 0){
continue;
}
// 假期名称
xqxlb.setJqmc(getCellValue(row, 2));
// 加粗
String jc = getCellValue(row, 3);
xqxlb.setJc(jc != null && "是".equals(jc));
// 备注
xqxlb.setBz(getCellValue(row, 4));
// 可排课
String kpk = getCellValue(row, 5);
xqxlb.setKpk("是".equals(kpk));
// 备注显示
String bzxs = getCellValue(row, 6);
xqxlb.setBzxs("是".equals(bzxs));
// 自动排课
String zdpk = getCellValue(row, 7);
xqxlb.setZdpk("是".equals(zdpk));
// 节次
xqxlb.setCourseClass(getCellValue(row, 8));
xqxlbMapper.insert(xqxlb);
count++;
}
return count;
} catch (Exception e) {
throw new RuntimeException("Excel解析失败: " + e.getMessage(), e);
}
}
/**
* 获取单元格字符串值
*/
private String getCellValue(Row row, int index) {
Cell cell = row.getCell(index, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
if (cell == null) {
return null;
}
CellType type = cell.getCellType();
// 处理公式单元格
if (type == CellType.FORMULA) {
type = cell.getCachedFormulaResultType();
}
switch (type) {
case NUMERIC:
double num = cell.getNumericCellValue();
// 整数就去掉小数点后多余的 .0
if (num == Math.floor(num)) {
return String.valueOf((long) num);
} else {
return String.valueOf(num);
}
case STRING:
return cell.getStringCellValue().trim();
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
default:
return null;
}
}
}
@@ -0,0 +1,91 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.roomroot.jwgl.entity.XQ;
import com.roomroot.jwgl.mapper.SemesterMapper;
import com.roomroot.jwgl.service.SemesterCalendarService;
import com.roomroot.jwgl.service.SemesterService;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
* 学期服务实现类
*/
@Service
public class SemesterServiceImpl extends ServiceImpl<SemesterMapper, XQ> implements SemesterService {
// 定义格式化器,静态常量只初始化一次
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@Resource
private SemesterMapper semesterMapper;
@Resource
SemesterCalendarService semesterCalendarService;
@Override
public void add(XQ xq) {
xq.setDelFlag(0);
xq.setTkbsp(true);
int insert = semesterMapper.insert(xq);
if(insert > 0){
LocalDateTime kxrq = xq.getKxrq();
LocalDateTime jsrq = xq.getJsrq();
String startDate = localDateTimeToDateStr(kxrq);
String endDate = localDateTimeToDateStr(jsrq);
semesterCalendarService.add(startDate, endDate);
}
}
/**
* LocalDateTime 转为 yyyy-MM-dd 日期字符串
* @param dateTime 日期时间对象
* @return 格式:2026-07-22,入参null返回空串
*/
public static String localDateTimeToDateStr(LocalDateTime dateTime) {
if (dateTime == null) {
return "";
}
return dateTime.format(DATE_FORMATTER);
}
@Override
public void delete(Integer nd) {
semesterMapper.deleteById(nd);
}
@Override
public void update(XQ xq) {
semesterMapper.updateById(xq);
}
@Override
public XQ getByNd(Integer nd) {
return semesterMapper.selectById(nd);
}
@Override
public PageResult<XQ> pageList(PageQuery query, XQ xq) {
Page<XQ> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<XQ> result = semesterMapper.selectPageList(page, xq);
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
@Override
public List<XQ> list() {
LambdaQueryWrapper<XQ> wrapper = new LambdaQueryWrapper<>();
wrapper.orderByDesc(XQ::getNd);
return semesterMapper.selectList(wrapper);
}
}
@@ -0,0 +1,197 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.XYXX;
import com.roomroot.jwgl.entity.XYKCCJ;
import com.roomroot.jwgl.entity.XYKCGCCJ;
import com.roomroot.jwgl.mapper.XYXXMapper;
import com.roomroot.jwgl.mapper.XYKCCJMapper;
import com.roomroot.jwgl.mapper.XYKCGCCJMapper;
import com.roomroot.jwgl.service.StudentRecordsService;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jakarta.annotation.Resource;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
/**
* 学员档案管理服务实现类
*/
@Service
public class StudentRecordsServiceImpl implements StudentRecordsService {
@Resource
private XYXXMapper xyxxMapper;
@Resource
private XYKCCJMapper xykccjMapper;
@Resource
private XYKCGCCJMapper xykgccjMapper;
@Override
@Transactional
public void add(XYXX xyxx) {
xyxxMapper.insert(xyxx);
}
@Override
@Transactional
public void delete(String bh) {
XYXX xyxx = new XYXX();
xyxx.setBh(bh);
xyxx.setTxzt("退学");
xyxxMapper.updateById(xyxx);
}
@Override
@Transactional
public void update(XYXX xyxx) {
xyxxMapper.updateById(xyxx);
}
@Override
public XYXX getById(String bh) {
return xyxxMapper.selectById(bh);
}
@Override
public PageResult<XYXX> pageList(PageQuery query, XYXX xyxx) {
Page<XYXX> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<XYXX> result = xyxxMapper.selectPageList(page, xyxx);
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
@Override
@Transactional
public void retainGrade(String bh) {
XYXX xyxx = new XYXX();
xyxx.setBh(bh);
xyxx.setLjzt("留级");
xyxxMapper.updateById(xyxx);
}
@Override
@Transactional
public void downgrade(String bh) {
XYXX xyxx = new XYXX();
xyxx.setBh(bh);
xyxx.setLjzt("降级");
xyxxMapper.updateById(xyxx);
}
@Override
@Transactional
public void dropOut(String bh) {
XYXX xyxx = new XYXX();
xyxx.setBh(bh);
xyxx.setTxzt("退学");
xyxxMapper.updateById(xyxx);
}
// ========== 学员课程成绩查询 ==========
@Override
public List<XYKCCJ> getCourseGrades(String xybh) {
return xykccjMapper.selectByStudentId(xybh);
}
@Override
public List<XYKCGCCJ> getCourseProcessGrades(String xybh) {
return xykgccjMapper.selectByStudentId(xybh);
}
// ========== Excel 导出 ==========
@Override
public void exportGradesExcel(String xybh, HttpServletResponse response) {
XYXX student = xyxxMapper.selectById(xybh);
if (student == null) {
throw new RuntimeException("学员不存在");
}
List<XYKCCJ> grades = xykccjMapper.selectByStudentId(xybh);
try (Workbook workbook = new XSSFWorkbook()) {
Sheet sheet = workbook.createSheet("课程成绩");
// 标题行样式
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerFont.setFontHeightInPoints((short) 12);
CellStyle headerStyle = workbook.createCellStyle();
headerStyle.setFont(headerFont);
headerStyle.setAlignment(HorizontalAlignment.CENTER);
headerStyle.setBorderBottom(BorderStyle.THIN);
headerStyle.setBorderTop(BorderStyle.THIN);
headerStyle.setBorderLeft(BorderStyle.THIN);
headerStyle.setBorderRight(BorderStyle.THIN);
CellStyle dataStyle = workbook.createCellStyle();
dataStyle.setAlignment(HorizontalAlignment.CENTER);
dataStyle.setBorderBottom(BorderStyle.THIN);
dataStyle.setBorderTop(BorderStyle.THIN);
dataStyle.setBorderLeft(BorderStyle.THIN);
dataStyle.setBorderRight(BorderStyle.THIN);
// 学员信息行
Row infoRow = sheet.createRow(0);
infoRow.createCell(0).setCellValue("学员:" + student.getXm());
infoRow.createCell(1).setCellValue("学号:" + student.getXh());
// 表头
String[] headers = {"年度", "科目编号", "考试成绩", "平时成绩", "最终成绩",
"补考成绩", "补考次数", "考试情况", "已通过", "备注"};
Row headerRow = sheet.createRow(2);
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
cell.setCellStyle(headerStyle);
}
// 数据行
int rowIdx = 3;
for (XYKCCJ g : grades) {
Row row = sheet.createRow(rowIdx++);
row.createCell(0).setCellValue(g.getNd() != null ? g.getNd().toString() : "");
row.createCell(1).setCellValue(g.getKmbh() != null ? g.getKmbh() : "");
row.createCell(2).setCellValue(g.getKscj() != null ? g.getKscj().toString() : "");
row.createCell(3).setCellValue(g.getPscj() != null ? g.getPscj().toString() : "");
row.createCell(4).setCellValue(g.getZzcj() != null ? g.getZzcj().toString() : "");
row.createCell(5).setCellValue(g.getBkcj() != null ? g.getBkcj().toString() : "");
row.createCell(6).setCellValue(g.getBkcs() != null ? g.getBkcs().toString() : "");
row.createCell(7).setCellValue(g.getKsqk() != null ? g.getKsqk() : "");
row.createCell(8).setCellValue(g.getYtg() != null && g.getYtg() == 1 ? "是" : "否");
row.createCell(9).setCellValue(g.getBz() != null ? g.getBz() : "");
for (int i = 0; i < 10; i++) {
row.getCell(i).setCellStyle(dataStyle);
}
}
// 自动列宽
for (int i = 0; i < headers.length; i++) {
sheet.autoSizeColumn(i);
}
// 输出
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition",
"attachment; filename=" + URLEncoder.encode(
student.getXm() + "_课程成绩.xlsx", "UTF-8"));
workbook.write(response.getOutputStream());
response.getOutputStream().flush();
} catch (IOException e) {
throw new RuntimeException("导出 Excel 失败", e);
}
}
}
@@ -0,0 +1,62 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.XYDSCKCB;
import com.roomroot.jwgl.mapper.StudentTeamOutputScheduleMapper;
import com.roomroot.jwgl.service.StudentTeamOutputScheduleService;
import com.roomroot.jwgl.utils.UuidUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 学员队输出课程表 Service实现
*/
@Service
public class StudentTeamOutputScheduleServiceImpl implements StudentTeamOutputScheduleService {
@Autowired
private StudentTeamOutputScheduleMapper xydsckcbMapper;
@Override
public void add(XYDSCKCB xydsckcb) {
xydsckcb.setBh(UuidUtil.getUUID());
xydsckcb.setDelFlag(0);
xydsckcbMapper.insert(xydsckcb);
}
@Override
public void delete(String bh) {
xydsckcbMapper.deleteById(bh);
}
@Override
public void update(XYDSCKCB xydsckcb) {
xydsckcbMapper.updateById(xydsckcb);
}
@Override
public XYDSCKCB getByBh(String bh) {
return xydsckcbMapper.selectByBh(bh);
}
@Override
public XYDSCKCB getByNdAndXydbh(Integer nd, String xydbh) {
return xydsckcbMapper.selectByNdAndXydbh(nd, xydbh);
}
@Override
public Page<XYDSCKCB> list(int pageNum, int pageSize, XYDSCKCB xydsckcb) {
Page<XYDSCKCB> page = new Page<>(pageNum, pageSize);
return xydsckcbMapper.selectPageList(page, xydsckcb);
}
@Override
public List<XYDSCKCB> all() {
LambdaQueryWrapper<XYDSCKCB> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(XYDSCKCB::getDelFlag, 0);
return xydsckcbMapper.selectList(wrapper);
}
}
@@ -0,0 +1,239 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.XYDB;
import com.roomroot.jwgl.entity.XYDNDXQJBXXB;
import com.roomroot.jwgl.entity.XYDRWB;
import com.roomroot.jwgl.entity.ZYJXJHB;
import com.roomroot.jwgl.mapper.ClassSemesterMapper;
import com.roomroot.jwgl.mapper.StudentTeamTaskMapper;
import com.roomroot.jwgl.mapper.XYDBMapper;
import com.roomroot.jwgl.mapper.ZYJXJHBMapper;
import com.roomroot.jwgl.service.StudentTeamTaskService;
import com.roomroot.jwgl.utils.UuidUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
* 学员队任务表 Service实现
*/
@Service
public class StudentTeamTaskServiceImpl implements StudentTeamTaskService {
@Autowired
private StudentTeamTaskMapper xydrwbMapper;
@Autowired
private ClassSemesterMapper classSemesterMapper;
@Autowired
private XYDBMapper xydbMapper;
@Autowired
private ZYJXJHBMapper zyjxjhbMapper;
@Override
public void add(XYDRWB xydrwb) {
xydrwb.setBh(UuidUtil.getUUID());
xydrwb.setDelFlag(0);
xydrwbMapper.insert(xydrwb);
}
@Override
public void delete(String bh) {
xydrwbMapper.deleteById(bh);
}
@Override
public void update(XYDRWB xydrwb) {
xydrwbMapper.updateById(xydrwb);
}
@Override
public XYDRWB getByBh(String bh) {
return xydrwbMapper.selectById(bh);
}
@Override
public Page<XYDRWB> list(int pageNum, int pageSize, XYDRWB xydrwb) {
Page<XYDRWB> page = new Page<>(pageNum, pageSize);
return xydrwbMapper.selectPageList(page, xydrwb);
}
@Override
public Page<XYDRWB> groupList(int pageNum, int pageSize, XYDRWB xydrwb) {
Page<XYDRWB> page = new Page<>(pageNum, pageSize);
return xydrwbMapper.selectPageListGroup(page, xydrwb);
}
@Transactional(rollbackFor = Exception.class)
@Override
public int batchPresetMerge(List<String> bhList) {
if (CollectionUtils.isEmpty(bhList)) {
return 0;
}
// 查询所有选中的记录
List<XYDRWB> tasks = bhList.stream()
.map(bh -> xydrwbMapper.selectById(bh))
.filter(task -> task != null)
.sorted(Comparator.comparing(XYDRWB::getNd, Comparator.nullsLast(Comparator.naturalOrder())))
.collect(Collectors.toList());
if (tasks.isEmpty()) {
return 0;
}
// 获取第一条记录的年度,查询该年度的最大课次序号
Integer nd = tasks.get(0).getNd();
Integer maxKcxh = xydrwbMapper.selectMaxKcxhByNd(nd);
int newKcxh = (maxKcxh == null ? 0 : maxKcxh) + 1;
// 为所有记录设置统一的课次序号
int count = 0;
for (XYDRWB task : tasks) {
task.setKcxh(newKcxh);
xydrwbMapper.updateById(task);
count++;
}
return count;
}
@Transactional(rollbackFor = Exception.class)
@Override
public int batchPresetSplit(List<String> bhList) {
if (CollectionUtils.isEmpty(bhList)) {
return 0;
}
int count = 0;
for (String bh : bhList) {
XYDRWB task = xydrwbMapper.selectById(bh);
if (task != null) {
LambdaUpdateWrapper<XYDRWB> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(XYDRWB::getBh, bh)
.set(XYDRWB::getKcxh, null);
xydrwbMapper.update(null, wrapper);
count++;
}
}
return count;
}
@Override
public List<XYDRWB> all() {
LambdaQueryWrapper<XYDRWB> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(XYDRWB::getDelFlag, 0);
return xydrwbMapper.selectList(wrapper);
}
@Transactional(rollbackFor = Exception.class)
@Override
public int autoGenerateRequiredCourses(String xydbh, Integer xqdc) {
// 1. 根据学员队编号查询学员队表,获取专业代号
XYDB xydb = xydbMapper.selectById(xydbh);
if (xydb == null || xydb.getZydh() == null) {
return 0;
}
String zydh = xydb.getZydh();
// 2. 根据专业代号和学期第次查询专业教学计划表(停用=0)
LambdaQueryWrapper<ZYJXJHB> planWrapper = new LambdaQueryWrapper<>();
planWrapper.eq(ZYJXJHB::getZydh, zydh)
.eq(ZYJXJHB::getXqdc, xqdc)
.eq(ZYJXJHB::getTy, 0);
List<ZYJXJHB> planList = zyjxjhbMapper.selectList(planWrapper);
if (CollectionUtils.isEmpty(planList)) {
return 0;
}
// 3. 根据学员队编号和学期第次查询学员队期表,获取年度和学员队学期编号
LambdaQueryWrapper<XYDNDXQJBXXB> semesterWrapper = new LambdaQueryWrapper<>();
semesterWrapper.eq(XYDNDXQJBXXB::getXydbh, xydbh)
.eq(XYDNDXQJBXXB::getXqdc, xqdc)
.eq(XYDNDXQJBXXB::getDelFlag, 0)
.orderByDesc(XYDNDXQJBXXB::getNd)
.last("LIMIT 1");
XYDNDXQJBXXB semester = classSemesterMapper.selectOne(semesterWrapper);
if (semester == null) {
return 0;
}
Integer nd = semester.getNd();
String xydxqbh = semester.getBh();
// 4. 查询当前班次已存在的课程编号集合
LambdaQueryWrapper<XYDRWB> existWrapper = new LambdaQueryWrapper<>();
existWrapper.eq(XYDRWB::getXydxqbh, xydxqbh)
.eq(XYDRWB::getDelFlag, 0);
List<XYDRWB> existList = xydrwbMapper.selectList(existWrapper);
List<String> existKbhs = existList.stream()
.map(XYDRWB::getKbh)
.filter(kbh -> kbh != null)
.collect(Collectors.toList());
// 5. 只添加缺少的课程
int count = 0;
for (ZYJXJHB plan : planList) {
if (existKbhs.contains(plan.getKbh())) {
continue;
}
XYDRWB newRecord = new XYDRWB();
newRecord.setBh(UuidUtil.getUUID());
newRecord.setNd(nd);
newRecord.setXydbh(xydbh);
newRecord.setKbh(plan.getKbh());
newRecord.setXqdc(xqdc);
newRecord.setKlx(plan.getKlx());
newRecord.setXs(plan.getXs());
newRecord.setJc(plan.getJc());
newRecord.setZks(plan.getZks());
newRecord.setXf(plan.getXf());
newRecord.setKsks(plan.getKsks());
newRecord.setCjfz(plan.getCjfz());
newRecord.setBjrxypjf(plan.getBjrxypjf());
newRecord.setLlxs(plan.getLlxs());
newRecord.setSjxs(plan.getSjxs());
newRecord.setKsksbxs(plan.getKsksbxs());
newRecord.setJysdh(plan.getJysdh());
newRecord.setXydxqbh(xydxqbh);
newRecord.setJhkcxh(count+1);
newRecord.setXhbs(count+1);
newRecord.setBz2(0);
newRecord.setPdtbykxxbz(1);
newRecord.setPdqyzdyxs(0);
newRecord.setDelFlag(0);
xydrwbMapper.insert(newRecord);
count++;
}
return count;
}
@Override
public List<String> copy(List<String> bhList, String newXydbh) {
List<String> newBhList = new java.util.ArrayList<>();
for (String bh : bhList) {
XYDRWB source = xydrwbMapper.selectById(bh);
if (source == null) {
continue;
}
String newBh = UuidUtil.getUUID();
source.setBh(newBh);
source.setXydbh(newXydbh);
source.setDelFlag(0);
xydrwbMapper.insert(source);
newBhList.add(newBh);
}
return newBhList;
}
}
@@ -0,0 +1,374 @@
package com.roomroot.jwgl.service.impl;
import com.roomroot.jwgl.dto.systeminitialization.*;
import com.roomroot.jwgl.mapper.SystemInitializationMapper;
import com.roomroot.jwgl.service.SystemInitializationService;
import com.roomroot.jwgl.unit.BusinessException;
import com.roomroot.jwgl.utils.UuidUtil;
import com.roomroot.jwgl.vo.systeminitialization.*;
import com.roomroot.jwgl.vo.systeminitialization.*;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import static com.roomroot.jwgl.utils.BasicManagementConstants.CONFLICT;
import static com.roomroot.jwgl.utils.BasicManagementConstants.NOT_FOUND;
import static com.roomroot.jwgl.utils.SystemInitializationUtil.*;
/**
* 系统初始化管理服务实现。
*
* <p>当前实现只编排系统初始化业务流程,参数校验、Excel 解析、
* 历史数据引用检查和返回对象组装统一由工具类负责。</p>
*/
@Service
public class SystemInitializationServiceImpl
implements SystemInitializationService {
/**
* 系统初始化数据访问对象。
*/
@Resource
private SystemInitializationMapper systemInitializationMapper;
/**
* 查询当前生效的系统基础参数。
*
* @return 当前系统基础参数
*/
@Override
@Transactional(readOnly = true)
public SystemInitializationConfigurationVO
getCurrentConfiguration() {
// 第一步:按照系统版本表最大编号查询当前生效的基础参数。
SystemInitializationConfigurationVO configuration =
systemInitializationMapper
.selectCurrentConfiguration();
// 第二步:系统尚未建立任何版本配置时返回明确的数据不存在异常。
if (configuration == null) {
throw new BusinessException(
NOT_FOUND,
"当前系统尚未配置初始化参数");
}
// 第三步:返回已经由 Mapper 显式转换完成的系统基础参数 VO。
return configuration;
}
/**
* 保存新的系统基础参数版本。
*
* @param dto 系统基础参数
* @return 保存后立即生效的新版本
*/
@Override
@Transactional(rollbackFor = Exception.class)
public SystemInitializationConfigurationVO saveConfiguration(
SystemInitializationConfigurationSaveDTO dto) {
// 第一步:查询当前配置,用于判断首次初始化和执行乐观版本校验。
SystemInitializationConfigurationVO current =
systemInitializationMapper
.selectCurrentConfiguration();
// 第二步:规范化页面参数并校验期望版本与当前版本一致。
SystemInitializationConfigurationSaveDTO configuration =
prepareConfiguration(dto, current);
// 第三步:使用同一服务端时间生成本次配置版本时间。
LocalDateTime versionDate = LocalDateTime.now();
/*
* 第四步:首次配置只在版本表仍为空时写入;
* 已有配置时复制全部非页面字段并新增下一条版本记录。
*/
int affectedRows;
if (current == null) {
affectedRows =
systemInitializationMapper
.insertInitialConfiguration(
configuration,
versionDate);
} else {
affectedRows =
systemInitializationMapper
.insertNextConfigurationVersion(
configuration,
versionDate);
}
// 第五步:影响行数不是一条说明配置在保存期间发生并发变化。
requireAffectedRows(
affectedRows,
1,
"系统基础参数已变化,请刷新后重试");
// 第六步:重新读取并返回刚刚生效的最新配置版本。
SystemInitializationConfigurationVO saved =
systemInitializationMapper
.selectCurrentConfiguration();
if (saved == null) {
throw new BusinessException(
"系统基础参数保存后读取失败");
}
return saved;
}
/**
* 查询当前学期。
*
* @return 当前学期
*/
@Override
@Transactional(readOnly = true)
public SemesterInitializationVO getCurrentSemester() {
// 第一步:查询当前学期标志对应的最新学期记录。
SemesterInitializationVO semester =
systemInitializationMapper
.selectCurrentSemester();
// 第二步:尚未设置当前学期时返回明确的数据不存在异常。
if (semester == null) {
throw new BusinessException(
NOT_FOUND,
"当前系统尚未设置当前学期");
}
// 第三步:补充统一展示名称并返回学期参数。
return completeSemesterName(semester);
}
/**
* 保存指定年度和序号的学期。
*
* @param dto 学期初始化参数
* @return 保存后的学期
*/
@Override
@Transactional(rollbackFor = Exception.class)
public SemesterInitializationVO saveSemester(
SemesterInitializationSaveDTO dto) {
// 第一步:规范化并校验学期年度、序号、时间范围和默认业务标志。
SemesterInitializationSaveDTO semester =
prepareSemester(dto);
// 第二步:禁止当前学期时间范围与其他学期发生重叠。
int overlapCount =
systemInitializationMapper
.countOverlappingSemesters(semester);
if (overlapCount > 0) {
throw new BusinessException(
CONFLICT,
"学期时间范围与已有学期重叠");
}
// 第三步:判断同一年度和序号的学期记录是否已经存在。
SemesterInitializationVO existing =
systemInitializationMapper.selectSemesterByKey(
semester.getYear(),
semester.getSequenceNumber());
// 第四步:目标学期设为当前学期时先清除其他记录的当前标志。
if (Boolean.TRUE.equals(
semester.getCurrentSemester())) {
systemInitializationMapper
.clearOtherCurrentSemesters(
semester.getYear(),
semester.getSequenceNumber());
}
// 第五步:根据业务键是否存在选择新增或修改,不重复创建同一学期。
int affectedRows;
if (existing == null) {
affectedRows =
systemInitializationMapper
.insertSemester(semester);
} else {
affectedRows =
systemInitializationMapper
.updateSemester(semester);
}
// 第六步:保存未精确影响一条记录时中止整个学期切换事务。
requireAffectedRows(
affectedRows,
1,
"学期参数保存失败,请刷新后重试");
// 第七步:重新查询并返回保存后的完整学期参数。
SemesterInitializationVO saved =
systemInitializationMapper.selectSemesterByKey(
semester.getYear(),
semester.getSequenceNumber());
if (saved == null) {
throw new BusinessException(
"学期参数保存后读取失败");
}
return completeSemesterName(saved);
}
/**
* 导入历史基础数据。
*
* @param dto 历史数据 Excel 文件
* @return 导入结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public SystemInitializationHistoricalImportResultVO
importHistoricalData(
SystemInitializationHistoricalImportDTO dto) {
// 第一步:解析 Excel 并完成工作表、标题、类型、长度和文件内重复校验。
SystemInitializationHistoricalDataDTO data =
parseHistoricalData(dto);
/*
* 第二步:一次性读取数据库已有编号,
* 在任何写入发生前完成覆盖冲突、所属部别引用和部别循环关系校验。
*/
data = prepareHistoricalData(
data,
systemInitializationMapper.selectDepartmentIds(),
systemInitializationMapper
.selectResearchOfficeIds(),
systemInitializationMapper.selectPersonnelIds());
// 第三步:由服务端为每条教研室隶属关系生成独立关系编号。
for (SystemInitializationResearchOfficeImportRowDTO row
: data.getResearchOffices()) {
row.setRelationId(UuidUtil.getOriginalUUID());
}
// 第四步:使用统一时间记录历史停用数据状态和最终导入完成时间。
LocalDateTime importedAt = LocalDateTime.now();
// 第五步:按照父节点优先顺序批量写入历史部别。
if (!data.getDepartments().isEmpty()) {
int departmentRows =
systemInitializationMapper
.insertHistoricalDepartments(
data.getDepartments(),
importedAt);
requireAffectedRows(
departmentRows,
data.getDepartments().size(),
"历史部别数据写入不完整");
}
// 第六步:批量写入教研室基础信息。
if (!data.getResearchOffices().isEmpty()) {
int researchOfficeRows =
systemInitializationMapper
.insertHistoricalResearchOffices(
data.getResearchOffices(),
importedAt);
requireAffectedRows(
researchOfficeRows,
data.getResearchOffices().size(),
"历史教研室数据写入不完整");
// 第七步:在教研室写入成功后批量建立对应的部别隶属关系。
int relationRows =
systemInitializationMapper
.insertHistoricalResearchOfficeRelations(
data.getResearchOffices());
requireAffectedRows(
relationRows,
data.getResearchOffices().size(),
"历史教研室隶属关系写入不完整");
}
// 第八步:批量写入部别人员,并保存原有在职和主官状态。
if (!data.getPersonnel().isEmpty()) {
int personnelRows =
systemInitializationMapper
.insertHistoricalPersonnel(
data.getPersonnel());
requireAffectedRows(
personnelRows,
data.getPersonnel().size(),
"历史部别人员数据写入不完整");
}
// 第九步:组装并返回本次事务实际完成的四张表写入数量。
return buildHistoricalImportResult(
data,
importedAt);
}
/**
* 执行系统初始化预检查。
*
* @return 预检查汇总结果
*/
@Override
@Transactional(readOnly = true)
public SystemInitializationPrecheckVO precheck() {
// 第一步:执行最小数据库查询,验证当前连接和 SQL 执行能力。
int databaseHealth =
systemInitializationMapper
.selectDatabaseHealth();
// 第二步:查询当前配置和当前学期,允许空值交给预检查项统一说明。
SystemInitializationConfigurationVO configuration =
systemInitializationMapper
.selectCurrentConfiguration();
SemesterInitializationVO semester =
systemInitializationMapper
.selectCurrentSemester();
// 第三步:统计三类初始化基础组织数据。
int departmentCount =
systemInitializationMapper.countDepartments();
int researchOfficeCount =
systemInitializationMapper
.countResearchOffices();
int personnelCount =
systemInitializationMapper.countPersonnel();
// 第四步:统计部别层级、教研室隶属关系和人员所属部别中的失效引用。
int brokenReferenceCount =
systemInitializationMapper
.countBrokenBaseReferences();
// 第五步:组装并返回包含全部检查项的统一预检查结果。
return buildPrecheck(
databaseHealth,
configuration,
semester,
departmentCount,
researchOfficeCount,
personnelCount,
brokenReferenceCount,
LocalDateTime.now());
}
/**
* 完成系统初始化最终确认。
*
* @param dto 页面预检查快照和确认标志
* @return 初始化执行结果
*/
@Override
@Transactional(readOnly = true)
public SystemInitializationExecutionResultVO execute(
SystemInitializationExecutionDTO dto) {
// 第一步:重新执行完整预检查,不信任页面缓存的检查结果。
SystemInitializationPrecheckVO currentPrecheck =
precheck();
/*
* 第二步:校验明确确认标志、全部检查项、
* 配置版本和当前学期均与页面预检查快照一致。
*/
validateExecution(dto, currentPrecheck);
// 第三步:返回最终确认使用的配置版本、学期和完成时间。
return buildExecutionResult(
currentPrecheck,
LocalDateTime.now());
}
}
@@ -0,0 +1,380 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.JCXX;
import com.roomroot.jwgl.mapper.TeachingMaterialMapper;
import com.roomroot.jwgl.service.TeachingMaterialService;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.utils.DateUtils;
import com.roomroot.jwgl.utils.UuidUtil;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.Resource;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
* 教材信息服务实现类
*/
@Service
public class TeachingMaterialServiceImpl implements TeachingMaterialService {
@Resource
private TeachingMaterialMapper teachingMaterialMapper;
@Override
public void add(JCXX jcxx) {
String uuid = UuidUtil.getUUID();
jcxx.setId(uuid);
jcxx.setDelFlag(0);
jcxx.setCjsj(LocalDateTime.now());
jcxx.setXgsj(LocalDateTime.now());
String nextNum = getNextNumber();
nextNum = getNextZeroFillCode(nextNum,4);
jcxx.setBh(nextNum);
teachingMaterialMapper.insert(jcxx);
}
@Override
public void delete(String id) {
JCXX jcxx = getById(id);
jcxx.setDelFlag(1);
jcxx.setXgsj(LocalDateTime.now());
teachingMaterialMapper.updateById(jcxx);
}
@Override
public void update(JCXX jcxx) {
jcxx.setXgsj(LocalDateTime.now());
teachingMaterialMapper.updateById(jcxx);
}
@Override
public JCXX getByBh(String bh) {
LambdaQueryWrapper<JCXX> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(JCXX::getBh, bh);
return teachingMaterialMapper.selectOne(wrapper);
}
@Override
public JCXX getById(String id) {
LambdaQueryWrapper<JCXX> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(JCXX::getId, id);
return teachingMaterialMapper.selectOne(wrapper);
}
@Override
public PageResult<JCXX> pageList(PageQuery query, JCXX jcxx) {
jcxx.setDelFlag(0);
Page<JCXX> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<JCXX> result = teachingMaterialMapper.selectPageList(page, jcxx);
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
@Override
public List<JCXX> list(JCXX jcxx) {
LambdaQueryWrapper<JCXX> wrapper = new LambdaQueryWrapper<>();
if (jcxx != null) {
if (jcxx.getBh() != null && !jcxx.getBh().isEmpty()) {
wrapper.like(JCXX::getBh, jcxx.getBh());
}
if (jcxx.getMc() != null && !jcxx.getMc().isEmpty()) {
wrapper.like(JCXX::getMc, jcxx.getMc());
}
if (jcxx.getIsbn() != null && !jcxx.getIsbn().isEmpty()) {
wrapper.like(JCXX::getIsbn, jcxx.getIsbn());
}
}
wrapper.orderByDesc(JCXX::getCjsj);
return teachingMaterialMapper.selectList(wrapper);
}
@Override
public int importExcel(MultipartFile file) {
try (InputStream is = file.getInputStream();
Workbook workbook = file.getOriginalFilename().endsWith(".xlsx")
? new XSSFWorkbook(is) : new HSSFWorkbook(is)) {
// 获取教材信息sheet
Sheet sheet = workbook.getSheet("教材信息");
if (sheet == null) {
sheet = workbook.getSheetAt(0);
}
int count = 0;
// 在循环外获取起始编号
String nextNum = getNextNumber();
// 从第二行开始读取数据(跳过表头)
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row == null) continue;
//获取编号
// 跳过空行
String bh = getCellValue(row, 0);
JCXX jcxx = getByBh(bh);
String flag = "0";
if(jcxx == null){
jcxx = new JCXX();
jcxx.setId(UuidUtil.getUUID());
jcxx.setDelFlag(0);
jcxx.setCjsj(LocalDateTime.now());
// 生成编号(数字字符串)
jcxx.setBh(bh == null?String.valueOf(nextNum):bh);
nextNum = getNextZeroFillCode(nextNum,4);
flag = "1";
}
jcxx.setXgsj(LocalDateTime.now());
// 教研室
jcxx.setJysdh(getCellValue(row, 1));
// 教材代码
jcxx.setJcdm(getCellValue(row, 2));
// 跳过空行
String mc = getCellValue(row, 3);
if (mc == null || mc.isEmpty()) continue;
// 名称
jcxx.setMc(mc);
// ISBN
jcxx.setIsbn(getCellValue(row, 4));
// 出版社
jcxx.setCbs(getCellValue(row, 5));
// 版次
jcxx.setBc(getCellValue(row, 6));
// 开本
jcxx.setKb(getCellValue(row, 7));
// 作者
//主编
String zb = getCellValue(row, 8) == null ? "" : getCellValue(row, 8);
//副主编
String fzb = getCellValue(row, 9) == null ? "" : getCellValue(row, 9);
//编者
String bz = getCellValue(row, 10)==null?"":getCellValue(row, 10);
jcxx.setZz(zb+" "+fzb+" "+bz);
// 定价
String djStr = getCellValue(row, 11);
if (djStr != null && !djStr.isEmpty()) {
try {
jcxx.setDj(new BigDecimal(djStr));
} catch (NumberFormatException e) {
jcxx.setDj(BigDecimal.ZERO);
}
}
// 教材分类
jcxx.setJcfl(getCellValue(row, 12));
// 教材类型
jcxx.setJclx(getCellValue(row, 13));
// 教材编写类型
jcxx.setJcbxlx(getCellValue(row, 14));
//教材编写类型
jcxx.setJcbxlx(getCellValue(row, 15)==null?"":getCellValue(row, 15));
//教材密级
jcxx.setJcmj(getCellValue(row, 16)==null?"":getCellValue(row, 16));
//编写单位
jcxx.setBxdw(getCellValue(row, 17)==null?"":getCellValue(row, 17));
//责任教研室
jcxx.setZrjyjlxff(getCellValue(row, 18)==null?"":getCellValue(row, 18));
//教材重点类型
jcxx.setJczdlx(getCellValue(row, 19)==null?"":getCellValue(row, 19));
//教材层次
jcxx.setJccc(getCellValue(row, 20)==null?"":getCellValue(row, 20));
//教材来源
jcxx.setJcly(getCellValue(row, 21)==null?"":getCellValue(row, 21));
//出版方式
jcxx.setCbfs(getCellValue(row, 22)==null?"":getCellValue(row, 22));
//计量单位
jcxx.setJldw(getCellValue(row, 23)==null?"":getCellValue(row, 23));
//出版日期
String dateStr = getCellValue(row, 24)==null?"":getCellValue(row, 24);
LocalDateTime cbrq = DateUtils.parseAnyDate(dateStr);
jcxx.setCbrq(cbrq);
//自编教材立项日期
jcxx.setJcmj(getCellValue(row, 25)==null?"":getCellValue(row, 25));
//评选情况
jcxx.setJcmj(getCellValue(row, 26)==null?"":getCellValue(row, 26));
//颁发审定单位
jcxx.setJcmj(getCellValue(row, 27)==null?"":getCellValue(row, 27));
//责任教员及联系方式
jcxx.setJcmj(getCellValue(row, 28)==null?"":getCellValue(row, 28));
//需彩印
jcxx.setJcmj(getCellValue(row, 29)==null?"":getCellValue(row, 29));
//外协排版
jcxx.setJcmj(getCellValue(row, 30)==null?"":getCellValue(row, 30));
//备注
jcxx.setJcmj(getCellValue(row, 31)==null?"":getCellValue(row, 31));
//停用
jcxx.setTy(false);
jcxx.setXcy(false);
if("1".equals(flag)){
teachingMaterialMapper.insert(jcxx);
}else if("0".equals(flag)){
teachingMaterialMapper.updateById(jcxx);
}
count++;
}
return count;
} catch (Exception e) {
throw new RuntimeException("Excel解析失败: " + e.getMessage(), e);
}
}
@Override
public void downloadTemplate(HttpServletResponse response) {
try {
// 从resources/template目录读取模板文件
ClassPathResource resource = new ClassPathResource("template/教材管理.xls");
InputStream is = resource.getInputStream();
// 设置响应头
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment;filename=" +
URLEncoder.encode("教材管理导入模板.xls", "UTF-8"));
// 输出文件
OutputStream os = response.getOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
os.flush();
is.close();
os.close();
} catch (Exception e) {
throw new RuntimeException("下载模板失败: " + e.getMessage(), e);
}
}
/**
* 获取下一个编号(四位补零)
*/
private String getNextNumber() {
String maxBh = teachingMaterialMapper.selectMaxBh();
String nextNum = "0001";
if (maxBh != null && !maxBh.isEmpty()) {
try {
// 去掉前导零后解析
nextNum = maxBh;
} catch (NumberFormatException e) {
nextNum = "0001";
}
}
return nextNum;
}
/**
* 根据数据库最大补零编号,生成下一个编号
* @param maxZeroCode 数据库查出的最大补零编号,为空则从00001开始
* @param length 固定补零长度,例:5=00001
* @return 下一个补零字符串编号
*/
public static String getNextZeroFillCode(String maxZeroCode, int length) {
int num = 1;
// 数据库有编号则解析+1
if (maxZeroCode != null && maxZeroCode.trim().length() > 0) {
num = Integer.parseInt(maxZeroCode.trim()) + 1;
}
// 格式化补零
String format = "%0" + length + "d";
return String.format(format, num);
}
/**
* 获取单元格字符串值
*/
private String getCellValue(Row row, int index) {
Cell cell = row.getCell(index, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
if (cell == null) {
return null;
}
CellType type = cell.getCellType();
// 处理公式单元格
if (type == CellType.FORMULA) {
type = cell.getCachedFormulaResultType();
}
switch (type) {
case NUMERIC:
// 判断是否是日期格式
if (DateUtil.isCellDateFormatted(cell)) {
java.util.Date date = cell.getDateCellValue();
if (date != null) {
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy/MM/dd");
return sdf.format(date);
}
return null;
}
double num = cell.getNumericCellValue();
// 整数就去掉小数点后多余的 .0
if (num == Math.floor(num)) {
return String.valueOf((long) num);
} else {
return String.valueOf(num);
}
case STRING:
return cell.getStringCellValue().trim();
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
default:
return null;
}
}
/**
* 解析日期时间字符串
*/
private LocalDateTime parseDateTime(String dateStr) {
// 尝试多种日期格式
String[] patterns = {
"yyyy-MM-dd HH:mm:ss",
"yyyy-MM-dd",
"yyyy/MM/dd",
"yyyy年MM月dd日"
};
for (String pattern : patterns) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
return LocalDateTime.parse(dateStr, formatter);
} catch (Exception e) {
// 继续尝试下一个格式
}
}
return null;
}
}
@@ -0,0 +1,107 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.JXSSJH;
import com.roomroot.jwgl.mapper.TeachingPlanMapper;
import com.roomroot.jwgl.service.TeachingPlanService;
import com.roomroot.jwgl.unit.BusinessException;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.utils.ExcelParseUtil;
import com.roomroot.jwgl.utils.UuidUtil;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 导入数据表-教学实施计划服务实现类
*/
@Service
public class TeachingPlanServiceImpl implements TeachingPlanService {
@Resource
private TeachingPlanMapper teachingPlanMapper;
@Override
public void add(JXSSJH drsjbjxssjh) {
String uuid = UuidUtil.getUUID();
drsjbjxssjh.setBsh(uuid);
drsjbjxssjh.setDelFlag(0);
teachingPlanMapper.insert(drsjbjxssjh);
}
@Override
public void delete(String bsh) {
JXSSJH drsjbjxssjh = getByBsh(bsh);
drsjbjxssjh.setDelFlag(1);
teachingPlanMapper.updateById(drsjbjxssjh);
}
@Override
public void update(JXSSJH drsjbjxssjh) {
teachingPlanMapper.updateById(drsjbjxssjh);
}
@Override
public JXSSJH getByBsh(String bsh) {
return teachingPlanMapper.selectById(bsh);
}
@Override
public PageResult<JXSSJH> pageList(PageQuery query, JXSSJH drsjbjxssjh) {
drsjbjxssjh.setDelFlag(0);
Page<JXSSJH> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<JXSSJH> result = teachingPlanMapper.selectPageList(page, drsjbjxssjh);
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
@Override
public List<JXSSJH> list(JXSSJH drsjbjxssjh) {
LambdaQueryWrapper<JXSSJH> wrapper = new LambdaQueryWrapper<>();
if (drsjbjxssjh != null) {
if (drsjbjxssjh.getNd() != null) {
wrapper.eq(JXSSJH::getNd, drsjbjxssjh.getNd());
}
if (drsjbjxssjh.getKcmc() != null && !drsjbjxssjh.getKcmc().isEmpty()) {
wrapper.like(JXSSJH::getKcmc, drsjbjxssjh.getKcmc());
}
if (drsjbjxssjh.getYtgjc() != null) {
wrapper.eq(JXSSJH::getYtgjc, drsjbjxssjh.getYtgjc());
}
}
wrapper.orderByAsc(JXSSJH::getXh);
return teachingPlanMapper.selectList(wrapper);
}
@Override
@Transactional(rollbackFor = Exception.class)
public int importFromExcel(MultipartFile file) throws Exception {
if (file == null || file.isEmpty()) {
throw new BusinessException("导入文件不能为空");
}
List<JXSSJH> list = ExcelParseUtil.parseJXSSJHExcel(
file.getInputStream(), file.getOriginalFilename());
if (list.isEmpty()) {
throw new BusinessException("Excel中无有效数据");
}
for (JXSSJH entity : list) {
// 标识号自动生成
entity.setBsh(UuidUtil.getUUID());
// 登录编号自动生成
entity.setDlbh(UuidUtil.getUUID());
// 默认值
entity.setYtgjc(false);
entity.setYbhl(false);
entity.setYbdr(false);
entity.setYbcl(false);
entity.setDelFlag(0);
teachingPlanMapper.insert(entity);
}
return list.size();
}
}
@@ -0,0 +1,106 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.JCKCMXX;
import com.roomroot.jwgl.mapper.TeachingStockDetailMapper;
import com.roomroot.jwgl.service.TeachingStockDetailService;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.utils.UuidUtil;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import java.util.List;
/**
* 教材库存明细项服务实现类
*/
@Service
public class TeachingStockDetailServiceImpl implements TeachingStockDetailService {
@Resource
private TeachingStockDetailMapper teachingStockDetailMapper;
@Override
public void add(JCKCMXX jckcmxx) {
String uuid = UuidUtil.getUUID();
jckcmxx.setId(uuid);
jckcmxx.setDelFlag(0);
// 生成编号(四位补零)
jckcmxx.setBh(getNextNumber());
teachingStockDetailMapper.insert(jckcmxx);
}
@Override
public void delete(String id) {
JCKCMXX jckcmxx = getById(id);
jckcmxx.setDelFlag(1);
teachingStockDetailMapper.updateById(jckcmxx);
}
@Override
public void update(JCKCMXX jckcmxx) {
teachingStockDetailMapper.updateById(jckcmxx);
}
@Override
public JCKCMXX getById(String id) {
return teachingStockDetailMapper.selectById(id);
}
@Override
public JCKCMXX getByBh(String bh) {
LambdaQueryWrapper<JCKCMXX> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(JCKCMXX::getBh, bh);
return teachingStockDetailMapper.selectOne(wrapper);
}
@Override
public List<JCKCMXX> getByJckcdbbh(String jckcdbbh) {
return teachingStockDetailMapper.selectByJckcdbbh(jckcdbbh);
}
@Override
public PageResult<JCKCMXX> pageList(PageQuery query, JCKCMXX jckcmxx) {
jckcmxx.setDelFlag(0);
Page<JCKCMXX> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<JCKCMXX> result = teachingStockDetailMapper.selectPageList(page, jckcmxx);
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
@Override
public List<JCKCMXX> list(JCKCMXX jckcmxx) {
LambdaQueryWrapper<JCKCMXX> wrapper = new LambdaQueryWrapper<>();
if (jckcmxx != null) {
if (jckcmxx.getBh() != null && !jckcmxx.getBh().isEmpty()) {
wrapper.like(JCKCMXX::getBh, jckcmxx.getBh());
}
if (jckcmxx.getJckcdbbh() != null && !jckcmxx.getJckcdbbh().isEmpty()) {
wrapper.eq(JCKCMXX::getJckcdbbh, jckcmxx.getJckcdbbh());
}
if (jckcmxx.getJcxxbh() != null && !jckcmxx.getJcxxbh().isEmpty()) {
wrapper.like(JCKCMXX::getJcxxbh, jckcmxx.getJcxxbh());
}
}
return teachingStockDetailMapper.selectList(wrapper);
}
/**
* 获取下一个编号(四位补零)
*/
private String getNextNumber() {
String maxBh = teachingStockDetailMapper.selectMaxBh();
int nextNum = 1;
if (maxBh != null && !maxBh.isEmpty()) {
try {
// 去掉前导零后解析
nextNum = Integer.parseInt(maxBh) + 1;
} catch (NumberFormatException e) {
nextNum = 1;
}
}
return String.format("%04d", nextNum);
}
}
@@ -0,0 +1,110 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.JCKCD;
import com.roomroot.jwgl.mapper.TeachingStockMapper;
import com.roomroot.jwgl.service.TeachingStockService;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.utils.UuidUtil;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
/**
* 教材库存单服务实现类
*/
@Service
public class TeachingStockServiceImpl implements TeachingStockService {
@Resource
private TeachingStockMapper teachingStockMapper;
@Override
public void add(JCKCD jckcd) {
String uuid = UuidUtil.getUUID();
jckcd.setId(uuid);
jckcd.setDelFlag(0);
jckcd.setCjsj(LocalDateTime.now());
jckcd.setXgsj(LocalDateTime.now());
// 生成编号(四位补零)
jckcd.setBh(getNextNumber());
teachingStockMapper.insert(jckcd);
}
@Override
public void delete(String id) {
JCKCD jckcd = getById(id);
jckcd.setDelFlag(1);
jckcd.setXgsj(LocalDateTime.now());
teachingStockMapper.updateById(jckcd);
}
@Override
public void update(JCKCD jckcd) {
jckcd.setXgsj(LocalDateTime.now());
teachingStockMapper.updateById(jckcd);
}
@Override
public JCKCD getById(String id) {
return teachingStockMapper.selectById(id);
}
@Override
public JCKCD getByBh(String bh) {
LambdaQueryWrapper<JCKCD> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(JCKCD::getBh, bh);
return teachingStockMapper.selectOne(wrapper);
}
@Override
public PageResult<JCKCD> pageList(PageQuery query, JCKCD jckcd) {
jckcd.setDelFlag(0);
Page<JCKCD> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<JCKCD> result = teachingStockMapper.selectPageList(page, jckcd);
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
@Override
public List<JCKCD> list(JCKCD jckcd) {
LambdaQueryWrapper<JCKCD> wrapper = new LambdaQueryWrapper<>();
if (jckcd != null) {
if (jckcd.getBh() != null && !jckcd.getBh().isEmpty()) {
wrapper.like(JCKCD::getBh, jckcd.getBh());
}
if (jckcd.getMc() != null && !jckcd.getMc().isEmpty()) {
wrapper.like(JCKCD::getMc, jckcd.getMc());
}
if (jckcd.getLb() != null && !jckcd.getLb().isEmpty()) {
wrapper.eq(JCKCD::getLb, jckcd.getLb());
}
if (jckcd.getZt() != null && !jckcd.getZt().isEmpty()) {
wrapper.eq(JCKCD::getZt, jckcd.getZt());
}
}
wrapper.orderByDesc(JCKCD::getCjsj);
return teachingStockMapper.selectList(wrapper);
}
/**
* 获取下一个编号(四位补零)
*/
private String getNextNumber() {
String maxBh = teachingStockMapper.selectMaxBh();
int nextNum = 1;
if (maxBh != null && !maxBh.isEmpty()) {
try {
// 去掉前导零后解析
nextNum = Integer.parseInt(maxBh) + 1;
} catch (NumberFormatException e) {
nextNum = 1;
}
}
return String.format("%04d", nextNum);
}
}
@@ -0,0 +1,128 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.roomroot.jwgl.entity.JXRW;
import com.roomroot.jwgl.entity.JYSRWS;
import com.roomroot.jwgl.mapper.TeachingTaskMapper;
import com.roomroot.jwgl.mapper.OfficeTaskBookMapper;
import com.roomroot.jwgl.service.TeachingTaskService;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import com.roomroot.jwgl.utils.UuidUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jakarta.annotation.Resource;
import java.util.List;
/**
* 教学任务服务实现类
*/
@Service
public class TeachingTaskServiceImpl implements TeachingTaskService {
@Resource
private TeachingTaskMapper teachingTaskMapper;
@Resource
private OfficeTaskBookMapper officeTaskBookMapper;
@Override
public void add(JXRW jxrw) {
String uuid = UuidUtil.getUUID();
jxrw.setBh(uuid);
jxrw.setDelFlag(0);
// 状态默认为未发布
jxrw.setZt("上报");
// 自动填充创建时间
java.time.LocalDateTime now = java.time.LocalDateTime.now();
jxrw.setCjsj(now); // 创建时间
teachingTaskMapper.insert(jxrw);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void delete(String bh) {
JXRW jxrw = getByBh(bh);
if (jxrw != null) {
jxrw.setDelFlag(1);
teachingTaskMapper.updateById(jxrw);
// 同时删除对应的教研室任务书(教学任务编号 = 教学任务编号)
LambdaQueryWrapper<JYSRWS> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(JYSRWS::getJxrwbh, bh);
List<JYSRWS> jysrwsList = officeTaskBookMapper.selectList(wrapper);
for (JYSRWS jysrws : jysrwsList) {
jysrws.setDelFlag(1);
officeTaskBookMapper.updateById(jysrws);
}
}
}
@Override
public void batchDelete(List<String> bhList) {
for (String bh : bhList) {
delete(bh);
}
}
@Override
public void update(JXRW jxrw) {
teachingTaskMapper.updateById(jxrw);
}
@Override
public JXRW getByBh(String bh) {
return teachingTaskMapper.selectById(bh);
}
@Override
public PageResult<JXRW> pageList(PageQuery query, JXRW jxrw) {
jxrw.setDelFlag(0);
Page<JXRW> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<JXRW> result = teachingTaskMapper.selectPageList(page, jxrw);
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
@Override
public List<JXRW> listAllValid() {
return teachingTaskMapper.selectList(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<JXRW>()
.eq(JXRW::getDelFlag, 0)
);
}
@Transactional(rollbackFor = Exception.class)
@Override
public int batchPublish(String bh) {
if (bh == null || bh.isEmpty()) {
return 0;
}
JXRW jxrw = teachingTaskMapper.selectById(bh);
if (jxrw == null || jxrw.getDelFlag() != 0) {
return 0;
}
// 更新教学任务状态为"发布"
jxrw.setZt("发布");
teachingTaskMapper.updateById(jxrw);
// 批量更新教研室任务书状态为"发布"
LambdaQueryWrapper<JYSRWS> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(JYSRWS::getJxrwbh, bh)
.eq(JYSRWS::getDelFlag, 0);
List<JYSRWS> jysrwsList = officeTaskBookMapper.selectList(wrapper);
int count = 0;
for (JYSRWS jysrws : jysrwsList) {
jysrws.setZt("发布");
officeTaskBookMapper.updateById(jysrws);
count++;
}
return count;
}
}
@@ -0,0 +1,69 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.roomroot.jwgl.entity.ZYB;
import com.roomroot.jwgl.mapper.ZYBMapper;
import com.roomroot.jwgl.service.TrainingProgramService;
import com.roomroot.jwgl.unit.PageQuery;
import com.roomroot.jwgl.unit.PageResult;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
/**
* 培养方案管理服务实现类
* <p>
* 实现专业/培养方案的增加、删除(逻辑删除)、更新、分页查询等功能。
* 逻辑删除将 停用 字段置为 true(停用=false为启用,停用=true为已停用/删除)。
* 多版本通过培训类型(培训类型)字段区分。
* </p>
*/
@Service
public class TrainingProgramServiceImpl implements TrainingProgramService {
@Resource
private ZYBMapper zybMapper;
@Override
@Transactional
public void add(ZYB entity) {
entity.setTy(false);
entity.setQysj(LocalDateTime.now());
zybMapper.insert(entity);
}
@Override
@Transactional
public void disable(String zydh) {
ZYB entity = new ZYB();
entity.setZydh(zydh);
entity.setTy(true);
entity.setTysj(LocalDateTime.now());
zybMapper.updateById(entity);
}
@Override
@Transactional
public void update(ZYB entity) {
zybMapper.updateById(entity);
}
@Override
public ZYB getById(String zydh) {
LambdaQueryWrapper<ZYB> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ZYB::getZydh, zydh)
.eq(ZYB::getTy, false);
return zybMapper.selectOne(wrapper);
}
@Override
public PageResult<ZYB> pageList(PageQuery query, ZYB cond) {
Page<ZYB> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<ZYB> result = zybMapper.selectPageList(page, cond);
return new PageResult<>(result.getRecords(), result.getTotal(),
query.getPageNum(), query.getPageSize());
}
}
@@ -0,0 +1,74 @@
package com.roomroot.jwgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.roomroot.jwgl.entity.ZYJXJHB;
import com.roomroot.jwgl.mapper.ZYJXJHBMapper;
import com.roomroot.jwgl.service.ZYJXJHBService;
import com.roomroot.jwgl.utils.UuidUtil;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
/**
* 专业教学计划表服务实现
*/
@Service
public class ZYJXJHBServiceImpl implements ZYJXJHBService {
@Resource
private ZYJXJHBMapper zyjxjhbMapper;
@Override
public void add(ZYJXJHB zyjxjhb) {
zyjxjhb.setBh(UuidUtil.getUUID());
if (zyjxjhb.getTy() == null) {
zyjxjhb.setTy(0);
}
if (zyjxjhb.getQysj() == null) {
zyjxjhb.setQysj(LocalDateTime.now());
}
zyjxjhbMapper.insert(zyjxjhb);
}
@Override
public void delete(String bh) {
LambdaUpdateWrapper<ZYJXJHB> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(ZYJXJHB::getBh, bh)
.set(ZYJXJHB::getTy, 1)
.set(ZYJXJHB::getTysj, LocalDateTime.now());
zyjxjhbMapper.update(null, wrapper);
}
@Override
public void batchDelete(List<String> bhList) {
for (String bh : bhList) {
delete(bh);
}
}
@Override
public void update(ZYJXJHB zyjxjhb) {
zyjxjhbMapper.updateById(zyjxjhb);
}
@Override
public ZYJXJHB getByBh(String bh) {
return zyjxjhbMapper.selectById(bh);
}
@Override
public List<ZYJXJHB> list() {
return zyjxjhbMapper.selectList(null);
}
@Override
public List<ZYJXJHB> listByZydhAndTy(String zydh, Integer ty) {
LambdaQueryWrapper<ZYJXJHB> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ZYJXJHB::getZydh, zydh)
.eq(ZYJXJHB::getTy, ty);
return zyjxjhbMapper.selectList(wrapper);
}
}