提交修改

This commit is contained in:
zlwu committed 2025-08-13 19:26:41 +08:00
1 parent df38590dbf
commit b0375f801c
18 files changed
+3625 -285

No files matched your search

@@ -41,29 +41,30 @@ public interface CwApprovalRecordMapper extends BaseMapperPlus<CwApprovalRecord,
* @param nodeStatus 节点状态
* @return 审批记录列表
*/
List<CwApprovalRecordVo> selectPendingByApproverId(@Param("approverId") Long approverId, @Param("nodeStatus") String nodeStatus);
List<CwApprovalRecordVo> selectPendingByApproverId(@Param("approverId") Long approverId,
@Param("nodeStatus") String nodeStatus);
/**
* 根据审批人ID和节点编码查询审批记录
*
* @param approverId 审批人ID
* @param nodeCode 节点编码
* @param nodeCode 节点编码
* @param nodeStatus 节点状态
* @return 审批记录列表
*/
List<CwApprovalRecordVo> selectByApproverAndNode(@Param("approverId") Long approverId,
@Param("nodeCode") String nodeCode,
@Param("nodeStatus") String nodeStatus);
@Param("nodeCode") String nodeCode,
@Param("nodeStatus") String nodeStatus);
/**
* 根据节点编码查询审批记录
*
* @param nodeCode 节点编码
* @param nodeCode 节点编码
* @param nodeStatus 节点状态
* @return 审批记录列表
*/
List<CwApprovalRecordVo> selectByNode(@Param("nodeCode") String nodeCode,
@Param("nodeStatus") String nodeStatus);
@Param("nodeStatus") String nodeStatus);
/**
* 查询财务审批节点的待审批记录(包含waiting和pending状态)
@@ -91,11 +92,11 @@ public interface CwApprovalRecordMapper extends BaseMapperPlus<CwApprovalRecord,
* 根据申报ID和节点编码查询审批记录
*
* @param applicationId 申报ID
* @param nodeCode 节点编码
* @param nodeCode 节点编码
* @return 审批记录
*/
CwApprovalRecordVo selectByApplicationAndNode(@Param("applicationId") Long applicationId,
@Param("nodeCode") String nodeCode);
@Param("nodeCode") String nodeCode);
/**
* 根据申报ID删除审批记录
@@ -109,11 +110,11 @@ public interface CwApprovalRecordMapper extends BaseMapperPlus<CwApprovalRecord,
* 查询当前审批节点记录
*
* @param applicationId 申报ID
* @param nodeStatus 节点状态
* @param nodeStatus 节点状态
* @return 当前审批节点记录
*/
CwApprovalRecordVo selectCurrentApprovalNode(@Param("applicationId") Long applicationId,
@Param("nodeStatus") String nodeStatus);
@Param("nodeStatus") String nodeStatus);
/**
* 批量插入审批记录
@@ -127,7 +128,7 @@ public interface CwApprovalRecordMapper extends BaseMapperPlus<CwApprovalRecord,
* 分页查询审批记录(支持关联查询)
*
* @param page 分页参数
* @param bo 查询条件
* @param bo 查询条件
* @return 分页结果
*/
IPage<CwApprovalRecordVo> selectPageWithJoin(IPage<CwApprovalRecordVo> page, @Param("bo") CwApprovalRecordBo bo);
@@ -140,4 +141,12 @@ public interface CwApprovalRecordMapper extends BaseMapperPlus<CwApprovalRecord,
*/
CwApprovalRecordVo selectByIdWithJoin(@Param("id") Long id);
/**
* 查询具有院领导审批权限的用户列表
* 查找具有 'cw:approval:leader' 权限的用户
*
* @return 可选审批人列表
*/
List<java.util.Map<String, Object>> selectAvailableApprovers();
}
@@ -37,7 +37,6 @@ import java.util.*;
import java.util.stream.Collectors;
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* 审批记录Service业务层处理
@@ -56,9 +55,6 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
private final ISysUserService sysUserService;
private final ISysDeptService sysDeptService;
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 根据主键查询审批记录
*/
@@ -90,7 +86,8 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
*/
@Override
public TableDataInfo<CwApprovalRecordVo> queryPageList(CwApprovalRecordBo bo, PageQuery pageQuery) {
log.info("🔍 开始查询审批记录,查询参数:deptId={}, pageNum={}, pageSize={}", bo.getDeptId(), pageQuery.getPageNum(), pageQuery.getPageSize());
log.info("🔍 开始查询审批记录,查询参数:deptId={}, pageNum={}, pageSize={}", bo.getDeptId(), pageQuery.getPageNum(),
pageQuery.getPageSize());
// 🔧 使用新的关联查询方法(支持完整字段显示)
Page<CwApprovalRecordVo> page = pageQuery.build();
@@ -167,7 +164,7 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
for (int i = 0; i < Math.min(result.getRecords().size(), 2); i++) {
CwApprovalRecordVo record = result.getRecords().get(i);
log.info("📋 记录{}详情:id={}, 申报编号={}, 项目名称={}, 申报人={}, 部门={}, 总投资={}, 当前审批人={}, 状态={}",
i+1, record.getId(), record.getApplicationNo(), record.getProjectName(),
i + 1, record.getId(), record.getApplicationNo(), record.getProjectName(),
record.getApplicantName(), record.getDeptName(), record.getTotalInvestment(),
record.getCurrentApproverName(), record.getApprovalStatus());
}
@@ -196,7 +193,8 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
private LambdaQueryWrapper<CwApprovalRecord> buildQueryWrapper(CwApprovalRecordBo bo) {
LambdaQueryWrapper<CwApprovalRecord> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getApplicationId() != null, CwApprovalRecord::getApplicationId, bo.getApplicationId());
lqw.eq(StringUtils.isNotBlank(bo.getApplicationNo()), CwApprovalRecord::getApplicationNo, bo.getApplicationNo());
lqw.eq(StringUtils.isNotBlank(bo.getApplicationNo()), CwApprovalRecord::getApplicationNo,
bo.getApplicationNo());
lqw.eq(StringUtils.isNotBlank(bo.getNodeCode()), CwApprovalRecord::getNodeCode, bo.getNodeCode());
lqw.eq(StringUtils.isNotBlank(bo.getNodeStatus()), CwApprovalRecord::getNodeStatus, bo.getNodeStatus());
lqw.eq(bo.getApproverId() != null, CwApprovalRecord::getApproverId, bo.getApproverId());
@@ -212,7 +210,8 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
if (currentDeptId != null) {
// 查询该部门的项目申报ID列表
lqw.inSql(CwApprovalRecord::getApplicationId,
"SELECT id FROM cw_project_application WHERE department_id = " + currentDeptId + " AND deleted = 0");
"SELECT id FROM cw_project_application WHERE department_id = " + currentDeptId
+ " AND deleted = 0");
// 只显示部门审批节点的记录
lqw.eq(CwApprovalRecord::getNodeCode, "dept_approve");
@@ -234,7 +233,8 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
try {
Long deptId = Long.valueOf(bo.getDeptId());
lqw.in(CwApprovalRecord::getApplicationId,
"SELECT id FROM cw_project_application WHERE department_id = " + deptId + " AND deleted = 0");
"SELECT id FROM cw_project_application WHERE department_id = " + deptId
+ " AND deleted = 0");
} catch (NumberFormatException e) {
log.warn("无效的部门ID格式:{}", bo.getDeptId());
}
@@ -245,11 +245,11 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
// 其他查询条件 - 暂时注释掉,避免复杂的关联查询导致编译错误
// if (StringUtils.isNotBlank(bo.getProjectName())) {
// // 项目名称查询需要关联查询,暂时跳过
// // 项目名称查询需要关联查询,暂时跳过
// }
// if (StringUtils.isNotBlank(bo.getApplicantName())) {
// // 申报人姓名查询需要关联查询,暂时跳过
// // 申报人姓名查询需要关联查询,暂时跳过
// }
if (StringUtils.isNotBlank(bo.getApproverName())) {
@@ -304,8 +304,8 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
if (deptId != null) {
// 通过关联查询,只显示本部门用户提交的申报对应的审批记录
lqw.exists("SELECT 1 FROM cw_project_application a " +
"WHERE a.id = cw_approval_record.application_id " +
"AND a.department_id = " + deptId);
"WHERE a.id = cw_approval_record.application_id " +
"AND a.department_id = " + deptId);
// 🔧 关键修复:部门领导也需要过滤为自己作为审批人的记录
lqw.eq(CwApprovalRecord::getApproverId, currentUserId);
log.debug("部门领导权限过滤完成:deptId={}, approverId={}", deptId, currentUserId);
@@ -383,8 +383,8 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
// 查询当前用户已审批的记录(精确匹配已完成的状态)
LambdaQueryWrapper<CwApprovalRecord> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(CwApprovalRecord::getApproverId, currentUserId)
.in(CwApprovalRecord::getNodeStatus, Arrays.asList("approved", "rejected", "returned"))
.orderByDesc(CwApprovalRecord::getEndTime);
.in(CwApprovalRecord::getNodeStatus, Arrays.asList("approved", "rejected", "returned"))
.orderByDesc(CwApprovalRecord::getEndTime);
return baseMapper.selectList(queryWrapper);
});
@@ -544,8 +544,8 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public Boolean createApprovalProcess(Long applicationId, String applicationNo,
Long currentUserId, String currentUserName,
Long currentDeptId, String currentTenantId) {
Long currentUserId, String currentUserName,
Long currentDeptId, String currentTenantId) {
log.info("🚀 开始创建审批流程:applicationId={}, applicationNo={}", applicationId, applicationNo);
log.info("📋 使用用户上下文:userId={}, userName={}, deptId={}, tenantId={}",
currentUserId, currentUserName, currentDeptId, currentTenantId);
@@ -565,15 +565,15 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
log.error("❌ 申报记录不存在:applicationId={}", applicationId);
throw new RuntimeException("申报记录不存在");
}
log.info("✅ 成功获取申报信息:projectName={}, status={}", application.getProjectName(), application.getApprovalStatus());
log.info("✅ 成功获取申报信息:projectName={}, status={}", application.getProjectName(),
application.getApprovalStatus());
// 🔧 重要修复:清理该申报的旧审批记录(避免重复数据)
log.info("🧹 开始清理申报ID={}的旧审批记录...", applicationId);
try {
int deletedCount = baseMapper.delete(
Wrappers.<CwApprovalRecord>lambdaQuery()
.eq(CwApprovalRecord::getApplicationId, applicationId)
);
Wrappers.<CwApprovalRecord>lambdaQuery()
.eq(CwApprovalRecord::getApplicationId, applicationId));
log.info("✅ 清理旧审批记录完成:删除{}条记录", deletedCount);
} catch (Exception e) {
log.error("❌ 清理旧审批记录失败:applicationId={}, error={}", applicationId, e.getMessage(), e);
@@ -587,8 +587,8 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
// 第1节点:部门审批(提交时只创建这一个记录)
CwApprovalRecord deptRecord = createApprovalRecord(applicationId, applicationNo,
"dept_approve", "部门审批", 1, "waiting",
currentUserId, currentUserName, currentDeptId, currentTenantId);
"dept_approve", "部门审批", 1, "waiting",
currentUserId, currentUserName, currentDeptId, currentTenantId);
records.add(deptRecord);
log.info("✅ 创建部门审批记录:ID={}, 审批人={}", deptRecord.getId(), deptRecord.getApproverName());
@@ -602,8 +602,8 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
for (int i = 0; i < records.size(); i++) {
CwApprovalRecord record = records.get(i);
log.info("📋 记录{}详情: ID={}, ApplicationId={}, NodeCode={}, ApproverId={}, CreateBy={}, TenantId={}",
i+1, record.getId(), record.getApplicationId(), record.getNodeCode(),
record.getApproverId(), record.getCreateBy(), record.getTenantId());
i + 1, record.getId(), record.getApplicationId(), record.getNodeCode(),
record.getApproverId(), record.getCreateBy(), record.getTenantId());
}
// 批量插入审批记录
@@ -626,11 +626,10 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
try {
// 直接使用Mapper在当前事务中更新,避免调用Service层的新事务
int updateCount = applicationMapper.update(null,
Wrappers.<CwProjectApplication>lambdaUpdate()
.set(CwProjectApplication::getApprovalStatus, "approving")
.set(CwProjectApplication::getUpdateTime, new Date())
.eq(CwProjectApplication::getId, applicationId)
);
Wrappers.<CwProjectApplication>lambdaUpdate()
.set(CwProjectApplication::getApprovalStatus, "approving")
.set(CwProjectApplication::getUpdateTime, new Date())
.eq(CwProjectApplication::getId, applicationId));
if (updateCount > 0) {
log.info("✅ 申报状态直接更新成功:applicationId={} -> approving, 影响行数={}", applicationId, updateCount);
@@ -673,10 +672,10 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
}
private CwApprovalRecord createApprovalRecord(Long applicationId, String applicationNo,
String nodeCode, String nodeName,
Integer nodeOrder, String nodeStatus,
Long currentUserId, String currentUserName,
Long currentDeptId, String currentTenantId) {
String nodeCode, String nodeName,
Integer nodeOrder, String nodeStatus,
Long currentUserId, String currentUserName,
Long currentDeptId, String currentTenantId) {
CwApprovalRecord record = new CwApprovalRecord();
// 生成唯一ID(批量插入需要手动生成)
@@ -709,7 +708,7 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
record.setCreateDept(currentDeptId);
record.setTenantId(currentTenantId);
// 设置其他必需字段(为批量插入提供完整数据)
// 设置其他必需字段(为批量插入提供完整数据)
record.setIsDelegate(false);
record.setIsOvertime(false);
@@ -748,7 +747,7 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
* 根据节点类型设置审批人
*/
private void setApproverByNodeCode(CwApprovalRecord record, String nodeCode,
Long currentUserId, String currentUserName) {
Long currentUserId, String currentUserName) {
// 根据审批节点类型查找正确的审批人,而不是使用申报人
try {
switch (nodeCode) {
@@ -875,12 +874,12 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
try {
// SQL查询:查找该部门内具有project_dept_manage角色的用户
String sql = "SELECT ur.user_id FROM sys_user_role ur " +
"INNER JOIN sys_role r ON ur.role_id = r.role_id " +
"INNER JOIN sys_user u ON ur.user_id = u.user_id " +
"WHERE r.role_key = 'project_dept_manage' " +
"AND u.dept_id = " + deptId + " " +
"AND u.del_flag = 0 AND u.status = 0 " +
"LIMIT 1";
"INNER JOIN sys_role r ON ur.role_id = r.role_id " +
"INNER JOIN sys_user u ON ur.user_id = u.user_id " +
"WHERE r.role_key = 'project_dept_manage' " +
"AND u.dept_id = " + deptId + " " +
"AND u.del_flag = 0 AND u.status = 0 " +
"LIMIT 1";
log.info("查找部门审批人SQL:{}", sql);
@@ -958,8 +957,8 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
// 更新当前审批记录
CwApprovalRecordBo updateBo = new CwApprovalRecordBo();
updateBo.setId(currentRecord.getId());
updateBo.setNodeStatus("approve".equals(approveAction) ? "approved" :
"reject".equals(approveAction) ? "rejected" : "returned");
updateBo.setNodeStatus("approve".equals(approveAction) ? "approved"
: "reject".equals(approveAction) ? "rejected" : "returned");
updateBo.setApproveAction(approveAction);
updateBo.setApproveOpinion(approveOpinion);
updateBo.setEndTime(new Date());
@@ -974,7 +973,7 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
} catch (Exception e) {
log.error("处理审批失败:applicationId={}, nodeCode={}, error={}",
applicationId, nodeCode, e.getMessage(), e);
applicationId, nodeCode, e.getMessage(), e);
throw new RuntimeException("处理审批失败:" + e.getMessage(), e);
}
}
@@ -1020,8 +1019,8 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
// 根据节点类型创建对应的审批记录
CwApprovalRecord newRecord = createApprovalRecordForNode(applicationId, application.getApplicationNo(),
nextNodeCode, application.getCreateBy(), application.getApplicantName(),
application.getDepartmentId(), LoginHelper.getTenantId());
nextNodeCode, application.getCreateBy(), application.getApplicantName(),
application.getDepartmentId(), LoginHelper.getTenantId());
if (newRecord != null) {
// 插入新的审批记录
@@ -1046,7 +1045,7 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
return true;
} catch (Exception e) {
log.error("启动下一个审批节点失败:applicationId={}, nodeCode={}, error={}",
applicationId, nextNodeCode, e.getMessage(), e);
applicationId, nextNodeCode, e.getMessage(), e);
throw new RuntimeException("启动下一个审批节点失败:" + e.getMessage());
}
}
@@ -1055,8 +1054,8 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
* 动态创建指定节点的审批记录
*/
private CwApprovalRecord createApprovalRecordForNode(Long applicationId, String applicationNo,
String nodeCode, Long createBy, String createByName,
Long deptId, String tenantId) {
String nodeCode, Long createBy, String createByName,
Long deptId, String tenantId) {
try {
String nodeName;
Integer nodeOrder;
@@ -1077,11 +1076,11 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
// 创建审批记录
CwApprovalRecord record = createApprovalRecord(applicationId, applicationNo,
nodeCode, nodeName, nodeOrder, "waiting",
createBy, createByName, deptId, tenantId);
nodeCode, nodeName, nodeOrder, "waiting",
createBy, createByName, deptId, tenantId);
log.info("✅ 动态创建审批记录:nodeCode={}, nodeName={}, approverId={}",
nodeCode, nodeName, record.getApproverId());
nodeCode, nodeName, record.getApproverId());
return record;
} catch (Exception e) {
@@ -1124,7 +1123,7 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
return checkApprovalPermission(applicationId, "dept_approve");
}
/**
/**
* 获取可选审批人列表(院领导)
* 修改为基于权限角色查找:查找具有cw:approval:leader权限的用户
*/
@@ -1135,46 +1134,22 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
List<Map<String, Object>> approvers = new ArrayList<>();
try {
// 使用原生SQL查询具有cw:approval:leader权限的用户
String sql = """
SELECT DISTINCT
u.user_id,
u.user_name,
u.nick_name,
COALESCE(d.dept_name, '未分配部门') as dept_name,
COALESCE(d.dept_id, 0) as dept_id
FROM sys_user u
LEFT JOIN sys_dept d ON u.dept_id = d.dept_id
INNER JOIN sys_user_role ur ON u.user_id = ur.user_id
INNER JOIN sys_role r ON ur.role_id = r.role_id
INNER JOIN sys_role_menu rm ON r.role_id = rm.role_id
INNER JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE m.perms = 'cw:approval:leader'
AND u.status = '0'
AND u.del_flag = '0'
AND r.status = '0'
ORDER BY u.user_name
""";
// 🔧 使用Mapper方法查询具有cw:approval:leader权限的用户
log.info("执行Mapper查询查找具有cw:approval:leader权限的用户");
log.info("执行权限查询SQL查找具有cw:approval:leader权限的用户");
// 直接调用Mapper方法,不需要手动转换结果
List<Map<String, Object>> results = baseMapper.selectAvailableApprovers();
// 执行SQL查询
List<Map<String, Object>> results = jdbcTemplate.queryForList(sql);
log.info("Mapper查询结果:找到 {} 个具有院领导审批权限的用户", results.size());
log.info("SQL查询结果:找到 {} 个具有院领导审批权限的用户", results.size());
// 转换查询结果为返回格式
for (Map<String, Object> row : results) {
Map<String, Object> approver = new HashMap<>();
approver.put("userId", row.get("user_id"));
approver.put("userName", row.get("user_name"));
approver.put("nickName", row.get("nick_name"));
approver.put("deptName", row.get("dept_name"));
approver.put("deptId", row.get("dept_id"));
approvers.add(approver);
// 直接使用查询结果,Mapper已经处理了字段映射
approvers.addAll(results);
// 记录查询到的审批人信息
for (Map<String, Object> approver : approvers) {
log.info("添加院领导审批人: userId={}, userName={}, nickName={}, deptName={}",
row.get("user_id"), row.get("user_name"), row.get("nick_name"), row.get("dept_name"));
approver.get("userId"), approver.get("userName"), approver.get("nickName"),
approver.get("deptName"));
}
// 如果基于权限查询没有找到用户,使用原有的部门查询作为备选方案
@@ -1230,11 +1205,10 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
// 1. 查找当前用户的待审批记录
CwApprovalRecord currentRecord = baseMapper.selectOne(
Wrappers.lambdaQuery(CwApprovalRecord.class)
.eq(CwApprovalRecord::getApplicationId, applicationId)
.eq(CwApprovalRecord::getApproverId, currentUserId)
.eq(CwApprovalRecord::getNodeStatus, "waiting")
);
Wrappers.lambdaQuery(CwApprovalRecord.class)
.eq(CwApprovalRecord::getApplicationId, applicationId)
.eq(CwApprovalRecord::getApproverId, currentUserId)
.eq(CwApprovalRecord::getNodeStatus, "waiting"));
if (currentRecord == null) {
throw new RuntimeException("未找到待审批记录或您没有审批权限");
@@ -1263,7 +1237,7 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
throw new RuntimeException("更新审批记录失败");
}
// 3. 更新项目申请状态
// 3. 更新项目申请状态
String newStatus;
if ("approve".equals(approveAction)) {
// 分管领导审批通过,整个审批流程完成
@@ -1292,7 +1266,8 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void financeApproval(Long applicationId, String approveAction, String approveOpinion, Long selectedApproverId) {
public void financeApproval(Long applicationId, String approveAction, String approveOpinion,
Long selectedApproverId) {
Long currentUserId = LoginHelper.getUserId();
log.info("🔍 财务审批开始:applicationId={}, userId={}, action={}, selectedApproverId={}",
applicationId, currentUserId, approveAction, selectedApproverId);
@@ -1306,24 +1281,22 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
log.info("🔍 查询财务审批记录:applicationId={}, nodeCode=finance_approve, nodeStatus=waiting", applicationId);
List<CwApprovalRecord> records = baseMapper.selectList(
Wrappers.lambdaQuery(CwApprovalRecord.class)
.eq(CwApprovalRecord::getApplicationId, applicationId)
.eq(CwApprovalRecord::getNodeCode, "finance_approve")
.eq(CwApprovalRecord::getNodeStatus, "waiting")
.orderByDesc(CwApprovalRecord::getCreateTime)
);
Wrappers.lambdaQuery(CwApprovalRecord.class)
.eq(CwApprovalRecord::getApplicationId, applicationId)
.eq(CwApprovalRecord::getNodeCode, "finance_approve")
.eq(CwApprovalRecord::getNodeStatus, "waiting")
.orderByDesc(CwApprovalRecord::getCreateTime));
if (records.isEmpty()) {
log.error("❌ 未找到财务审批记录:applicationId={}", applicationId);
// 查询所有相关记录用于调试
List<CwApprovalRecord> allRecords = baseMapper.selectList(
Wrappers.lambdaQuery(CwApprovalRecord.class)
.eq(CwApprovalRecord::getApplicationId, applicationId)
);
Wrappers.lambdaQuery(CwApprovalRecord.class)
.eq(CwApprovalRecord::getApplicationId, applicationId));
log.error("📋 该申请的所有审批记录:{}", allRecords.size());
for (CwApprovalRecord record : allRecords) {
log.error("📋 记录详情:nodeCode={}, nodeStatus={}, approverId={}",
record.getNodeCode(), record.getNodeStatus(), record.getApproverId());
record.getNodeCode(), record.getNodeStatus(), record.getApproverId());
}
throw new RuntimeException("未找到待审批记录或该申请已被处理");
}
@@ -1335,7 +1308,8 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
}
log.info("✅ 找到财务审批记录:recordId={}, nodeCode={}, nodeStatus={}, approverId={}",
currentRecord.getId(), currentRecord.getNodeCode(), currentRecord.getNodeStatus(), currentRecord.getApproverId());
currentRecord.getId(), currentRecord.getNodeCode(), currentRecord.getNodeStatus(),
currentRecord.getApproverId());
// 3. 更新当前审批记录
Date now = new Date();
@@ -1392,23 +1366,21 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
private void updateLeaderApproverAndStart(Long applicationId, Long selectedApproverId) {
// 1. 查找分管领导审批记录(先按waiting状态查找)
CwApprovalRecord leaderRecord = baseMapper.selectOne(
Wrappers.lambdaQuery(CwApprovalRecord.class)
.eq(CwApprovalRecord::getApplicationId, applicationId)
.eq(CwApprovalRecord::getNodeCode, "leader_approve")
.eq(CwApprovalRecord::getNodeStatus, "waiting")
);
Wrappers.lambdaQuery(CwApprovalRecord.class)
.eq(CwApprovalRecord::getApplicationId, applicationId)
.eq(CwApprovalRecord::getNodeCode, "leader_approve")
.eq(CwApprovalRecord::getNodeStatus, "waiting"));
// 2. 如果waiting状态未找到,尝试查找pending状态(兼容处理)
if (leaderRecord == null) {
leaderRecord = baseMapper.selectOne(
Wrappers.lambdaQuery(CwApprovalRecord.class)
.eq(CwApprovalRecord::getApplicationId, applicationId)
.eq(CwApprovalRecord::getNodeCode, "leader_approve")
.eq(CwApprovalRecord::getNodeStatus, "pending")
);
Wrappers.lambdaQuery(CwApprovalRecord.class)
.eq(CwApprovalRecord::getApplicationId, applicationId)
.eq(CwApprovalRecord::getNodeCode, "leader_approve")
.eq(CwApprovalRecord::getNodeStatus, "pending"));
}
// 3. 如果仍未找到,动态创建分管领导审批记录
// 3. 如果仍未找到,动态创建分管领导审批记录
if (leaderRecord == null) {
log.warn("未找到分管领导审批记录,开始动态创建:applicationId={}", applicationId);
@@ -1426,17 +1398,16 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
// 创建分管领导审批记录
leaderRecord = createApprovalRecord(
applicationId,
applicationVo.getApplicationNo(),
"leader_approve",
"分管领导审批",
3, // leader_approve 节点顺序
"waiting",
currentUserId,
currentUserName,
currentDeptId,
currentTenantId
);
applicationId,
applicationVo.getApplicationNo(),
"leader_approve",
"分管领导审批",
3, // leader_approve 节点顺序
"waiting",
currentUserId,
currentUserName,
currentDeptId,
currentTenantId);
// 保存到数据库
int result = baseMapper.insert(leaderRecord);
@@ -1447,7 +1418,7 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
log.info("✅ 动态创建分管领导审批记录成功:recordId={}", leaderRecord.getId());
}
// 2. 获取选择的院领导信息
// 2. 获取选择的院领导信息
try {
SysUserVo selectedUser = sysUserService.selectUserById(selectedApproverId);
if (selectedUser == null) {
@@ -1470,12 +1441,12 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
throw new RuntimeException("更新分管领导审批记录失败");
}
log.info("✅ 成功指定院领导审批人:userId={}, userName={}",
log.info("✅ 成功指定院领导审批人:userId={}, userName={}",
selectedApproverId, selectedUser.getUserName());
} catch (Exception e) {
log.error("❌ 更新分管领导审批人失败:applicationId={}, selectedApproverId={}, error={}",
applicationId, selectedApproverId, e.getMessage(), e);
applicationId, selectedApproverId, e.getMessage(), e);
throw new RuntimeException("指定院领导审批人失败:" + e.getMessage());
}
}
@@ -234,4 +234,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
ORDER BY ar.create_time DESC
</select>
<!-- 查询具有院领导审批权限的用户列表 -->
<select id="selectAvailableApprovers" resultType="java.util.Map">
SELECT DISTINCT
u.user_id as userId,
u.user_name as userName,
u.nick_name as nickName,
COALESCE(d.dept_name, '未分配部门') as deptName,
COALESCE(d.dept_id, 0) as deptId
FROM sys_user u
LEFT JOIN sys_dept d ON u.dept_id = d.dept_id
INNER JOIN sys_user_role ur ON u.user_id = ur.user_id
INNER JOIN sys_role r ON ur.role_id = r.role_id
INNER JOIN sys_role_menu rm ON r.role_id = rm.role_id
INNER JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE m.perms = 'cw:approval:leader'
AND u.status = '0'
AND u.del_flag = '0'
AND r.status = '0'
ORDER BY u.user_name
</select>
</mapper>
@@ -70,6 +70,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
* 查询奖学金申请记录详情
*/
@Override
@Transactional(rollbackFor = Exception.class)
public JcbtScholarshipApplicationVo queryById(Long id) {
return baseMapper.selectByIdWithRelations(id);
}
@@ -78,10 +79,13 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
* 查询奖学金申请记录分页列表
*/
@Override
public TableDataInfo<JcbtScholarshipApplicationVo> queryPageList(JcbtScholarshipApplicationBo bo, PageQuery pageQuery) {
@Transactional(rollbackFor = Exception.class)
public TableDataInfo<JcbtScholarshipApplicationVo> queryPageList(JcbtScholarshipApplicationBo bo,
PageQuery pageQuery) {
LambdaQueryWrapper<JcbtScholarshipApplication> lqw = buildQueryWrapper(bo);
// 使用自定义分页查询(包含关联表信息)
Page<JcbtScholarshipApplicationVo> result = baseMapper.selectJcbtScholarshipApplicationPage(pageQuery.build(), lqw);
Page<JcbtScholarshipApplicationVo> result = baseMapper.selectJcbtScholarshipApplicationPage(pageQuery.build(),
lqw);
return TableDataInfo.build(result);
}
@@ -89,6 +93,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
* 查询奖学金申请记录列表
*/
@Override
@Transactional(rollbackFor = Exception.class)
public List<JcbtScholarshipApplicationVo> queryList(JcbtScholarshipApplicationBo bo) {
LambdaQueryWrapper<JcbtScholarshipApplication> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
@@ -98,18 +103,21 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<JcbtScholarshipApplication> lqw = Wrappers.lambdaQuery();
lqw.eq(ObjectUtil.isNotNull(bo.getBatchId()), JcbtScholarshipApplication::getBatchId, bo.getBatchId());
lqw.eq(ObjectUtil.isNotNull(bo.getScholarshipTypeId()), JcbtScholarshipApplication::getScholarshipTypeId, bo.getScholarshipTypeId());
lqw.eq(ObjectUtil.isNotNull(bo.getScholarshipTypeId()), JcbtScholarshipApplication::getScholarshipTypeId,
bo.getScholarshipTypeId());
lqw.eq(StringUtils.isNotBlank(bo.getStudentNo()), JcbtScholarshipApplication::getStudentNo, bo.getStudentNo());
lqw.like(StringUtils.isNotBlank(bo.getStudentName()), JcbtScholarshipApplication::getStudentName, bo.getStudentName());
lqw.like(StringUtils.isNotBlank(bo.getStudentName()), JcbtScholarshipApplication::getStudentName,
bo.getStudentName());
lqw.eq(ObjectUtil.isNotNull(bo.getStudentId()), JcbtScholarshipApplication::getStudentId, bo.getStudentId());
lqw.eq(ObjectUtil.isNotNull(bo.getDeptId()), JcbtScholarshipApplication::getDeptId, bo.getDeptId());
lqw.eq(ObjectUtil.isNotNull(bo.getClassId()), JcbtScholarshipApplication::getClassId, bo.getClassId());
// 已删除 applicantType 和 applicantId 字段
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), JcbtScholarshipApplication::getCurrentStatus, bo.getStatus());
// 支持前端 applicationStatus 查询参数
lqw.eq(StringUtils.isNotBlank(bo.getApplicationStatus()), JcbtScholarshipApplication::getCurrentStatus, bo.getApplicationStatus());
lqw.eq(StringUtils.isNotBlank(bo.getApplicationStatus()), JcbtScholarshipApplication::getCurrentStatus,
bo.getApplicationStatus());
lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
JcbtScholarshipApplication::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
JcbtScholarshipApplication::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
// 注意:排序在XML中处理,避免SQL语法错误
// lqw.orderByDesc(JcbtScholarshipApplication::getCreateTime);
return lqw;
@@ -122,7 +130,8 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(JcbtScholarshipApplicationBo bo) {
// 检查申请资格
Map<String, Object> eligibilityCheck = checkStudentEligibility(bo.getStudentId(), bo.getBatchId(), bo.getScholarshipTypeId());
Map<String, Object> eligibilityCheck = checkStudentEligibility(bo.getStudentId(), bo.getBatchId(),
bo.getScholarshipTypeId());
if (!(Boolean) eligibilityCheck.get("eligible")) {
throw new RuntimeException((String) eligibilityCheck.get("message"));
}
@@ -254,15 +263,14 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean batchSubmitApplicationsByTeacher(Long batchId, Long scholarshipTypeId, Long classId,
List<JcbtScholarshipApplicationBo> studentApplications) {
List<JcbtScholarshipApplicationBo> studentApplications) {
// 检查班级名额 - 使用和前端一致的查询方法
log.info("开始检查班级名额 - 批次ID: {}, 奖学金类型ID: {}, 班级ID: {}, 申请人数: {}",
batchId, scholarshipTypeId, classId, studentApplications.size());
// 使用和前端getScholarshipTypesWithQuota一致的查询方法
JcbtScholarshipQuotaVo classQuota = quotaService.selectClassQuota(classId, scholarshipTypeId, batchId);
log.info("班级名额查询结果: {}", classQuota != null ?
String.format("ID=%s, 总名额=%s, 已用=%s, 可用=%s, 状态=%s",
log.info("班级名额查询结果: {}", classQuota != null ? String.format("ID=%s, 总名额=%s, 已用=%s, 可用=%s, 状态=%s",
classQuota.getId(), classQuota.getTotalQuota(), classQuota.getUsedQuota(),
classQuota.getAvailableQuota(), classQuota.getStatus()) : "null");
@@ -306,7 +314,8 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
if (classQuota != null) {
// 有班级名额分配,更新班级名额
log.info("更新班级名额使用情况 - ID: {}, 原已用: {}, 原可用: {}, 新增申请: {}",
classQuota.getId(), classQuota.getUsedQuota(), classQuota.getAvailableQuota(), studentApplications.size());
classQuota.getId(), classQuota.getUsedQuota(), classQuota.getAvailableQuota(),
studentApplications.size());
// 注意:这里需要调用全校名额表的更新方法,而不是班级名额表的方法
quotaService.updateQuotaUsage(classQuota.getId(), studentApplications.size());
@@ -334,6 +343,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
* 查询班主任负责班级的申请记录
*/
@Override
@Transactional(rollbackFor = Exception.class)
public List<JcbtScholarshipApplicationVo> queryApplicationsByApplicant(Long applicantId, Long batchId) {
// TODO: 重新设计查询逻辑,因为applicantId字段已删除
// 可能需要通过班级ID或其他方式查询班主任负责的申请记录
@@ -344,6 +354,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
* 查询班级的申请记录
*/
@Override
@Transactional(rollbackFor = Exception.class)
public List<JcbtScholarshipApplicationVo> queryApplicationsByClass(Long classId, Long batchId) {
return baseMapper.selectApplicationsByClass(classId, batchId);
}
@@ -352,6 +363,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
* 查询待审核的申请记录
*/
@Override
@Transactional(rollbackFor = Exception.class)
public List<JcbtScholarshipApplicationVo> queryPendingApplications(Long reviewerId, String status) {
return baseMapper.selectPendingApplications(reviewerId, status);
}
@@ -360,6 +372,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
* 检查学生申请资格和限制
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> checkStudentEligibility(Long studentId, Long batchId, Long scholarshipTypeId) {
Map<String, Object> result = new HashMap<>();
@@ -383,7 +396,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean batchReviewApplications(List<Long> applicationIds, String status, Long reviewerId,
String reviewerName, String reviewComment) {
String reviewerName, String reviewComment) {
return baseMapper.batchUpdateStatus(applicationIds, status, reviewerId, reviewerName, reviewComment) > 0;
}
@@ -429,6 +442,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
* 获取申请统计数据
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> getApplicationStatistics(Long batchId, Long deptId) {
List<JcbtScholarshipApplicationVo> statistics = baseMapper.selectApplicationStatistics(batchId, deptId);
@@ -443,6 +457,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
* 查询可申请的奖学金类型列表
*/
@Override
@Transactional(rollbackFor = Exception.class)
public List<Map<String, Object>> queryAvailableScholarshipTypes(Long classId, Long batchId) {
// 查询该班级在该批次的名额分配情况
List<JcbtScholarshipClassQuotaVo> quotas = classQuotaService.queryQuotasByClass(classId, batchId);
@@ -466,6 +481,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
* 获取班级名额使用情况
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> getClassQuotaUsage(Long classId, Long batchId) {
List<JcbtScholarshipClassQuotaVo> quotas = classQuotaService.queryQuotasByClass(classId, batchId);
@@ -485,7 +501,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
// ========== 班主任申请专用接口实现 ==========
/**
/**
* 根据班主任用户ID获取完整的班级信息
* 自包含方法,避免跨模块调用,直接使用SQL查询
*/
@@ -493,26 +509,26 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
try {
// 使用自定义SQL一次性关联查询所有需要的信息
String sql = """
SELECT
cm.id as class_id,
cm.class_name,
cm.class_code,
cm.grade,
cm.college_id,
cm.college_name,
cm.major_id,
cm.major_name,
cm.class_teacher_id,
cm.class_teacher_name,
cm.current_student_count,
cm.class_status,
cm.class_type
FROM xg_class_msg cm
WHERE cm.class_teacher_id = ?
AND cm.deleted = 0
AND cm.class_status = '1'
ORDER BY cm.grade DESC, cm.class_name ASC
""";
SELECT
cm.id as class_id,
cm.class_name,
cm.class_code,
cm.grade,
cm.college_id,
cm.college_name,
cm.major_id,
cm.major_name,
cm.class_teacher_id,
cm.class_teacher_name,
cm.current_student_count,
cm.class_status,
cm.class_type
FROM xg_class_msg cm
WHERE cm.class_teacher_id = ?
AND cm.deleted = 0
AND cm.class_status = '1'
ORDER BY cm.grade DESC, cm.class_name ASC
""";
// 执行查询
List<Map<String, Object>> result = jdbcTemplate.queryForList(sql, teacherId);
@@ -530,6 +546,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
* 重构后的获取班级信息方法 - 自包含,无跨模块依赖
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> getClassInfoByTeacher(Long userId) {
Map<String, Object> result = new HashMap<>();
@@ -582,8 +599,9 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
/**
* 从Map构建班级结果信息
*/
private void buildClassResultFromMap(Map<String, Object> result, Long teacherId, String teacherName, Map<String, Object> classInfo) {
result.put("teacherId", teacherId.toString());
private void buildClassResultFromMap(Map<String, Object> result, Long teacherId, String teacherName,
Map<String, Object> classInfo) {
result.put("teacherId", teacherId.toString());
result.put("teacherName", teacherName);
result.put("classId", classInfo.get("class_id") != null ? classInfo.get("class_id").toString() : "");
result.put("className", (String) classInfo.get("class_name"));
@@ -613,36 +631,37 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
return option;
}
/**
/**
* 确认选择班级(当用户从多个班级中选择一个时调用)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> confirmClassSelection(Long teacherId, Long classId) {
Map<String, Object> result = new HashMap<>();
try {
// 验证该班级确实属于该老师 - 使用自包含查询
String sql = """
SELECT
cm.id as class_id,
cm.class_name,
cm.class_code,
cm.grade,
cm.college_id,
cm.college_name,
cm.major_id,
cm.major_name,
cm.class_teacher_id,
cm.class_teacher_name,
cm.current_student_count,
cm.class_status,
cm.class_type
FROM xg_class_msg cm
WHERE cm.class_teacher_id = ?
AND cm.id = ?
AND cm.deleted = 0
AND cm.class_status = '1'
""";
SELECT
cm.id as class_id,
cm.class_name,
cm.class_code,
cm.grade,
cm.college_id,
cm.college_name,
cm.major_id,
cm.major_name,
cm.class_teacher_id,
cm.class_teacher_name,
cm.current_student_count,
cm.class_status,
cm.class_type
FROM xg_class_msg cm
WHERE cm.class_teacher_id = ?
AND cm.id = ?
AND cm.deleted = 0
AND cm.class_status = '1'
""";
List<Map<String, Object>> classList = jdbcTemplate.queryForList(sql, teacherId, classId);
@@ -663,38 +682,39 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
}
}
/**
/**
* 获取班级学生列表(用于勾选)- 使用自包含查询
*/
@Override
@Transactional(rollbackFor = Exception.class)
public List<Map<String, Object>> getClassStudents(Long classId) {
List<Map<String, Object>> students = new ArrayList<>();
try {
// 使用自包含查询获取学生列表,关联班级表获取学院和专业信息
String sql = """
SELECT
sm.id,
sm.student_no,
sm.name,
sm.gender,
sm.phone,
sm.email,
sm.student_status,
cm.college_id,
cm.college_name,
cm.major_id,
cm.major_name,
cm.class_name,
cm.grade
FROM xg_student_msg sm
LEFT JOIN xg_class_msg cm ON sm.class_id = cm.id
WHERE sm.class_id = ?
AND sm.deleted = 0
AND sm.student_status = '1'
AND (cm.deleted IS NULL OR cm.deleted = 0)
ORDER BY sm.student_no ASC
""";
SELECT
sm.id,
sm.student_no,
sm.name,
sm.gender,
sm.phone,
sm.email,
sm.student_status,
cm.college_id,
cm.college_name,
cm.major_id,
cm.major_name,
cm.class_name,
cm.grade
FROM xg_student_msg sm
LEFT JOIN xg_class_msg cm ON sm.class_id = cm.id
WHERE sm.class_id = ?
AND sm.deleted = 0
AND sm.student_status = '1'
AND (cm.deleted IS NULL OR cm.deleted = 0)
ORDER BY sm.student_no ASC
""";
List<Map<String, Object>> studentList = jdbcTemplate.queryForList(sql, classId);
@@ -702,7 +722,8 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
for (Map<String, Object> student : studentList) {
Map<String, Object> studentMap = new HashMap<>();
studentMap.put("id", student.get("id"));
studentMap.put("studentCode", student.get("student_no") != null ? student.get("student_no").toString() : "");
studentMap.put("studentCode",
student.get("student_no") != null ? student.get("student_no").toString() : "");
studentMap.put("studentName", student.get("name") != null ? student.get("name").toString() : "");
// 性别转换:0-男,1-女
@@ -715,14 +736,20 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
studentMap.put("phone", student.get("phone") != null ? student.get("phone").toString() : "");
studentMap.put("email", student.get("email") != null ? student.get("email").toString() : "");
studentMap.put("status", student.get("student_status") != null ? student.get("student_status").toString() : "1");
studentMap.put("status",
student.get("student_status") != null ? student.get("student_status").toString() : "1");
// 添加学院和专业信息
studentMap.put("collegeId", student.get("college_id") != null ? student.get("college_id").toString() : "");
studentMap.put("collegeName", student.get("college_name") != null ? student.get("college_name").toString() : "");
studentMap.put("majorId", student.get("major_id") != null ? student.get("major_id").toString() : "");
studentMap.put("majorName", student.get("major_name") != null ? student.get("major_name").toString() : "");
studentMap.put("className", student.get("class_name") != null ? student.get("class_name").toString() : "");
studentMap.put("collegeId",
student.get("college_id") != null ? student.get("college_id").toString() : "");
studentMap.put("collegeName",
student.get("college_name") != null ? student.get("college_name").toString() : "");
studentMap.put("majorId",
student.get("major_id") != null ? student.get("major_id").toString() : "");
studentMap.put("majorName",
student.get("major_name") != null ? student.get("major_name").toString() : "");
studentMap.put("className",
student.get("class_name") != null ? student.get("class_name").toString() : "");
studentMap.put("grade", student.get("grade") != null ? student.get("grade").toString() : "");
students.add(studentMap);
@@ -739,10 +766,11 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
}
}
/**
/**
* 根据批次获取可申请的奖学金类型
*/
@Override
@Transactional(rollbackFor = Exception.class)
public List<Map<String, Object>> getScholarshipTypesByBatch(Long batchId) {
List<Map<String, Object>> scholarshipTypes = new ArrayList<>();
@@ -752,21 +780,24 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
if (batchTypeIds != null && !batchTypeIds.isEmpty()) {
for (Long scholarshipTypeId : batchTypeIds) {
// 查询奖学金类型基本信息
JcbtScholarshipTypeVo typeInfo = scholarshipTypeService.queryById(scholarshipTypeId);
// 查询奖学金类型基本信息
JcbtScholarshipTypeVo typeInfo = scholarshipTypeService.queryById(scholarshipTypeId);
if (typeInfo != null && typeInfo.getStatus() == 1) { // 只返回启用的类型
Map<String, Object> scholarshipType = new HashMap<>();
scholarshipType.put("id", scholarshipTypeId);
scholarshipType.put("name", typeInfo.getTypeName());
scholarshipType.put("amount", typeInfo.getAwardAmount());
scholarshipType.put("description", typeInfo.getDescription());
scholarshipType.put("typeCategory", typeInfo.getTypeCategory());
scholarshipType.put("levelType", typeInfo.getLevelType());
Map<String, Object> scholarshipType = new HashMap<>();
scholarshipType.put("id", scholarshipTypeId);
scholarshipType.put("name", typeInfo.getTypeName());
scholarshipType.put("amount", typeInfo.getAwardAmount());
scholarshipType.put("description", typeInfo.getDescription());
scholarshipType.put("typeCategory", typeInfo.getTypeCategory());
scholarshipType.put("levelType", typeInfo.getLevelType());
// 查询该类型在该批次下的名额统计信息
List<JcbtScholarshipQuotaVo> quotaList = quotaService.queryListByBatchAndType(batchId, scholarshipTypeId);
int totalQuota = quotaList.stream().mapToInt(q -> q.getTotalQuota() != null ? q.getTotalQuota() : 0).sum();
int usedQuota = quotaList.stream().mapToInt(q -> q.getUsedQuota() != null ? q.getUsedQuota() : 0).sum();
List<JcbtScholarshipQuotaVo> quotaList = quotaService.queryListByBatchAndType(batchId,
scholarshipTypeId);
int totalQuota = quotaList.stream()
.mapToInt(q -> q.getTotalQuota() != null ? q.getTotalQuota() : 0).sum();
int usedQuota = quotaList.stream()
.mapToInt(q -> q.getUsedQuota() != null ? q.getUsedQuota() : 0).sum();
int availableQuota = totalQuota - usedQuota;
scholarshipType.put("totalQuota", totalQuota);
@@ -805,6 +836,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
* 根据批次和班级获取带名额信息的奖学金类型(班主任专用)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public List<Map<String, Object>> getScholarshipTypesWithQuota(Long batchId, Long classId) {
List<Map<String, Object>> scholarshipTypes = new ArrayList<>();
@@ -822,8 +854,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
// 查询奖学金类型基本信息
JcbtScholarshipTypeVo typeInfo = scholarshipTypeService.queryById(scholarshipTypeId);
log.info("奖学金类型基本信息: {}", typeInfo != null ?
String.format("ID=%s, 名称=%s, 状态=%s, 金额=%s, 等级=%s",
log.info("奖学金类型基本信息: {}", typeInfo != null ? String.format("ID=%s, 名称=%s, 状态=%s, 金额=%s, 等级=%s",
typeInfo.getId(), typeInfo.getTypeName(), typeInfo.getStatus(),
typeInfo.getAwardAmount(), typeInfo.getLevelType()) : "null");
@@ -837,14 +868,18 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
scholarshipType.put("levelType", typeInfo.getLevelType());
// 查询该班级在该奖学金类型下的名额分配信息(从jcbt_scholarship_quota表查询)
log.info("开始查询班级名额 - SQL参数: classId={}, scholarshipTypeId={}, batchId={}", classId, scholarshipTypeId, batchId);
log.info("执行SQL: SELECT * FROM jcbt_scholarship_quota WHERE allocation_level = 'CLASS' AND target_dept_id = {} AND scholarship_type_id = {} AND batch_id = {} AND status = 'ACTIVE'", classId, scholarshipTypeId, batchId);
log.info("开始查询班级名额 - SQL参数: classId={}, scholarshipTypeId={}, batchId={}", classId,
scholarshipTypeId, batchId);
log.info(
"执行SQL: SELECT * FROM jcbt_scholarship_quota WHERE allocation_level = 'CLASS' AND target_dept_id = {} AND scholarship_type_id = {} AND batch_id = {} AND status = 'ACTIVE'",
classId, scholarshipTypeId, batchId);
JcbtScholarshipQuotaVo classQuota = quotaService.selectClassQuota(classId, scholarshipTypeId, batchId);
log.info("班级名额查询结果: {}", classQuota != null ?
String.format("ID=%s, 总名额=%s, 已用=%s, 可用=%s, 状态=%s",
classQuota.getId(), classQuota.getTotalQuota(), classQuota.getUsedQuota(),
classQuota.getAvailableQuota(), classQuota.getStatus()) : "null");
JcbtScholarshipQuotaVo classQuota = quotaService.selectClassQuota(classId, scholarshipTypeId,
batchId);
log.info("班级名额查询结果: {}",
classQuota != null ? String.format("ID=%s, 总名额=%s, 已用=%s, 可用=%s, 状态=%s",
classQuota.getId(), classQuota.getTotalQuota(), classQuota.getUsedQuota(),
classQuota.getAvailableQuota(), classQuota.getStatus()) : "null");
if (classQuota != null) {
// 有班级名额分配
@@ -852,7 +887,8 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
scholarshipType.put("hasQuota", true);
int totalQuota = classQuota.getTotalQuota() != null ? classQuota.getTotalQuota() : 0;
int usedQuota = classQuota.getUsedQuota() != null ? classQuota.getUsedQuota() : 0;
int availableQuota = classQuota.getAvailableQuota() != null ? classQuota.getAvailableQuota() : Math.max(0, totalQuota - usedQuota);
int availableQuota = classQuota.getAvailableQuota() != null ? classQuota.getAvailableQuota()
: Math.max(0, totalQuota - usedQuota);
scholarshipType.put("totalQuota", totalQuota);
scholarshipType.put("usedQuota", usedQuota);
@@ -860,44 +896,49 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
scholarshipType.put("quotaStatus", availableQuota > 0 ? "available" : "full");
log.info("班级名额处理结果 - 类型: {}, 总名额: {}, 已用: {}, 可用: {}, 状态: {}",
typeInfo.getTypeName(), totalQuota, usedQuota, availableQuota, availableQuota > 0 ? "available" : "full");
typeInfo.getTypeName(), totalQuota, usedQuota, availableQuota,
availableQuota > 0 ? "available" : "full");
} else {
// 没有班级名额分配,查询全校统计信息
log.info("未找到班级名额分配,开始查询全校名额");
log.info("执行全校名额查询SQL - 参数: batchId={}, scholarshipTypeId={}", batchId, scholarshipTypeId);
List<JcbtScholarshipQuotaVo> quotaList = quotaService.queryListByBatchAndType(batchId, scholarshipTypeId);
List<JcbtScholarshipQuotaVo> quotaList = quotaService.queryListByBatchAndType(batchId,
scholarshipTypeId);
log.info("全校名额查询结果数量: {}", quotaList != null ? quotaList.size() : 0);
if (quotaList != null && !quotaList.isEmpty()) {
for (int i = 0; i < quotaList.size(); i++) {
JcbtScholarshipQuotaVo quota = quotaList.get(i);
log.info("全校名额[{}]: ID={}, 分配级别={}, 目标部门={}, 总名额={}, 已用={}, 可用={}",
i, quota.getId(), quota.getAllocationLevel(), quota.getTargetDeptName(),
quota.getTotalQuota(), quota.getUsedQuota(), quota.getAvailableQuota());
i, quota.getId(), quota.getAllocationLevel(), quota.getTargetDeptName(),
quota.getTotalQuota(), quota.getUsedQuota(), quota.getAvailableQuota());
}
}
int totalQuota = quotaList.stream().mapToInt(q -> q.getTotalQuota() != null ? q.getTotalQuota() : 0).sum();
int usedQuota = quotaList.stream().mapToInt(q -> q.getUsedQuota() != null ? q.getUsedQuota() : 0).sum();
int totalQuota = quotaList.stream()
.mapToInt(q -> q.getTotalQuota() != null ? q.getTotalQuota() : 0).sum();
int usedQuota = quotaList.stream()
.mapToInt(q -> q.getUsedQuota() != null ? q.getUsedQuota() : 0).sum();
int availableQuota = Math.max(0, totalQuota - usedQuota);
scholarshipType.put("hasQuota", false);
scholarshipType.put("totalQuota", totalQuota);
scholarshipType.put("usedQuota", usedQuota);
scholarshipType.put("availableQuota", availableQuota);
scholarshipType.put("quotaStatus", totalQuota > 0 ? (availableQuota > 0 ? "available" : "full") : "unlimited");
scholarshipType.put("quotaStatus",
totalQuota > 0 ? (availableQuota > 0 ? "available" : "full") : "unlimited");
log.info("全校名额统计结果 - 类型: {} (全校), 总名额: {}, 已用: {}, 可用: {}, 状态: {}",
typeInfo.getTypeName(), totalQuota, usedQuota, availableQuota,
totalQuota > 0 ? (availableQuota > 0 ? "available" : "full") : "unlimited");
typeInfo.getTypeName(), totalQuota, usedQuota, availableQuota,
totalQuota > 0 ? (availableQuota > 0 ? "available" : "full") : "unlimited");
}
scholarshipTypes.add(scholarshipType);
log.info("奖学金类型 {} 处理完成,添加到结果列表", typeInfo.getTypeName());
} else {
log.warn("奖学金类型被跳过 - ID: {}, 原因: {}", scholarshipTypeId,
typeInfo == null ? "类型信息不存在" : "状态未启用(status=" + typeInfo.getStatus() + ")");
typeInfo == null ? "类型信息不存在" : "状态未启用(status=" + typeInfo.getStatus() + ")");
}
}
@@ -928,7 +969,8 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
for (int i = 0; i < scholarshipTypes.size(); i++) {
Map<String, Object> type = scholarshipTypes.get(i);
log.info("结果[{}]: 名称={}, 状态={}, 总名额={}, 可用名额={}",
i, type.get("name"), type.get("quotaStatus"), type.get("totalQuota"), type.get("availableQuota"));
i, type.get("name"), type.get("quotaStatus"), type.get("totalQuota"),
type.get("availableQuota"));
}
} else {
log.warn("批次 {} 未关联任何奖学金类型", batchId);
@@ -948,6 +990,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
* 测试班级名额查询(用于SQL日志调试)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> testClassQuotaQuery(Long classId, Long scholarshipTypeId, Long batchId) {
Map<String, Object> result = new HashMap<>();
@@ -960,7 +1003,8 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
// 执行查询
log.info("💾 执行班级名额查询...");
JcbtScholarshipClassQuotaVo classQuota = classQuotaService.queryAvailableQuota(classId, scholarshipTypeId, batchId);
JcbtScholarshipClassQuotaVo classQuota = classQuotaService.queryAvailableQuota(classId, scholarshipTypeId,
batchId);
// 记录查询结束时间
long endTime = System.currentTimeMillis();
@@ -971,23 +1015,24 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
if (classQuota != null) {
log.info("✅ 查询成功,找到班级名额记录");
log.info("📊 名额详情: ID={}, 批次ID={}, 奖学金类型ID={}, 班级ID={}",
classQuota.getId(), classQuota.getBatchId(), classQuota.getScholarshipTypeId(), classQuota.getClassId());
classQuota.getId(), classQuota.getBatchId(), classQuota.getScholarshipTypeId(),
classQuota.getClassId());
log.info("📈 名额数据: 总名额={}, 已用名额={}, 可用名额={}, 状态={}",
classQuota.getTotalQuota(), classQuota.getUsedQuota(), classQuota.getAvailableQuota(), classQuota.getStatus());
classQuota.getTotalQuota(), classQuota.getUsedQuota(), classQuota.getAvailableQuota(),
classQuota.getStatus());
result.put("found", true);
result.put("data", Map.of(
"id", classQuota.getId(),
"batchId", classQuota.getBatchId(),
"scholarshipTypeId", classQuota.getScholarshipTypeId(),
"classId", classQuota.getClassId(),
"totalQuota", classQuota.getTotalQuota(),
"usedQuota", classQuota.getUsedQuota(),
"availableQuota", classQuota.getAvailableQuota(),
"status", classQuota.getStatus(),
"createTime", classQuota.getCreateTime(),
"updateTime", classQuota.getUpdateTime()
));
"id", classQuota.getId(),
"batchId", classQuota.getBatchId(),
"scholarshipTypeId", classQuota.getScholarshipTypeId(),
"classId", classQuota.getClassId(),
"totalQuota", classQuota.getTotalQuota(),
"usedQuota", classQuota.getUsedQuota(),
"availableQuota", classQuota.getAvailableQuota(),
"status", classQuota.getStatus(),
"createTime", classQuota.getCreateTime(),
"updateTime", classQuota.getUpdateTime()));
} else {
log.warn("⚠️ 未找到班级名额记录");
log.info("🔍 可能原因:");
@@ -1033,6 +1078,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
* 测试全校名额查询(用于SQL日志调试)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> testSchoolQuotaQuery(Long batchId, Long scholarshipTypeId) {
Map<String, Object> result = new HashMap<>();
@@ -1063,9 +1109,11 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
for (int i = 0; i < quotaList.size(); i++) {
JcbtScholarshipQuotaVo quota = quotaList.get(i);
log.info("📈 记录[{}]: ID={}, 分配级别={}, 目标部门ID={}, 目标部门={}",
i, quota.getId(), quota.getAllocationLevel(), quota.getTargetDeptId(), quota.getTargetDeptName());
i, quota.getId(), quota.getAllocationLevel(), quota.getTargetDeptId(),
quota.getTargetDeptName());
log.info("📊 名额[{}]: 总名额={}, 已用={}, 预留={}, 可用={}, 状态={}",
i, quota.getTotalQuota(), quota.getUsedQuota(), quota.getReservedQuota(), quota.getAvailableQuota(), quota.getStatus());
i, quota.getTotalQuota(), quota.getUsedQuota(), quota.getReservedQuota(),
quota.getAvailableQuota(), quota.getStatus());
Map<String, Object> quotaDetail = new HashMap<>();
quotaDetail.put("id", quota.getId());
@@ -1099,11 +1147,10 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
result.put("count", quotaList.size());
result.put("details", quotaDetails);
result.put("summary", Map.of(
"totalQuota", totalQuota,
"usedQuota", usedQuota,
"availableQuota", availableQuota,
"calculatedAvailable", Math.max(0, totalQuota - usedQuota)
));
"totalQuota", totalQuota,
"usedQuota", usedQuota,
"availableQuota", availableQuota,
"calculatedAvailable", Math.max(0, totalQuota - usedQuota)));
} else {
log.warn("⚠️ 未找到全校名额记录");
@@ -23,6 +23,7 @@ import org.dromara.xg.domain.bo.XgDormBedBatchAllocateBo;
import org.dromara.xg.service.IXgDormBedStudentService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.xg.domain.bo.XgDormBedBatchRecallBo;
import org.dromara.xg.domain.vo.DormBuildingTreeVo;
/**
* 床位学生分配关系
@@ -126,4 +127,15 @@ public class XgDormBedStudentController extends BaseController {
public R<Void> batchRecallBeds(@Validated(AddGroup.class) @RequestBody XgDormBedBatchRecallBo bo) {
return toAjax(xgDormBedStudentService.batchRecallBeds(bo));
}
/**
* 获取当前用户二级学院的宿舍楼树形结构
* 校区-宿舍区-宿舍楼三级结构,校区和宿舍区不可选,只有宿舍楼可以选择
*/
@SaCheckPermission("dormBedStudent:dormBedStudent:list")
@GetMapping("/currentUserCollegeDormTree")
public R<List<DormBuildingTreeVo>> getCurrentUserCollegeDormBuildingTree() {
List<DormBuildingTreeVo> treeData = xgDormBedStudentService.getCurrentUserCollegeDormBuildingTree();
return R.ok(treeData);
}
}
@@ -1286,4 +1286,15 @@ public class XgStudentMsgController extends BaseController {
List<XgStudentTreeVo> studentTree = xgStudentMsgService.getStudentTree(secondaryCollegeId);
return R.ok(studentTree);
}
/**
* 获取当前用户所在学院的学生信息树
*/
@SaCheckPermission("studentMsg:studentMsg:list")
@GetMapping("/getCurrentUserStudentTree")
public R<List<XgStudentTreeVo>> getCurrentUserStudentTree() {
log.info("获取当前用户所在学院的学生信息树");
List<XgStudentTreeVo> studentTree = xgStudentMsgService.getCurrentUserStudentTree();
return R.ok(studentTree);
}
}
@@ -10,6 +10,7 @@ import java.util.Collection;
import java.util.List;
import org.dromara.xg.domain.bo.XgDormBedBatchRecallBo;
import org.dromara.xg.domain.vo.DormBuildingTreeVo;
/**
* 床位学生分配关系Service接口
@@ -85,4 +86,12 @@ public interface IXgDormBedStudentService {
* @return 是否成功
*/
Boolean batchRecallBeds(XgDormBedBatchRecallBo bo);
/**
* 获取当前用户二级学院的宿舍楼树形结构
* 校区-宿舍区-宿舍楼三级结构,校区和宿舍区不可选,只有宿舍楼可以选择
*
* @return 宿舍楼树形结构数据
*/
List<DormBuildingTreeVo> getCurrentUserCollegeDormBuildingTree();
}
@@ -161,4 +161,11 @@ public interface IXgStudentMsgService {
* @return 学生信息树
*/
List<XgStudentTreeVo> getStudentTree(Long secondaryCollegeId);
/**
* 获取当前用户所在学院的学生信息树
*
* @return 学生信息树
*/
List<XgStudentTreeVo> getCurrentUserStudentTree();
}
@@ -36,6 +36,15 @@ import org.dromara.xg.mapper.XgStudentMsgMapper;
import org.dromara.xg.domain.XgStudentMsg;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.xg.domain.vo.DormBuildingTreeVo;
import org.dromara.xg.domain.XgDormCampus;
import org.dromara.xg.domain.XgDormArea;
import org.dromara.xg.mapper.XgDormCampusMapper;
import org.dromara.xg.mapper.XgDormAreaMapper;
import org.dromara.xg.mapper.XgSysDeptMapper;
import org.dromara.xg.domain.XgSysDept;
import java.util.stream.Collectors;
import java.util.LinkedHashMap;
/**
* 床位学生分配关系Service业务层处理
@@ -53,6 +62,9 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService {
private final XgDormBuildingMapper xgDormBuildingMapper;
private final XgDormRoomMapper xgDormRoomMapper;
private final XgStudentMsgMapper xgStudentMsgMapper;
private final XgDormCampusMapper xgDormCampusMapper;
private final XgDormAreaMapper xgDormAreaMapper;
private final XgSysDeptMapper xgSysDeptMapper;
/**
* 查询床位学生分配关系
@@ -491,4 +503,230 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService {
return true;
}
/**
* 获取当前用户二级学院的宿舍楼树形结构
* 校区-宿舍区-宿舍楼三级结构,校区和宿舍区不可选,只有宿舍楼可以选择
*
* @return 宿舍楼树形结构数据
*/
@Override
public List<DormBuildingTreeVo> getCurrentUserCollegeDormBuildingTree() {
try {
// 1. 获取当前用户的二级学院ID
Long collegeId = getCurrentUserCollegeId();
if (collegeId == null) {
log.warn("无法获取当前用户的二级学院信息");
return new ArrayList<>();
}
// 2. 通过xg_dorm_bed_student表获取分配给该学院的宿舍楼ID列表
Set<Long> buildingIds = getCollegeAllocatedBuildingIds(collegeId);
if (buildingIds.isEmpty()) {
log.info("学院 {} 还没有分配任何宿舍楼", collegeId);
return new ArrayList<>();
}
// 3. 获取宿舍楼信息并构建树形结构
return buildDormBuildingTree(buildingIds);
} catch (Exception e) {
log.error("获取当前用户二级学院宿舍楼树失败", e);
return new ArrayList<>();
}
}
/**
* 获取当前用户的二级学院ID
* 通过当前用户的部门id,向上寻找包括他自己,找到dept_category前几位是xy-的就是二级学院
* 或者parent_id = 101的部门就是二级学院
*/
private Long getCurrentUserCollegeId() {
try {
Long currentUserId = LoginHelper.getUserId();
Long currentDeptId = LoginHelper.getDeptId();
if (currentUserId == null || currentDeptId == null) {
log.warn("用户未登录或没有部门信息");
return null;
}
// 首先查找parent_id = 101的部门(使用LambdaQueryWrapper)
LambdaQueryWrapper<XgSysDept> parentQuery = Wrappers.lambdaQuery();
parentQuery.eq(XgSysDept::getParentId, 101L)
.eq(XgSysDept::getStatus, "0")
.eq(XgSysDept::getDelFlag, "0")
.and(wrapper -> wrapper.eq(XgSysDept::getDeptId, currentDeptId)
.or().like(XgSysDept::getAncestors, "," + currentDeptId + ",") // 中间位置
.or().likeRight(XgSysDept::getAncestors, currentDeptId + ",") // 开头位置
.or().likeLeft(XgSysDept::getAncestors, "," + currentDeptId) // 末尾位置
.or().eq(XgSysDept::getAncestors, currentDeptId.toString())) // 单独一个值
.last("LIMIT 1");
List<XgSysDept> parentColleges = xgSysDeptMapper.selectList(parentQuery);
if (!parentColleges.isEmpty()) {
return parentColleges.get(0).getDeptId();
}
// 如果上面的查询没找到,尝试通过dept_category查找
LambdaQueryWrapper<XgSysDept> categoryQuery = Wrappers.lambdaQuery();
categoryQuery.likeRight(XgSysDept::getDeptCategory, "xy-")
.eq(XgSysDept::getStatus, "0")
.eq(XgSysDept::getDelFlag, "0")
.and(wrapper -> wrapper.eq(XgSysDept::getDeptId, currentDeptId)
.or().like(XgSysDept::getAncestors, "," + currentDeptId + ",") // 中间位置
.or().likeRight(XgSysDept::getAncestors, currentDeptId + ",") // 开头位置
.or().likeLeft(XgSysDept::getAncestors, "," + currentDeptId) // 末尾位置
.or().eq(XgSysDept::getAncestors, currentDeptId.toString())) // 单独一个值
.last("LIMIT 1");
List<XgSysDept> categoryColleges = xgSysDeptMapper.selectList(categoryQuery);
return categoryColleges.isEmpty() ? null : categoryColleges.get(0).getDeptId();
} catch (Exception e) {
log.error("获取当前用户二级学院ID失败", e);
return null;
}
}
/**
* 获取分配给指定学院的宿舍楼ID列表
*/
private Set<Long> getCollegeAllocatedBuildingIds(Long collegeId) {
try {
LambdaQueryWrapper<XgDormBedStudent> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.select(XgDormBedStudent::getBuildingId)
.eq(XgDormBedStudent::getCollegeId, collegeId)
.eq(XgDormBedStudent::getStatus, "0")
.eq(XgDormBedStudent::getDeleted, "0")
.isNotNull(XgDormBedStudent::getBuildingId)
.groupBy(XgDormBedStudent::getBuildingId);
List<XgDormBedStudent> buildingRecords = baseMapper.selectList(queryWrapper);
return buildingRecords.stream()
.map(XgDormBedStudent::getBuildingId)
.collect(Collectors.toSet());
} catch (Exception e) {
log.error("获取学院 {} 分配的宿舍楼ID列表失败", collegeId, e);
return Set.of();
}
}
/**
* 根据宿舍楼ID列表构建宿舍楼树形结构
*/
private List<DormBuildingTreeVo> buildDormBuildingTree(Set<Long> buildingIds) {
if (buildingIds.isEmpty()) {
return new ArrayList<>();
}
try {
// 获取宿舍楼信息
LambdaQueryWrapper<XgDormBuilding> buildingWrapper = Wrappers.lambdaQuery();
buildingWrapper.in(XgDormBuilding::getId, buildingIds)
.eq(XgDormBuilding::getDeleted, "0")
.orderByAsc(XgDormBuilding::getSortOrder, XgDormBuilding::getId);
List<XgDormBuilding> buildings = xgDormBuildingMapper.selectList(buildingWrapper);
if (buildings.isEmpty()) {
return new ArrayList<>();
}
// 获取涉及的宿舍区ID
Set<Long> areaIds = buildings.stream()
.map(XgDormBuilding::getAreaId)
.collect(Collectors.toSet());
// 获取宿舍区信息
LambdaQueryWrapper<XgDormArea> areaWrapper = Wrappers.lambdaQuery();
areaWrapper.in(XgDormArea::getId, areaIds)
.eq(XgDormArea::getDeleted, "0")
.orderByAsc(XgDormArea::getSortOrder, XgDormArea::getId);
List<XgDormArea> areas = xgDormAreaMapper.selectList(areaWrapper);
// 获取涉及的校区ID
Set<Long> campusIds = areas.stream()
.map(XgDormArea::getCampusId)
.collect(Collectors.toSet());
// 获取校区信息
LambdaQueryWrapper<XgDormCampus> campusWrapper = Wrappers.lambdaQuery();
campusWrapper.in(XgDormCampus::getId, campusIds)
.eq(XgDormCampus::getDeleted, "0")
.orderByAsc(XgDormCampus::getSortOrder, XgDormCampus::getId);
List<XgDormCampus> campuses = xgDormCampusMapper.selectList(campusWrapper);
// 构建树形结构
return buildTreeStructure(campuses, areas, buildings);
} catch (Exception e) {
log.error("构建宿舍楼树形结构失败", e);
return new ArrayList<>();
}
}
/**
* 构建树形结构:校区-宿舍区-宿舍楼
*/
private List<DormBuildingTreeVo> buildTreeStructure(List<XgDormCampus> campuses,
List<XgDormArea> areas,
List<XgDormBuilding> buildings) {
// 构建校区节点(第一层)
Map<Long, DormBuildingTreeVo> campusMap = new LinkedHashMap<>();
for (XgDormCampus campus : campuses) {
// 只保留有宿舍楼的校区
if (areas.stream().anyMatch(a -> a.getCampusId().equals(campus.getId()))) {
DormBuildingTreeVo campusNode = new DormBuildingTreeVo();
campusNode.setId(campus.getId());
campusNode.setLabel(campus.getCampusName());
campusNode.setType("campus");
campusNode.setDisabled(true); // 校区节点禁止点击
campusNode.setChildren(new ArrayList<>());
campusMap.put(campus.getId(), campusNode);
}
}
// 构建宿舍区节点(第二层)
Map<Long, DormBuildingTreeVo> areaMap = new LinkedHashMap<>();
for (XgDormArea area : areas) {
// 只保留有宿舍楼的宿舍区
if (buildings.stream().anyMatch(b -> b.getAreaId().equals(area.getId()))) {
DormBuildingTreeVo areaNode = new DormBuildingTreeVo();
areaNode.setId(area.getId());
areaNode.setLabel(area.getAreaName());
areaNode.setType("area");
areaNode.setDisabled(true); // 宿舍区节点禁止点击
areaNode.setChildren(new ArrayList<>());
areaMap.put(area.getId(), areaNode);
// 添加到对应的校区下
DormBuildingTreeVo campusNode = campusMap.get(area.getCampusId());
if (campusNode != null) {
campusNode.getChildren().add(areaNode);
}
}
}
// 构建宿舍楼节点(第三层)
for (XgDormBuilding building : buildings) {
DormBuildingTreeVo buildingNode = new DormBuildingTreeVo();
buildingNode.setId(building.getId());
buildingNode.setLabel(building.getBuildingName());
buildingNode.setType("building");
buildingNode.setDisabled(false); // 宿舍楼节点可以点击
buildingNode.setBuildingFloors(building.getFloors());
buildingNode.setBuildingType(building.getBuildingType());
buildingNode.setChildren(new ArrayList<>());
// 添加到对应的宿舍区下
DormBuildingTreeVo areaNode = areaMap.get(building.getAreaId());
if (areaNode != null) {
areaNode.getChildren().add(buildingNode);
}
}
return new ArrayList<>(campusMap.values());
}
}
@@ -167,6 +167,7 @@ public class XgStudentManagerSourceServiceImpl implements IXgStudentManagerSourc
}
@Override
@Transactional(readOnly = true)
public List<String> getAuthSourceNames(Long managerId) {
// 1. 查询xg_student_manager_source表,manager_id=managerId, deleted=0,
// status=0,获取auth_source
@@ -190,6 +191,7 @@ public class XgStudentManagerSourceServiceImpl implements IXgStudentManagerSourc
}
@Override
@Transactional(readOnly = true)
public List<XgStudentManagerDeptTreeVo> getCurrentUserAuthDeptTree() {
// 获取当前登录用户ID
Long currentUserId = LoginHelper.getUserId();
@@ -37,11 +37,13 @@ import org.dromara.xg.mapper.XgStudentManagerSourceMapper;
import org.dromara.xg.mapper.XgClassTeacherCounselorRelMapper;
import org.dromara.xg.mapper.XgClassMsgMapper;
import org.dromara.xg.mapper.XgDormBedStudentMapper;
import org.dromara.xg.mapper.XgSysDeptMapper;
import org.dromara.xg.domain.XgStudentManager;
import org.dromara.xg.domain.XgStudentManagerSource;
import org.dromara.xg.domain.XgClassTeacherCounselorRel;
import org.dromara.xg.domain.XgClassMsg;
import org.dromara.xg.domain.XgDormBedStudent;
import org.dromara.xg.domain.XgSysDept;
import org.dromara.common.satoken.utils.LoginHelper;
import java.util.stream.Collectors;
@@ -73,6 +75,7 @@ public class XgStudentMsgServiceImpl implements IXgStudentMsgService {
private final XgClassTeacherCounselorRelMapper xgClassTeacherCounselorRelMapper;
private final XgClassMsgMapper xgClassMsgMapper;
private final XgDormBedStudentMapper xgDormBedStudentMapper;
private final XgSysDeptMapper xgSysDeptMapper;
/**
* 查询学生信息
@@ -1317,4 +1320,83 @@ public class XgStudentMsgServiceImpl implements IXgStudentMsgService {
return result;
}
/**
* 获取当前用户所在学院的学生信息树
*
* @return 学生信息树
*/
@Override
public List<XgStudentTreeVo> getCurrentUserStudentTree() {
try {
// 1. 获取当前用户的二级学院ID
Long collegeId = getCurrentUserCollegeId();
if (collegeId == null) {
log.warn("无法获取当前用户的二级学院信息");
return new ArrayList<>();
}
log.info("获取到当前用户的学院ID: {}", collegeId);
// 2. 调用现有的getStudentTree方法
return getStudentTree(collegeId);
} catch (Exception e) {
log.error("获取当前用户学院的学生信息树失败", e);
return new ArrayList<>();
}
}
/**
* 获取当前用户的二级学院ID
* 通过当前用户的部门id,向上寻找包括他自己,找到dept_category前几位是xy-的就是二级学院
* 或者parent_id = 101的部门就是二级学院
*/
private Long getCurrentUserCollegeId() {
try {
Long currentUserId = LoginHelper.getUserId();
Long currentDeptId = LoginHelper.getDeptId();
if (currentUserId == null || currentDeptId == null) {
log.warn("用户未登录或没有部门信息");
return null;
}
// 首先查找parent_id = 101的部门(使用LambdaQueryWrapper)
LambdaQueryWrapper<XgSysDept> parentQuery = Wrappers.lambdaQuery();
parentQuery.eq(XgSysDept::getParentId, 101L)
.eq(XgSysDept::getStatus, "0")
.eq(XgSysDept::getDelFlag, "0")
.and(wrapper -> wrapper.eq(XgSysDept::getDeptId, currentDeptId)
.or().like(XgSysDept::getAncestors, "," + currentDeptId + ",") // 中间位置
.or().likeRight(XgSysDept::getAncestors, currentDeptId + ",") // 开头位置
.or().likeLeft(XgSysDept::getAncestors, "," + currentDeptId) // 末尾位置
.or().eq(XgSysDept::getAncestors, currentDeptId.toString())) // 单独一个值
.last("LIMIT 1");
List<XgSysDept> parentColleges = xgSysDeptMapper.selectList(parentQuery);
if (!parentColleges.isEmpty()) {
return parentColleges.get(0).getDeptId();
}
// 如果上面的查询没找到,尝试通过dept_category查找
LambdaQueryWrapper<XgSysDept> categoryQuery = Wrappers.lambdaQuery();
categoryQuery.likeRight(XgSysDept::getDeptCategory, "xy-")
.eq(XgSysDept::getStatus, "0")
.eq(XgSysDept::getDelFlag, "0")
.and(wrapper -> wrapper.eq(XgSysDept::getDeptId, currentDeptId)
.or().like(XgSysDept::getAncestors, "," + currentDeptId + ",") // 中间位置
.or().likeRight(XgSysDept::getAncestors, currentDeptId + ",") // 开头位置
.or().likeLeft(XgSysDept::getAncestors, "," + currentDeptId) // 末尾位置
.or().eq(XgSysDept::getAncestors, currentDeptId.toString())) // 单独一个值
.last("LIMIT 1");
List<XgSysDept> categoryColleges = xgSysDeptMapper.selectList(categoryQuery);
return categoryColleges.isEmpty() ? null : categoryColleges.get(0).getDeptId();
} catch (Exception e) {
log.error("获取当前用户二级学院ID失败", e);
return null;
}
}
}
@@ -1,6 +1,6 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { DormBedStudentVO, DormBedStudentForm, DormBedStudentQuery } from '@/api/xg/xgss/dormBedStudent/dormBedStudent/types';
import { DormBedStudentVO, DormBedStudentForm, DormBedStudentQuery, DormBuildingTreeVO } from '@/api/xg/xgss/dormBedStudent/dormBedStudent/types';
/**
* 查询床位学生分配关系列表
@@ -72,3 +72,14 @@ export const batchRecallDormBeds = (data: { bedIds: Array<string | number> }) =>
data
});
};
/**
* 获取当前用户二级学院的宿舍楼树形结构
* 校区-宿舍区-宿舍楼三级结构,校区和宿舍区不可选,只有宿舍楼可以选择
*/
export const getCurrentUserCollegeDormTree = (): AxiosPromise<DormBuildingTreeVO[]> => {
return request({
url: '/dormBedStudent/dormBedStudent/currentUserCollegeDormTree',
method: 'get'
});
};
@@ -223,3 +223,43 @@ export interface DormBedStudentQuery extends PageQuery {
*/
params?: any;
}
/**
* 宿舍楼树形结构VO
*/
export interface DormBuildingTreeVO {
/**
* 节点ID
*/
id: string | number;
/**
* 节点名称
*/
label: string;
/**
* 节点类型 (campus:校区, area:宿舍区, building:宿舍楼)
*/
type: string;
/**
* 是否禁用节点(校区和宿舍区节点禁用点击)
*/
disabled?: boolean;
/**
* 宿舍楼层数(仅宿舍楼节点有此字段)
*/
buildingFloors?: number;
/**
* 宿舍楼类型(1男生楼 2女生楼 3混合楼)(仅宿舍楼节点有此字段)
*/
buildingType?: string;
/**
* 子节点
*/
children?: DormBuildingTreeVO[];
}
@@ -207,6 +207,14 @@ export function getStudentTree(secondaryCollegeId: number = 101): AxiosPromise<R
});
}
// 获取当前用户所在学院的学生信息树
export function getCurrentUserStudentTree(): AxiosPromise<R<XgStudentTreeVO[]>> {
return request({
url: '/studentMsg/studentMsg/getCurrentUserStudentTree',
method: 'get'
});
}
// 学生状态修改
export function changeStudentStatus(studentId: string | number, status: string): AxiosPromise<R<any>> {
// 实际使用时连接后端接口
@@ -0,0 +1,150 @@
<template>
<el-dialog v-model="visible" :title="title" width="500px" append-to-body @close="closeDialog">
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="床位编号" prop="bedCode">
<el-input v-model="form.bedCode" placeholder="请输入床位编号" maxlength="20" />
</el-form-item>
<el-form-item label="床位名称" prop="bedName">
<el-input v-model="form.bedName" placeholder="请输入床位名称" maxlength="20" />
</el-form-item>
<el-form-item label="床位状态" prop="bedStatus">
<el-radio-group v-model="form.bedStatus">
<el-radio label="0">空闲</el-radio>
<el-radio label="1" disabled>已分配</el-radio>
<el-radio label="2">维修中</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="床位类型" prop="bedType">
<el-radio-group v-model="form.bedType">
<el-radio label="1">下铺</el-radio>
<el-radio label="2">上铺</el-radio>
<el-radio label="3">中铺</el-radio>
<el-radio label="4">独立床</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注内容" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="visible = false">取 消</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, watch } from 'vue';
import type { FormInstance, FormRules } from 'element-plus';
import { DormBedForm } from '@/api/xg/xgss/dormBed/types';
import { addDormBed, updateDormBed, getDormBed } from '@/api/xg/xgss/dormBed';
const props = defineProps<{
modelValue: boolean;
title: string;
bedId?: string | number;
roomId: string | number;
}>();
const emit = defineEmits(['update:modelValue', 'success']);
const formRef = ref<FormInstance>();
const visible = ref(false);
// 表单数据
const initFormData: DormBedForm = {
id: '',
bedCode: '',
bedName: '',
roomId: '',
bedStatus: '0',
bedType: '4',
remark: ''
};
const form = reactive<DormBedForm>({ ...initFormData });
// 表单校验规则
const rules = reactive<FormRules>({
bedCode: [{ required: true, message: '床位编号不能为空', trigger: 'blur' }],
bedName: [{ required: true, message: '床位名称不能为空', trigger: 'blur' }],
bedStatus: [{ required: true, message: '床位状态不能为空', trigger: 'change' }],
bedType: [{ required: true, message: '床位类型不能为空', trigger: 'change' }]
});
// 监听visible属性变化
watch(
() => props.modelValue,
(val) => {
visible.value = val;
if (val) {
resetForm();
// 设置表单初始值
form.roomId = props.roomId;
// 编辑模式,获取床位详情
if (props.bedId) {
console.log('开始获取床位详情, bedId:', props.bedId);
getDormBed(props.bedId)
.then((res) => {
console.log('获取床位详情成功:', res.data);
if (res.data) {
Object.assign(form, res.data);
}
})
.catch((error) => {
console.error('获取床位详情失败', error);
});
}
}
}
);
// 监听visible变化,同步回父组件
watch(
() => visible.value,
(val) => {
emit('update:modelValue', val);
}
);
// 关闭对话框
const closeDialog = () => {
visible.value = false;
resetForm();
};
// 重置表单
const resetForm = () => {
Object.assign(form, { ...initFormData });
formRef.value?.resetFields();
};
// 提交表单
const submitForm = () => {
formRef.value?.validate(async (valid) => {
if (valid) {
try {
if (props.bedId) {
// 修改
form.id = props.bedId;
await updateDormBed(form);
} else {
// 新增
await addDormBed(form);
}
closeDialog();
emit('success');
} catch (error) {
console.error('操作失败', error);
}
}
});
};
// 添加默认导出以支持导入
defineOptions({
name: 'BedDialog'
});
</script>
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,171 @@
<template>
<el-dialog v-model="visible" :title="title" width="500px" append-to-body @close="closeDialog">
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="楼层" prop="floorNum">
<el-input v-model="form.floorNum" placeholder="请输入楼层" :disabled="true" />
</el-form-item>
<el-form-item label="房间编号" prop="roomCode">
<el-input v-model="form.roomCode" placeholder="请输入房间编号" maxlength="20" />
</el-form-item>
<el-form-item label="房间名称" prop="roomName">
<el-input v-model="form.roomName" placeholder="请输入房间名称" maxlength="20" />
</el-form-item>
<el-form-item label="房间类型" prop="roomType">
<el-select v-model="form.roomType" placeholder="请选择房间类型" clearable style="width: 100%">
<el-option label="标准间" value="1" />
<el-option label="单人间" value="2" />
<el-option label="套间" value="3" />
</el-select>
</el-form-item>
<el-form-item label="床位数量" prop="actualBeds">
<el-input-number v-model="form.actualBeds" :min="1" :max="8" controls-position="right" style="width: 100%" />
</el-form-item>
<el-form-item label="房间面积" prop="roomArea">
<el-input v-model="form.roomArea" placeholder="请输入房间面积" maxlength="20">
<template #append>
<span style="color: #000; background: transparent">平方米</span>
</template>
</el-input>
</el-form-item>
<el-form-item label="房间状态" prop="roomStatus">
<el-radio-group v-model="form.roomStatus">
<el-radio label="0">正常</el-radio>
<el-radio label="1">维修中</el-radio>
<el-radio label="2">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注内容" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="visible = false">取 消</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, watch } from 'vue';
import type { FormInstance, FormRules } from 'element-plus';
import { DormRoomForm, DormRoomVO } from '@/api/xg/xgss/dormRoom/types';
import { addDormRoom, updateDormRoom } from '@/api/xg/xgss/dormRoom';
const props = defineProps<{
modelValue: boolean;
title: string;
roomId?: string | number;
floorNum: number;
buildingId: string | number;
room?: DormRoomVO;
}>();
const emit = defineEmits(['update:modelValue', 'success']);
const formRef = ref<FormInstance>();
const visible = ref(false);
// 表单数据
const initFormData: DormRoomForm = {
id: undefined,
roomCode: '',
roomName: '',
floorNum: 1,
buildingId: '',
actualBeds: 4,
roomArea: 0,
roomType: '1',
roomStatus: '0',
remark: ''
};
const form = reactive<DormRoomForm>({ ...initFormData });
// 表单校验规则
const rules = reactive<FormRules>({
roomCode: [{ required: true, message: '房间编号不能为空', trigger: 'blur' }],
roomName: [{ required: true, message: '房间名称不能为空', trigger: 'blur' }],
floorNum: [{ required: true, message: '楼层不能为空', trigger: 'blur' }],
actualBeds: [{ required: true, message: '床位数量不能为空', trigger: 'change' }],
roomType: [{ required: true, message: '房间类型不能为空', trigger: 'change' }],
roomStatus: [{ required: true, message: '房间状态不能为空', trigger: 'change' }]
});
// 监听visible属性变化
watch(
() => props.modelValue,
(val) => {
visible.value = val;
if (val) {
// 完全重置表单,确保没有残留数据
resetForm();
// 重新设置基本参数
form.floorNum = props.floorNum;
form.buildingId = props.buildingId;
// 如果是编辑模式且有room数据,填充表单数据
if (props.room) {
Object.keys(form).forEach((key) => {
if (key in props.room) {
form[key] = props.room[key];
}
});
}
}
}
);
// 监听visible变化,同步回父组件
watch(
() => visible.value,
(val) => {
emit('update:modelValue', val);
}
);
// 关闭对话框
const closeDialog = () => {
visible.value = false;
resetForm();
};
// 重置表单
const resetForm = () => {
// 使用深拷贝确保完全重置
Object.keys(form).forEach((key) => {
form[key] = initFormData[key];
});
formRef.value?.resetFields();
};
// 提交表单
const submitForm = () => {
formRef.value?.validate(async (valid) => {
if (valid) {
try {
console.log(props.roomId);
if (props.roomId) {
// 修改
form.id = props.roomId;
await updateDormRoom(form);
} else {
// 新增
await addDormRoom(form);
}
closeDialog();
emit('success');
} catch (error) {
console.error('操作失败', error);
}
}
});
};
// 添加默认导出以支持导入
defineOptions({
name: 'RoomDialog'
});
</script>
File diff suppressed because it is too large. Load diff