diff --git a/.gitignore b/.gitignore index 03ef4a45..1ec16190 100644 --- a/.gitignore +++ b/.gitignore @@ -328,3 +328,6 @@ docs/部署文档/nginx配置/hzdyjsweb.camasort.com.conf 首页完善的提示词.md scripts/ 学生宿舍卫生检查、学生违纪记录统计.md +学生调宿、退宿功能.md +学生动向信息界面(宿管员).md +prompt/ diff --git a/backend/ruoyi-modules/ruoyi-stgl/pom.xml b/backend/ruoyi-modules/ruoyi-stgl/pom.xml index 8ae11d2d..7ea0ba55 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/pom.xml +++ b/backend/ruoyi-modules/ruoyi-stgl/pom.xml @@ -93,6 +93,12 @@ ruoyi-xg + + + org.dromara + ruoyi-system + + \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglAnnualReviewAuditController.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglAnnualReviewAuditController.java new file mode 100644 index 00000000..b3f1c93c --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglAnnualReviewAuditController.java @@ -0,0 +1,113 @@ +package org.dromara.stgl.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.web.core.BaseController; +import org.dromara.stgl.domain.StglAnnualReviewAudit; +import org.dromara.stgl.domain.bo.StglAnnualReviewApprovalBo; +import org.dromara.stgl.service.IStglAnnualReviewAuditService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.util.List; + +/** + * 社团年审审批 + * + * @author ruoyi + * @date 2024-01-01 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/stgl/annualReviewAudit") +public class StglAnnualReviewAuditController extends BaseController { + + private final IStglAnnualReviewAuditService stglAnnualReviewAuditService; + + /** + * 查询社团年审审批详细 + */ + @SaCheckPermission("stgl:annualReviewAudit:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(stglAnnualReviewAuditService.queryById(id)); + } + + /** + * 根据年审记录ID查询审批记录 + */ + @SaCheckPermission("stgl:annualReviewAudit:query") + @GetMapping("/byAnnualReview/{annualReviewId}") + public R> getByAnnualReviewId(@NotNull(message = "年审记录ID不能为空") + @PathVariable Long annualReviewId) { + return R.ok(stglAnnualReviewAuditService.queryByAnnualReviewId(annualReviewId)); + } + + /** + * 根据审批人ID查询待审批记录 + */ + @SaCheckPermission("stgl:annualReviewAudit:query") + @GetMapping("/pending/{approverId}") + public R> getPendingByApproverId(@NotNull(message = "审批人ID不能为空") + @PathVariable Long approverId) { + return R.ok(stglAnnualReviewAuditService.queryPendingByApproverId(approverId)); + } + + /** + * 新增社团年审审批 + */ + @SaCheckPermission("stgl:annualReviewAudit:add") + @Log(title = "社团年审审批", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody StglAnnualReviewAudit stglAnnualReviewAudit) { + return toAjax(stglAnnualReviewAuditService.insertByBo(stglAnnualReviewAudit)); + } + + /** + * 修改社团年审审批 + */ + @SaCheckPermission("stgl:annualReviewAudit:edit") + @Log(title = "社团年审审批", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody StglAnnualReviewAudit stglAnnualReviewAudit) { + return toAjax(stglAnnualReviewAuditService.updateByBo(stglAnnualReviewAudit)); + } + + /** + * 删除社团年审审批 + */ + @SaCheckPermission("stgl:annualReviewAudit:remove") + @Log(title = "社团年审审批", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(stglAnnualReviewAuditService.deleteWithValidByIds(List.of(ids), true)); + } + + /** + * 审批年审记录 + */ + @SaCheckPermission("stgl:annualReviewAudit:approve") + @Log(title = "社团年审审批", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PostMapping("/approve") + public R approve(@Validated @RequestBody StglAnnualReviewApprovalBo approvalBo) { + return toAjax(stglAnnualReviewAuditService.approveAnnualReview(approvalBo)); + } +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglAuditorController.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglAuditorController.java new file mode 100644 index 00000000..bd131134 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglAuditorController.java @@ -0,0 +1,179 @@ +package org.dromara.stgl.controller; + +import java.util.List; + +import lombok.RequiredArgsConstructor; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.*; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.web.core.BaseController; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.stgl.domain.vo.StglAuditorVo; +import org.dromara.stgl.domain.bo.StglAuditorBo; +import org.dromara.stgl.service.IStglAuditorService; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.system.service.ISysUserService; +import org.dromara.system.domain.vo.SysUserVo; + +/** + * 社团管理审核人配置 + * + * @author ruoyi + * @date 2024-01-01 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/stgl/auditor") +public class StglAuditorController extends BaseController { + + private final IStglAuditorService stglAuditorService; + private final ISysUserService sysUserService; + + /** + * 查询社团管理审核人配置列表 + */ + @SaCheckPermission("stgl:auditor:list") + @GetMapping("/list") + public TableDataInfo list(StglAuditorBo bo, PageQuery pageQuery) { + return stglAuditorService.queryPageList(bo, pageQuery); + } + + /** + * 导出社团管理审核人配置列表 + */ + @SaCheckPermission("stgl:auditor:export") + @Log(title = "社团管理审核人配置", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(StglAuditorBo bo, HttpServletResponse response) { + List list = stglAuditorService.queryList(bo); + ExcelUtil.exportExcel(list, "社团管理审核人配置", StglAuditorVo.class, response); + } + + /** + * 获取社团管理审核人配置详细信息 + */ + @SaCheckPermission("stgl:auditor:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(stglAuditorService.queryById(id)); + } + + /** + * 新增社团管理审核人配置 + */ + @SaCheckPermission("stgl:auditor:add") + @Log(title = "社团管理审核人配置", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody StglAuditorBo bo) { + return toAjax(stglAuditorService.insertByBo(bo)); + } + + /** + * 修改社团管理审核人配置 + */ + @SaCheckPermission("stgl:auditor:edit") + @Log(title = "社团管理审核人配置", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody StglAuditorBo bo) { + return toAjax(stglAuditorService.updateByBo(bo)); + } + + /** + * 删除社团管理审核人配置 + */ + @SaCheckPermission("stgl:auditor:remove") + @Log(title = "社团管理审核人配置", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(stglAuditorService.deleteWithValidByIds(List.of(ids))); + } + + /** + * 批量添加审核人 + */ + @SaCheckPermission("stgl:auditor:add") + @Log(title = "批量添加审核人", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/batchAdd") + public R batchAdd(@RequestBody BatchAddRequest request) { + return toAjax(stglAuditorService.batchAddAuditors(request.getUserIds(), request.getRemark())); + } + + /** + * 查询我配置的审核人列表 + */ + @SaCheckPermission("stgl:auditor:list") + @GetMapping("/myConfig") + public TableDataInfo myConfigList(StglAuditorBo bo, PageQuery pageQuery) { + return stglAuditorService.queryMyConfigList(bo, pageQuery); + } + + /** + * 批量启用审核人 + */ + @SaCheckPermission("stgl:auditor:edit") + @Log(title = "启用审核人", businessType = BusinessType.UPDATE) + @PostMapping("/enable") + public R enable(@RequestBody List ids) { + return toAjax(stglAuditorService.enableAuditors(ids)); + } + + /** + * 批量停用审核人 + */ + @SaCheckPermission("stgl:auditor:edit") + @Log(title = "停用审核人", businessType = BusinessType.UPDATE) + @PostMapping("/disable") + public R disable(@RequestBody List ids) { + return toAjax(stglAuditorService.disableAuditors(ids)); + } + + /** + * 根据部门ID获取用户列表 + */ + @SaCheckPermission("stgl:auditor:list") + @GetMapping("/users/{deptId}") + public R> listUserByDeptId(@PathVariable String deptId) { + List users = sysUserService.selectUserListByDept(Long.valueOf(deptId)); + return R.ok(users); + } + + /** + * 批量添加请求对象 + */ + public static class BatchAddRequest { + private List userIds; + private String remark; + + public List getUserIds() { + return userIds; + } + + public void setUserIds(List userIds) { + this.userIds = userIds; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + } + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglClubAdvisorHistoryController.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglClubAdvisorHistoryController.java new file mode 100644 index 00000000..42bb8e1d --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglClubAdvisorHistoryController.java @@ -0,0 +1,147 @@ +package org.dromara.stgl.controller; + +import java.util.List; + +import lombok.RequiredArgsConstructor; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.*; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.web.core.BaseController; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.stgl.domain.vo.StglClubAdvisorHistoryVo; +import org.dromara.stgl.domain.bo.StglClubAdvisorHistoryBo; +import org.dromara.stgl.service.IStglClubAdvisorHistoryService; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 社团指导老师任职历史管理 + * + * @author Cursor AI + * @date 2025-01-03 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/stgl/club/advisor/history") +@Tag(name = "社团指导老师任职历史管理", description = "社团指导老师任职历史管理") +public class StglClubAdvisorHistoryController extends BaseController { + + private final IStglClubAdvisorHistoryService stglClubAdvisorHistoryService; + + /** + * 查询社团指导老师任职历史列表 + */ + @Operation(summary = "查询社团指导老师任职历史列表") + @SaCheckPermission("stgl:club:query") + @GetMapping("/list") + public TableDataInfo list(StglClubAdvisorHistoryBo bo, PageQuery pageQuery) { + return stglClubAdvisorHistoryService.queryPageList(bo, pageQuery); + } + + /** + * 导出社团指导老师任职历史列表 + */ + @Operation(summary = "导出社团指导老师任职历史列表") + @SaCheckPermission("stgl:club:export") + @Log(title = "社团指导老师任职历史", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(StglClubAdvisorHistoryBo bo, HttpServletResponse response) { + List list = stglClubAdvisorHistoryService.queryList(bo); + ExcelUtil.exportExcel(list, "社团指导老师任职历史", StglClubAdvisorHistoryVo.class, response); + } + + /** + * 获取社团指导老师任职历史详细信息 + */ + @Operation(summary = "获取社团指导老师任职历史详细信息") + @SaCheckPermission("stgl:club:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) { + return R.ok(stglClubAdvisorHistoryService.queryById(id)); + } + + /** + * 查询指定社团的当前在任指导老师 + */ + @Operation(summary = "查询指定社团的当前在任指导老师") + @SaCheckPermission("stgl:club:query") + @GetMapping("/current/{clubId}") + @Parameter(name = "clubId", description = "社团ID", required = true) + public R> getCurrentAdvisors(@PathVariable Long clubId) { + List list = stglClubAdvisorHistoryService.getCurrentAdvisorsByClubId(clubId); + return R.ok(list); + } + + /** + * 查询指定社团的历史指导老师(已离任) + */ + @Operation(summary = "查询指定社团的历史指导老师") + @SaCheckPermission("stgl:club:query") + @GetMapping("/history/{clubId}") + @Parameter(name = "clubId", description = "社团ID", required = true) + public R> getHistoryAdvisors(@PathVariable Long clubId) { + List list = stglClubAdvisorHistoryService.getHistoryAdvisorsByClubId(clubId); + return R.ok(list); + } + + /** + * 查询指定社团的所有指导老师(包含当前和历史) + */ + @Operation(summary = "查询指定社团的所有指导老师") + @SaCheckPermission("stgl:club:query") + @GetMapping("/all/{clubId}") + @Parameter(name = "clubId", description = "社团ID", required = true) + public R> getAllAdvisors(@PathVariable Long clubId) { + List list = stglClubAdvisorHistoryService.getAllAdvisorsByClubId(clubId); + return R.ok(list); + } + + /** + * 新增社团指导老师任职历史 + */ + @Operation(summary = "新增社团指导老师任职历史") + @SaCheckPermission("stgl:club:add") + @Log(title = "社团指导老师任职历史", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody StglClubAdvisorHistoryBo bo) { + return toAjax(stglClubAdvisorHistoryService.insertByBo(bo)); + } + + /** + * 修改社团指导老师任职历史 + */ + @Operation(summary = "修改社团指导老师任职历史") + @SaCheckPermission("stgl:club:edit") + @Log(title = "社团指导老师任职历史", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody StglClubAdvisorHistoryBo bo) { + return toAjax(stglClubAdvisorHistoryService.updateByBo(bo)); + } + + /** + * 删除社团指导老师任职历史 + */ + @Operation(summary = "删除社团指导老师任职历史") + @SaCheckPermission("stgl:club:remove") + @Log(title = "社团指导老师任职历史", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) { + return toAjax(stglClubAdvisorHistoryService.deleteWithValidByIds(List.of(ids), true)); + } + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglClubAnnualReviewController.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglClubAnnualReviewController.java new file mode 100644 index 00000000..437086b7 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglClubAnnualReviewController.java @@ -0,0 +1,213 @@ +package org.dromara.stgl.controller; + +import java.util.List; + +import lombok.RequiredArgsConstructor; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.*; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.web.core.BaseController; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.stgl.domain.vo.StglClubAnnualReviewVo; +import org.dromara.stgl.domain.bo.StglClubAnnualReviewBo; +import org.dromara.stgl.service.IStglClubAnnualReviewService; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 社团年审记录管理 + * + * @author Cursor AI + * @date 2025-01-15 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/stgl/club/annualReview") +@Tag(name = "社团年审记录管理", description = "社团年审记录管理") +public class StglClubAnnualReviewController extends BaseController { + + private final IStglClubAnnualReviewService stglClubAnnualReviewService; + + /** + * 查询社团年审记录列表 + */ + @Operation(summary = "查询社团年审记录列表") + @SaCheckPermission("stgl:clubAnnualReview:list") + @GetMapping("/list") + public TableDataInfo list(StglClubAnnualReviewBo bo, PageQuery pageQuery) { + return stglClubAnnualReviewService.queryPageList(bo, pageQuery); + } + + /** + * 查询我的社团年审记录列表 + */ + @Operation(summary = "查询我的社团年审记录列表") + @SaCheckPermission("stgl:clubAnnualReview:myList") + @GetMapping("/myList") + public TableDataInfo myList(StglClubAnnualReviewBo bo, PageQuery pageQuery) { + return stglClubAnnualReviewService.queryMyPageList(bo, pageQuery); + } + + /** + * 导出社团年审记录列表 + */ + @Operation(summary = "导出社团年审记录列表") + @SaCheckPermission("stgl:clubAnnualReview:export") + @Log(title = "社团年审记录", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(StglClubAnnualReviewBo bo, HttpServletResponse response) { + List list = stglClubAnnualReviewService.queryList(bo); + ExcelUtil.exportExcel(list, "社团年审记录", StglClubAnnualReviewVo.class, response); + } + + /** + * 获取社团年审记录详细信息 + */ + @Operation(summary = "获取社团年审记录详细信息") + @SaCheckPermission("stgl:clubAnnualReview:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(stglClubAnnualReviewService.queryById(id)); + } + + /** + * 新增社团年审记录 + */ + @Operation(summary = "新增社团年审记录") + @SaCheckPermission("stgl:clubAnnualReview:add") + @Log(title = "社团年审记录", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody StglClubAnnualReviewBo bo) { + return toAjax(stglClubAnnualReviewService.insertByBo(bo)); + } + + /** + * 修改社团年审记录 + */ + @Operation(summary = "修改社团年审记录") + @SaCheckPermission("stgl:clubAnnualReview:edit") + @Log(title = "社团年审记录", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody StglClubAnnualReviewBo bo) { + return toAjax(stglClubAnnualReviewService.updateByBo(bo)); + } + + /** + * 删除社团年审记录 + */ + @Operation(summary = "删除社团年审记录") + @SaCheckPermission("stgl:clubAnnualReview:remove") + @Log(title = "社团年审记录", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(stglClubAnnualReviewService.deleteWithValidByIds(List.of(ids), true)); + } + + /** + * 提交年审记录 + */ + @Operation(summary = "提交年审记录") + @SaCheckPermission("stgl:clubAnnualReview:submit") + @Log(title = "社团年审记录", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PostMapping("/submit/{id}") + public R submit(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return toAjax(stglClubAnnualReviewService.submitReview(id)); + } + + /** + * 撤回年审记录 + */ + @Operation(summary = "撤回年审记录") + @SaCheckPermission("stgl:clubAnnualReview:withdraw") + @Log(title = "社团年审记录", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PostMapping("/withdraw/{id}") + public R withdraw(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return toAjax(stglClubAnnualReviewService.withdrawReview(id)); + } + + /** + * 审核年审记录 + */ + @Operation(summary = "审核年审记录") + @SaCheckPermission("stgl:clubAnnualReview:audit") + @Log(title = "社团年审记录", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PostMapping("/audit") + public R audit(@RequestParam @NotNull(message = "年审记录ID不能为空") Long id, + @RequestParam @NotBlank(message = "审核结果不能为空") String auditResult, + @RequestParam String auditOpinion, + @RequestParam @NotBlank(message = "审核类型不能为空") String auditType) { + return toAjax(stglClubAnnualReviewService.auditReview(id, auditResult, auditOpinion, auditType)); + } + + /** + * 检查是否可以创建年审记录 + */ + @Operation(summary = "检查是否可以创建年审记录") + @SaCheckPermission("stgl:clubAnnualReview:check") + @GetMapping("/check") + public R checkCanCreate(@RequestParam @NotNull(message = "社团ID不能为空") Long clubId, + @RequestParam @NotNull(message = "年审年度不能为空") Integer reviewYear) { + return R.ok(stglClubAnnualReviewService.checkCanCreateReview(clubId, reviewYear)); + } + + /** + * 获取年审状态选项 + */ + @Operation(summary = "获取年审状态选项") + @GetMapping("/statusOptions") + public R> getStatusOptions() { + List options = List.of("0", "1", "2", "3", "4"); + return R.ok(options); + } + + /** + * 根据社团ID和年审年度获取该年度的活动数据 + */ + @Operation(summary = "根据社团ID和年审年度获取该年度的活动数据") + @Parameters({ + @Parameter(name = "clubId", description = "社团ID", required = true), + @Parameter(name = "reviewYear", description = "年审年度", required = true) + }) + @SaCheckPermission("stgl:clubAnnualReview:query") + @GetMapping("/clubActivities") + public R> getClubActivities( + @RequestParam @NotNull(message = "社团ID不能为空") Long clubId, + @RequestParam @NotNull(message = "年审年度不能为空") Integer reviewYear) { + return R.ok(stglClubAnnualReviewService.getClubActivitiesByReviewYear(clubId, reviewYear)); + } + + /** + * 根据年审记录ID获取对应的活动数据 + */ + @Operation(summary = "根据年审记录ID获取对应的活动数据") + @Parameter(name = "id", description = "年审记录ID", required = true) + @SaCheckPermission("stgl:clubAnnualReview:query") + @GetMapping("/{id}/activities") + public R> getAnnualReviewActivities( + @NotNull(message = "年审记录ID不能为空") @PathVariable Long id) { + return R.ok(stglClubAnnualReviewService.getAnnualReviewActivities(id)); + } + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglClubPermissionController.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglClubPermissionController.java new file mode 100644 index 00000000..4c2565c9 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglClubPermissionController.java @@ -0,0 +1,200 @@ +package org.dromara.stgl.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.domain.R; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.web.core.BaseController; +import org.dromara.stgl.domain.vo.ClubActivityPermissionResult; +import org.dromara.stgl.service.IStglClubPermissionService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 社团权限管理Controller + * + * @author Cursor AI + * @date 2025-01-15 + */ +@Slf4j +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/stgl/club/permission") +public class StglClubPermissionController extends BaseController { + + private final IStglClubPermissionService clubPermissionService; + + /** + * 检查社团活动权限 + * 前端每次进入活动管理页面都会调用这个接口 + * + * @param clubId 社团ID + * @return 权限检查结果 + */ + @SaCheckPermission("stgl:club:list") + @GetMapping("/checkActivityPermission/{clubId}") + @Log(title = "检查社团活动权限", businessType = BusinessType.OTHER) + public R checkActivityPermission(@PathVariable Long clubId) { + log.info("检查社团活动权限请求,clubId: {}", clubId); + + if (clubId == null || clubId <= 0) { + return R.fail("社团ID不能为空"); + } + + try { + ClubActivityPermissionResult result = clubPermissionService.checkClubActivityPermission(clubId); + log.info("社团ID: {} 权限检查结果: {}", clubId, result.getStatus()); + return R.ok(result); + } catch (Exception e) { + log.error("检查社团活动权限失败,clubId: {}", clubId, e); + return R.fail("检查权限失败: " + e.getMessage()); + } + } + + /** + * 批量检查多个社团的权限状态 + * 用于列表页面显示多个社团的权限状态 + * + * @param clubIds 社团ID列表 + * @return 权限检查结果映射 + */ + @SaCheckPermission("stgl:club:list") + @PostMapping("/batchCheckPermission") + @Log(title = "批量检查社团权限", businessType = BusinessType.OTHER) + public R> batchCheckPermission(@RequestBody List clubIds) { + log.info("批量检查社团权限请求,社团数量: {}", clubIds != null ? clubIds.size() : 0); + + if (clubIds == null || clubIds.isEmpty()) { + return R.fail("社团ID列表不能为空"); + } + + if (clubIds.size() > 100) { + return R.fail("批量检查数量不能超过100个"); + } + + try { + Map results = + clubPermissionService.batchCheckClubActivityPermission(clubIds); + log.info("批量权限检查完成,处理社团数量: {}", results.size()); + return R.ok(results); + } catch (Exception e) { + log.error("批量检查社团权限失败,clubIds: {}", clubIds, e); + return R.fail("批量检查权限失败: " + e.getMessage()); + } + } + + /** + * 检查社团是否在年审窗口期内 + * + * @param clubId 社团ID + * @return 是否在年审窗口期 + */ + @SaCheckPermission("stgl:club:list") + @GetMapping("/isInAnnualReviewWindow/{clubId}") + public R isInAnnualReviewWindow(@PathVariable Long clubId) { + log.debug("检查社团是否在年审窗口期,clubId: {}", clubId); + + if (clubId == null || clubId <= 0) { + return R.fail("社团ID不能为空"); + } + + try { + boolean inWindow = clubPermissionService.isInAnnualReviewWindow(clubId); + return R.ok(inWindow); + } catch (Exception e) { + log.error("检查年审窗口期失败,clubId: {}", clubId, e); + return R.fail("检查失败: " + e.getMessage()); + } + } + + /** + * 获取社团距离下次年审的天数 + * + * @param clubId 社团ID + * @return 距离年审的天数 + */ + @SaCheckPermission("stgl:club:list") + @GetMapping("/getDaysUntilNextAnnualReview/{clubId}") + public R getDaysUntilNextAnnualReview(@PathVariable Long clubId) { + log.debug("获取社团距离年审天数,clubId: {}", clubId); + + if (clubId == null || clubId <= 0) { + return R.fail("社团ID不能为空"); + } + + try { + long days = clubPermissionService.getDaysUntilNextAnnualReview(clubId); + return R.ok(days); + } catch (Exception e) { + log.error("获取距离年审天数失败,clubId: {}", clubId, e); + return R.fail("获取失败: " + e.getMessage()); + } + } + + /** + * 检查社团是否已在当前年审期内提交年审 + * + * @param clubId 社团ID + * @return 是否已提交年审 + */ + @SaCheckPermission("stgl:club:list") + @GetMapping("/hasSubmittedCurrentYearAnnualReview/{clubId}") + public R hasSubmittedCurrentYearAnnualReview(@PathVariable Long clubId) { + log.debug("检查社团是否已提交年审,clubId: {}", clubId); + + if (clubId == null || clubId <= 0) { + return R.fail("社团ID不能为空"); + } + + try { + boolean hasSubmitted = clubPermissionService.hasSubmittedCurrentYearAnnualReview(clubId); + return R.ok(hasSubmitted); + } catch (Exception e) { + log.error("检查年审提交状态失败,clubId: {}", clubId, e); + return R.fail("检查失败: " + e.getMessage()); + } + } + + /** + * 获取社团权限详细信息(包含所有相关信息的综合接口) + * + * @param clubId 社团ID + * @return 权限详细信息 + */ + @SaCheckPermission("stgl:club:list") + @GetMapping("/getPermissionDetails/{clubId}") + @Log(title = "获取社团权限详情", businessType = BusinessType.OTHER) + public R> getPermissionDetails(@PathVariable Long clubId) { + log.info("获取社团权限详情,clubId: {}", clubId); + + if (clubId == null || clubId <= 0) { + return R.fail("社团ID不能为空"); + } + + try { + ClubActivityPermissionResult permissionResult = + clubPermissionService.checkClubActivityPermission(clubId); + boolean inWindow = clubPermissionService.isInAnnualReviewWindow(clubId); + long daysUntilNext = clubPermissionService.getDaysUntilNextAnnualReview(clubId); + boolean hasSubmitted = clubPermissionService.hasSubmittedCurrentYearAnnualReview(clubId); + + Map details = Map.of( + "permissionResult", permissionResult, + "isInAnnualReviewWindow", inWindow, + "daysUntilNextAnnualReview", daysUntilNext, + "hasSubmittedCurrentYearAnnualReview", hasSubmitted + ); + + return R.ok(details); + } catch (Exception e) { + log.error("获取社团权限详情失败,clubId: {}", clubId, e); + return R.fail("获取详情失败: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglActivity.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglActivity.java index f3525a71..2ed27089 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglActivity.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglActivity.java @@ -82,6 +82,30 @@ public class StglActivity extends TenantEntity { @TableField("poster_url") private String posterUrl; + /** + * 关联部门ID + */ + @TableField("related_dept_id") + private Long relatedDeptId; + + /** + * 关联部门名称 + */ + @TableField("related_dept_name") + private String relatedDeptName; + + /** + * 关联部门ID列表(JSON格式) + */ + @TableField("related_dept_ids") + private String relatedDeptIds; + + /** + * 关联部门名称列表(逗号分隔) + */ + @TableField("related_dept_names") + private String relatedDeptNames; + /** * 关联社团ID */ diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglAnnualReviewAudit.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglAnnualReviewAudit.java new file mode 100644 index 00000000..0e80a7d9 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglAnnualReviewAudit.java @@ -0,0 +1,90 @@ +package org.dromara.stgl.domain; + +import org.dromara.common.tenant.core.TenantEntity; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 社团年审审批对象 stgl_annual_review_audit + * + * @author ruoyi + * @date 2024-01-01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("stgl_annual_review_audit") +public class StglAnnualReviewAudit extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId + private Long id; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + private Integer deleted; + + /** + * 年审记录ID + */ + private Long annualReviewId; + + /** + * 节点代码(advisor_approve/college_approve/youth_league_approve) + */ + private String nodeCode; + + /** + * 节点名称(指导老师审批/学院审批/校团委审批) + */ + private String nodeName; + + /** + * 节点顺序(1.1,1.2,2,3) + */ + private BigDecimal nodeOrder; + + /** + * 节点状态(pending/waiting/approved) + */ + @TableField("audit_status") + private String nodeStatus; + + /** + * 审批人ID + */ + private Long approverId; + + /** + * 审批人姓名 + */ + private String approverName; + + /** + * 审批结果(通过/不通过) + */ + private String approveResult; + + /** + * 审批意见 + */ + @TableField("audit_opinion") + private String approveOpinion; + + /** + * 审批时间 + */ + @TableField("audit_time") + private Date approveTime; +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglAuditor.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglAuditor.java new file mode 100644 index 00000000..0b81ddd2 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglAuditor.java @@ -0,0 +1,86 @@ +package org.dromara.stgl.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.tenant.core.TenantEntity; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 社团管理审核人配置对象 stgl_auditor + * + * @author ruoyi + * @date 2024-01-01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("stgl_auditor") +public class StglAuditor extends TenantEntity implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id") + private Long id; + + /** + * 用户ID + */ + private Long userId; + + /** + * 用户姓名 + */ + private String userName; + + /** + * 用户昵称 + */ + private String nickName; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 联系电话 + */ + private String phoneNumber; + + /** + * 邮箱地址 + */ + private String email; + + /** + * 状态(1正常 0停用) + */ + private String status; + + /** + * 备注说明 + */ + private String remark; + + /** + * 配置人ID + */ + private Long configBy; + + /** + * 配置人姓名 + */ + private String configByName; + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglClubAdvisorHistory.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglClubAdvisorHistory.java new file mode 100644 index 00000000..e7ea1698 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglClubAdvisorHistory.java @@ -0,0 +1,75 @@ +package org.dromara.stgl.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import org.dromara.common.tenant.core.TenantEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +/** + * 社团指导老师任职历史对象 stgl_club_advisor_history + * + * @author Cursor AI + * @date 2025-01-03 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("stgl_club_advisor_history") +public class StglClubAdvisorHistory extends TenantEntity { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id") + private Long id; + + /** + * 社团ID + */ + private Long clubId; + + /** + * 指导老师姓名 + */ + private String advisorName; + + /** + * 指导老师部门 + */ + private String advisorDept; + + /** + * 指导老师电话 + */ + private String advisorPhone; + + /** + * 指导老师邮箱 + */ + private String advisorEmail; + + /** + * 指导老师用户ID + */ + private Long advisorUserId; + + /** + * 在任状态:1-当前在任,0-历史(已离任) + */ + private Integer status; + + /** + * 上任时间 + */ + private Date startTime; + + /** + * 离任时间 + */ + private Date endTime; + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglClubAnnualReview.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglClubAnnualReview.java new file mode 100644 index 00000000..8c426740 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglClubAnnualReview.java @@ -0,0 +1,130 @@ +package org.dromara.stgl.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import org.dromara.common.tenant.core.TenantEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 社团年审记录对象 stgl_club_annual_review + * + * @author Cursor AI + * @date 2025-01-15 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("stgl_club_annual_review") +public class StglClubAnnualReview extends TenantEntity { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id") + private Long id; + + /** + * 社团ID + */ + private Long clubId; + + /** + * 年审年度 + */ + private Integer reviewYear; + + /** + * 年审开始日期 + */ + private Date reviewStartDate; + + /** + * 年审结束日期 + */ + private Date reviewEndDate; + + /** + * 年审时间 + */ + private Date reviewTime; + + /** + * 社团名称 + */ + private String clubName; + + /** + * 成立日期 + */ + private Date establishDate; + + /** + * 社长姓名 + */ + private String presidentName; + + /** + * 社团类型 + */ + private String clubType; + + /** + * 指导老师姓名 + */ + private String advisorName; + + /** + * 指导老师部门 + */ + private String advisorDept; + + /** + * 是否有章程 + */ + private String hasCharter; + + /** + * 章程文件URL + */ + private String charterUrl; + + /** + * 是否有资金 + */ + private String hasFunds; + + /** + * 资金数额 + */ + private BigDecimal fundAmount; + + /** + * 成员数量 + */ + private Integer memberCount; + + /** + * 状态(0-草稿 1-已提交 2-审核中 3-审核通过 4-审核不通过) + */ + private String status; + + /** + * 指导老师意见 + */ + private String advisorOpinion; + + /** + * 学院意见 + */ + private String collegeOpinion; + + /** + * 团委意见 + */ + private String youthLeagueOpinion; +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglClubAnnualReviewActivity.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglClubAnnualReviewActivity.java new file mode 100644 index 00000000..8ce08d5b --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglClubAnnualReviewActivity.java @@ -0,0 +1,94 @@ +package org.dromara.stgl.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.common.tenant.core.TenantEntity; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.io.Serializable; +import java.util.Date; + +/** + * 社团年审记录活动对象 stgl_club_annual_review_activity + * + * @author Lion Li + * @date 2024-12-31 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("stgl_club_annual_review_activity") +@Schema(description = "社团年审记录活动") +public class StglClubAnnualReviewActivity extends TenantEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id") + @Schema(description = "主键ID") + private Long id; + + /** + * 年审记录ID + */ + @Schema(description = "年审记录ID") + private Long annualReviewId; + + /** + * 活动名称 + */ + @Schema(description = "活动名称") + private String activityName; + + /** + * 活动时间 + */ + @Schema(description = "活动时间") + private Date activityTime; + + /** + * 活动开始时间 + */ + @Schema(description = "活动开始时间") + private Date activityStartTime; + + /** + * 活动结束时间 + */ + @Schema(description = "活动结束时间") + private Date activityEndTime; + + /** + * 活动地点 + */ + @Schema(description = "活动地点") + private String activityLocation; + + /** + * 活动类型 + */ + @Schema(description = "活动类型") + private String activityType; + + /** + * 参与人数 + */ + @Schema(description = "参与人数") + private Integer participantCount; + + /** + * 活动描述 + */ + @Schema(description = "活动描述") + private String activityDescription; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic(value = "0", delval = "1") + private Integer deleted; + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglClubAnnualReviewMember.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglClubAnnualReviewMember.java new file mode 100644 index 00000000..74b5dc98 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglClubAnnualReviewMember.java @@ -0,0 +1,69 @@ +package org.dromara.stgl.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.common.tenant.core.TenantEntity; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.io.Serializable; + +/** + * 社团年审记录成员对象 stgl_club_annual_review_member + * + * @author Lion Li + * @date 2024-12-31 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("stgl_club_annual_review_member") +@Schema(description = "社团年审记录成员") +public class StglClubAnnualReviewMember extends TenantEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id") + @Schema(description = "主键ID") + private Long id; + + /** + * 年审记录ID + */ + @Schema(description = "年审记录ID") + private Long annualReviewId; + + /** + * 成员姓名 + */ + @Schema(description = "成员姓名") + private String memberName; + + /** + * 班级 + */ + @Schema(description = "班级") + private String className; + + /** + * 职务 + */ + @Schema(description = "职务") + private String role; + + /** + * 联系方式 + */ + @Schema(description = "联系方式") + private String contactInfo; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic(value = "0", delval = "1") + private Integer deleted; + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglUserWithDeptInfo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglUserWithDeptInfo.java new file mode 100644 index 00000000..4eb5fc0b --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglUserWithDeptInfo.java @@ -0,0 +1,39 @@ +package org.dromara.stgl.domain; + +import lombok.Data; + +/** + * 社团管理用户和部门完整信息 + * 用于批量添加审核人时获取完整的用户数据 + * + * @author ruoyi + * @date 2025-01-03 + */ +@Data +public class StglUserWithDeptInfo { + + /** 用户ID */ + private Long userId; + + /** 用户账号 */ + private String userName; + + /** 用户昵称 */ + private String nickName; + + /** 部门ID */ + private Long deptId; + + /** 部门名称 */ + private String deptName; + + /** 手机号码 */ + private String phonenumber; + + /** 用户邮箱 */ + private String email; + + /** 帐号状态(1正常 0停用) */ + private String status; + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglActivityBo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglActivityBo.java index 4273c1a2..df878a3d 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglActivityBo.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglActivityBo.java @@ -76,6 +76,26 @@ public class StglActivityBo extends BaseEntity { */ private String posterUrl; + /** + * 关联部门ID + */ + private Long relatedDeptId; + + /** + * 关联部门名称 + */ + private String relatedDeptName; + + /** + * 关联部门ID列表(JSON格式) + */ + private String relatedDeptIds; + + /** + * 关联部门名称列表(逗号分隔) + */ + private String relatedDeptNames; + /** * 关联社团ID */ @@ -89,7 +109,6 @@ public class StglActivityBo extends BaseEntity { /** * 主办方名称 */ - @NotBlank(message = "主办方名称不能为空", groups = { AddGroup.class, EditGroup.class }) private String organizerName; /** diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglAnnualReviewApprovalBo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglAnnualReviewApprovalBo.java new file mode 100644 index 00000000..6dc8c100 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglAnnualReviewApprovalBo.java @@ -0,0 +1,60 @@ +package org.dromara.stgl.domain.bo; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 社团年审审批请求业务对象 + * + * @author system + * @date 2025-01-03 + */ +@Data +public class StglAnnualReviewApprovalBo { + + /** + * 审批记录ID + */ + @NotNull(message = "审批记录ID不能为空") + private Long auditId; + + /** + * 审批结果 + */ + @NotBlank(message = "审批结果不能为空") + private String approveResult; + + /** + * 审批意见 + */ + private String approveOpinion; + + /** + * 下一步审核人列表 + */ + private List nextAuditors; + + /** + * 下一步审核人信息 + */ + @Data + public static class NextAuditor { + /** + * 用户ID + */ + private Long userId; + + /** + * 用户名 + */ + private String userName; + + /** + * 昵称 + */ + private String nickName; + } +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglAuditorBo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglAuditorBo.java new file mode 100644 index 00000000..05463d19 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglAuditorBo.java @@ -0,0 +1,113 @@ +package org.dromara.stgl.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.stgl.domain.StglAuditor; + +import jakarta.validation.constraints.*; +import java.util.Date; +import java.util.List; + +/** + * 社团管理审核人配置业务对象 stgl_auditor + * + * @author ruoyi + * @date 2024-01-01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = StglAuditor.class, reverseConvertGenerate = false) +public class StglAuditorBo extends BaseEntity { + + /** + * 主键ID + */ + @NotNull(message = "主键ID不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 用户ID + */ + @NotNull(message = "用户ID不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long userId; + + /** + * 用户姓名 + */ + @NotBlank(message = "用户姓名不能为空", groups = { AddGroup.class, EditGroup.class }) + private String userName; + + /** + * 用户昵称 + */ + @NotBlank(message = "用户昵称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String nickName; + + /** + * 部门ID + */ + @NotNull(message = "部门ID不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long deptId; + + /** + * 部门名称 + */ + @NotBlank(message = "部门名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String deptName; + + /** + * 联系电话 + */ + private String phoneNumber; + + /** + * 邮箱地址 + */ + private String email; + + /** + * 状态(1正常 0停用) + */ + @NotBlank(message = "状态不能为空", groups = { AddGroup.class, EditGroup.class }) + private String status; + + /** + * 备注说明 + */ + private String remark; + + /** + * 配置人ID + */ + private Long configBy; + + /** + * 配置人姓名 + */ + private String configByName; + + /** + * 分页大小 + */ + private Integer pageSize; + + /** + * 页码 + */ + private Integer pageNum; + + /** + * 搜索值 + */ + private String searchValue; + + /** + * 创建时间范围 + */ + private List createTimeRange; + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglClubAdvisorHistoryBo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglClubAdvisorHistoryBo.java new file mode 100644 index 00000000..6a941d09 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglClubAdvisorHistoryBo.java @@ -0,0 +1,85 @@ +package org.dromara.stgl.domain.bo; + +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.*; +import org.dromara.stgl.domain.StglClubAdvisorHistory; + +import java.util.Date; + +/** + * 社团指导老师任职历史业务对象 stgl_club_advisor_history + * + * @author Cursor AI + * @date 2025-01-03 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = StglClubAdvisorHistory.class, reverseConvertGenerate = false) +public class StglClubAdvisorHistoryBo extends BaseEntity { + + /** + * 主键ID + */ + @NotNull(message = "主键ID不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 社团ID + */ + @NotNull(message = "社团ID不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long clubId; + + /** + * 指导老师姓名 + */ + @NotBlank(message = "指导老师姓名不能为空", groups = { AddGroup.class, EditGroup.class }) + @Size(max = 100, message = "指导老师姓名长度不能超过100个字符") + private String advisorName; + + /** + * 指导老师部门 + */ + @Size(max = 200, message = "指导老师部门长度不能超过200个字符") + private String advisorDept; + + /** + * 指导老师电话 + */ + @Size(max = 20, message = "指导老师电话长度不能超过20个字符") + private String advisorPhone; + + /** + * 指导老师邮箱 + */ + @Size(max = 100, message = "指导老师邮箱长度不能超过100个字符") + @Email(message = "指导老师邮箱格式不正确") + private String advisorEmail; + + /** + * 指导老师用户ID + */ + private Long advisorUserId; + + /** + * 在任状态:1-当前在任,0-历史(已离任) + */ + @NotNull(message = "在任状态不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer status; + + /** + * 上任时间 + */ + @NotNull(message = "上任时间不能为空", groups = { AddGroup.class, EditGroup.class }) + private Date startTime; + + /** + * 离任时间 + */ + private Date endTime; + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglClubAnnualReviewBo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglClubAnnualReviewBo.java new file mode 100644 index 00000000..1036893d --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglClubAnnualReviewBo.java @@ -0,0 +1,163 @@ +package org.dromara.stgl.domain.bo; + +import org.dromara.stgl.domain.StglClubAnnualReview; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.*; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 社团年审记录业务对象 StglClubAnnualReviewBo + * + * @author Cursor AI + * @date 2025-01-15 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = StglClubAnnualReview.class, reverseConvertGenerate = false) +public class StglClubAnnualReviewBo extends BaseEntity { + + /** + * 主键ID + */ + @NotNull(message = "主键ID不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 社团ID + */ + @NotNull(message = "社团ID不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long clubId; + + /** + * 年审年度 + */ + @NotNull(message = "年审年度不能为空", groups = { AddGroup.class, EditGroup.class }) + @Min(value = 2000, message = "年审年度不能小于2000年") + @Max(value = 9999, message = "年审年度不能大于9999年") + private Integer reviewYear; + + /** + * 年审开始日期 + */ + @NotNull(message = "年审开始日期不能为空", groups = { AddGroup.class, EditGroup.class }) + private Date reviewStartDate; + + /** + * 年审结束日期 + */ + @NotNull(message = "年审结束日期不能为空", groups = { AddGroup.class, EditGroup.class }) + private Date reviewEndDate; + + /** + * 年审时间 + */ + @NotNull(message = "年审时间不能为空", groups = { AddGroup.class, EditGroup.class }) + private Date reviewTime; + + /** + * 社团名称 + */ + @NotBlank(message = "社团名称不能为空", groups = { AddGroup.class, EditGroup.class }) + @Size(max = 100, message = "社团名称长度不能超过100个字符") + private String clubName; + + /** + * 成立日期 + */ + private Date establishDate; + + /** + * 社长姓名 + */ + @Size(max = 50, message = "社长姓名长度不能超过50个字符") + private String presidentName; + + /** + * 社团类型 + */ + @Size(max = 20, message = "社团类型长度不能超过20个字符") + private String clubType; + + /** + * 指导老师姓名 + */ + @Size(max = 50, message = "指导老师姓名长度不能超过50个字符") + private String advisorName; + + /** + * 指导老师部门 + */ + @Size(max = 100, message = "指导老师部门长度不能超过100个字符") + private String advisorDept; + + /** + * 是否有章程 + */ + @Pattern(regexp = "^[YN]$", message = "是否有章程必须是Y或N") + private String hasCharter; + + /** + * 章程文件URL + */ + @Size(max = 500, message = "章程文件URL长度不能超过500个字符") + private String charterUrl; + + /** + * 是否有资金 + */ + @Pattern(regexp = "^[YN]$", message = "是否有资金必须是Y或N") + private String hasFunds; + + /** + * 资金数额 + */ + @DecimalMin(value = "0.00", message = "资金数额不能小于0") + @DecimalMax(value = "99999999.99", message = "资金数额不能超过99999999.99") + private BigDecimal fundAmount; + + /** + * 成员数量 + */ + @Min(value = 0, message = "成员数量不能小于0") + @Max(value = 9999, message = "成员数量不能超过9999") + private Integer memberCount; + + /** + * 状态(0-草稿 1-已提交 2-审核中 3-审核通过 4-审核不通过) + */ + @Pattern(regexp = "^[0-4]$", message = "状态值必须是0-4之间的数字") + private String status; + + /** + * 指导老师意见 + */ + private String advisorOpinion; + + /** + * 学院意见 + */ + private String collegeOpinion; + + /** + * 团委意见 + */ + private String youthLeagueOpinion; + + /** + * 成员信息JSON(前端传入) + */ + private String membersJson; + + /** + * 活动信息JSON(前端传入) + */ + private String activitiesJson; + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/ClubActivityPermissionResult.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/ClubActivityPermissionResult.java new file mode 100644 index 00000000..0dd91173 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/ClubActivityPermissionResult.java @@ -0,0 +1,95 @@ +package org.dromara.stgl.domain.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.util.Date; + +/** + * 社团活动权限检查结果 + * + * @author Cursor AI + * @date 2025-01-15 + */ +@Data +public class ClubActivityPermissionResult { + + /** + * 是否可以发起活动 + */ + private Boolean canCreateActivity; + + /** + * 权限状态 + * NORMAL - 正常状态 + * TEMPORARILY_LIMITED - 临时限制(15天内可恢复) + * PERMANENTLY_LIMITED - 永久限制(超过15天) + */ + private String status; + + /** + * 状态描述信息 + */ + private String message; + + /** + * 年审截止时间(仅临时限制时有值) + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date deadlineTime; + + /** + * 剩余小时数(仅临时限制时有值) + */ + private Long remainingHours; + + /** + * 剩余天数(仅临时限制时有值) + */ + private Long remainingDays; + + /** + * 年审开始时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date annualReviewStartTime; + + /** + * 创建正常状态结果 + */ + public static ClubActivityPermissionResult allowed() { + ClubActivityPermissionResult result = new ClubActivityPermissionResult(); + result.setCanCreateActivity(true); + result.setStatus("NORMAL"); + result.setMessage("权限正常,可以发起活动"); + return result; + } + + /** + * 创建临时限制状态结果 + */ + public static ClubActivityPermissionResult temporarilyDenied(String message, Date deadlineTime, + Date annualReviewStartTime, + Long remainingHours, Long remainingDays) { + ClubActivityPermissionResult result = new ClubActivityPermissionResult(); + result.setCanCreateActivity(false); + result.setStatus("TEMPORARILY_LIMITED"); + result.setMessage(message); + result.setDeadlineTime(deadlineTime); + result.setAnnualReviewStartTime(annualReviewStartTime); + result.setRemainingHours(remainingHours); + result.setRemainingDays(remainingDays); + return result; + } + + /** + * 创建永久限制状态结果 + */ + public static ClubActivityPermissionResult permanentlyDenied(String message) { + ClubActivityPermissionResult result = new ClubActivityPermissionResult(); + result.setCanCreateActivity(false); + result.setStatus("PERMANENTLY_LIMITED"); + result.setMessage(message); + return result; + } +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/MyActivityVo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/MyActivityVo.java index 82ac300e..280aa9f8 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/MyActivityVo.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/MyActivityVo.java @@ -65,6 +65,16 @@ public class MyActivityVo implements Serializable { */ private String organizerName; + /** + * 关联部门名称 + */ + private String relatedDeptName; + + /** + * 关联部门名称列表 + */ + private String relatedDeptNames; + /** * 关联类型(1-社团 2-志愿者队伍) */ diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglActivityVo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglActivityVo.java index ccb9848b..a4e7ca65 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglActivityVo.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglActivityVo.java @@ -88,6 +88,27 @@ public class StglActivityVo implements Serializable { */ private String posterUrl; + /** + * 关联部门ID + */ + private Long relatedDeptId; + + /** + * 关联部门名称 + */ + @ExcelProperty(value = "关联部门") + private String relatedDeptName; + + /** + * 关联部门ID列表(JSON格式) + */ + private String relatedDeptIds; + + /** + * 关联部门名称列表(逗号分隔) + */ + private String relatedDeptNames; + /** * 关联社团ID */ diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglAuditorVo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglAuditorVo.java new file mode 100644 index 00000000..cc019388 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglAuditorVo.java @@ -0,0 +1,133 @@ +package org.dromara.stgl.domain.vo; + +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.excel.convert.ExcelDictConvert; +import org.dromara.stgl.domain.StglAuditor; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 社团管理审核人配置视图对象 stgl_auditor + * + * @author ruoyi + * @date 2024-01-01 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = StglAuditor.class) +public class StglAuditorVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @ExcelProperty(value = "主键ID") + private Long id; + + /** + * 用户ID + */ + @ExcelProperty(value = "用户ID") + private Long userId; + + /** + * 用户姓名 + */ + @ExcelProperty(value = "用户姓名") + private String userName; + + /** + * 用户昵称 + */ + @ExcelProperty(value = "用户昵称") + private String nickName; + + /** + * 部门ID + */ + @ExcelProperty(value = "部门ID") + private Long deptId; + + /** + * 部门名称 + */ + @ExcelProperty(value = "部门名称") + private String deptName; + + /** + * 联系电话 + */ + @ExcelProperty(value = "联系电话") + private String phoneNumber; + + /** + * 邮箱地址 + */ + @ExcelProperty(value = "邮箱地址") + private String email; + + /** + * 状态(1正常 0停用) + */ + @ExcelProperty(value = "状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_normal_disable") + private String status; + + /** + * 备注说明 + */ + @ExcelProperty(value = "备注说明") + private String remark; + + /** + * 配置人ID + */ + private Long configBy; + + /** + * 配置人姓名 + */ + @ExcelProperty(value = "配置人") + private String configByName; + + /** + * 租户编号 + */ + private String tenantId; + + /** + * 创建部门 + */ + private Long createDept; + + /** + * 创建者ID + */ + private Long createBy; + + /** + * 创建时间 + */ + @ExcelProperty(value = "创建时间") + private Date createTime; + + /** + * 更新者ID + */ + private Long updateBy; + + /** + * 更新时间 + */ + @ExcelProperty(value = "更新时间") + private Date updateTime; + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClassVo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClassVo.java index 75788a93..c8f12e24 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClassVo.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClassVo.java @@ -65,6 +65,21 @@ public class StglClassVo implements Serializable { */ private Integer activityCount; + /** + * 今日活动名称(审核通过且时间为今天的活动) + */ + private String todayActivityName; + + /** + * 今日活动开始时间 + */ + private String todayActivityStartTime; + + /** + * 今日活动结束时间 + */ + private String todayActivityEndTime; + /** * 创建者 */ diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClubAdvisorHistoryVo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClubAdvisorHistoryVo.java new file mode 100644 index 00000000..3ee6a933 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClubAdvisorHistoryVo.java @@ -0,0 +1,119 @@ +package org.dromara.stgl.domain.vo; + +import org.dromara.stgl.domain.StglClubAdvisorHistory; +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 社团指导老师任职历史视图对象 stgl_club_advisor_history + * + * @author Cursor AI + * @date 2025-01-03 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = StglClubAdvisorHistory.class) +public class StglClubAdvisorHistoryVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @ExcelProperty(value = "主键ID") + private Long id; + + /** + * 社团ID + */ + @ExcelProperty(value = "社团ID") + private Long clubId; + + /** + * 社团名称 + */ + @ExcelProperty(value = "社团名称") + private String clubName; + + /** + * 指导老师姓名 + */ + @ExcelProperty(value = "指导老师姓名") + private String advisorName; + + /** + * 指导老师部门 + */ + @ExcelProperty(value = "指导老师部门") + private String advisorDept; + + /** + * 指导老师电话 + */ + @ExcelProperty(value = "指导老师电话") + private String advisorPhone; + + /** + * 指导老师邮箱 + */ + @ExcelProperty(value = "指导老师邮箱") + private String advisorEmail; + + /** + * 指导老师用户ID + */ + @ExcelProperty(value = "指导老师用户ID") + private Long advisorUserId; + + /** + * 在任状态:1-当前在任,0-历史(已离任) + */ + @ExcelProperty(value = "在任状态", converter = org.dromara.common.excel.convert.ExcelDictConvert.class) + @org.dromara.common.excel.annotation.ExcelDictFormat(dictType = "sys_normal_disable") + private Integer status; + + /** + * 上任时间 + */ + @ExcelProperty(value = "上任时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date startTime; + + /** + * 离任时间 + */ + @ExcelProperty(value = "离任时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date endTime; + + /** + * 创建者 + */ + private Long createBy; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** + * 更新者 + */ + private Long updateBy; + + /** + * 更新时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClubAnnualReviewActivityVo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClubAnnualReviewActivityVo.java new file mode 100644 index 00000000..a8c73bdd --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClubAnnualReviewActivityVo.java @@ -0,0 +1,111 @@ +package org.dromara.stgl.domain.vo; + +import org.dromara.stgl.domain.StglClubAnnualReviewActivity; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 社团年审记录活动视图对象 StglClubAnnualReviewActivityVo + * + * @author Cursor AI + * @date 2025-01-15 + */ +@Data +@AutoMapper(target = StglClubAnnualReviewActivity.class) +public class StglClubAnnualReviewActivityVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + private Long id; + + /** + * 年审记录ID + */ + private Long annualReviewId; + + /** + * 活动名称 + */ + private String activityName; + + /** + * 活动时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date activityTime; + + /** + * 活动开始时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date activityStartTime; + + /** + * 活动结束时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date activityEndTime; + + /** + * 活动地点 + */ + private String activityLocation; + + /** + * 活动类型 + */ + private String activityType; + + /** + * 参与人数 + */ + private Integer participantCount; + + /** + * 活动描述 + */ + private String activityDescription; + + /** + * 创建者 + */ + private Long createBy; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** + * 更新者 + */ + private Long updateBy; + + /** + * 更新时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + // 为了兼容前端,添加一些别名字段 + /** + * 地点(别名) + */ + public String getLocation() { + return this.activityLocation; + } + + public void setLocation(String location) { + this.activityLocation = location; + } +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClubAnnualReviewVo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClubAnnualReviewVo.java new file mode 100644 index 00000000..3b962b9b --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClubAnnualReviewVo.java @@ -0,0 +1,156 @@ +package org.dromara.stgl.domain.vo; + +import org.dromara.stgl.domain.StglClubAnnualReview; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 社团年审记录视图对象 StglClubAnnualReviewVo + * + * @author Cursor AI + * @date 2025-01-15 + */ +@Data +@AutoMapper(target = StglClubAnnualReview.class) +public class StglClubAnnualReviewVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + private Long id; + + /** + * 社团ID + */ + private Long clubId; + + /** + * 年审年度 + */ + private Integer reviewYear; + + /** + * 年审开始日期 + */ + @JsonFormat(pattern = "yyyy-MM-dd") + private Date reviewStartDate; + + /** + * 年审结束日期 + */ + @JsonFormat(pattern = "yyyy-MM-dd") + private Date reviewEndDate; + + /** + * 年审时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd") + private Date reviewTime; + + /** + * 社团名称 + */ + private String clubName; + + /** + * 成立日期 + */ + @JsonFormat(pattern = "yyyy-MM-dd") + private Date establishDate; + + /** + * 社长姓名 + */ + private String presidentName; + + /** + * 社团类型 + */ + private String clubType; + + /** + * 指导老师姓名 + */ + private String advisorName; + + /** + * 指导老师部门 + */ + private String advisorDept; + + /** + * 是否有章程 + */ + private String hasCharter; + + /** + * 章程文件URL + */ + private String charterUrl; + + /** + * 是否有资金 + */ + private String hasFunds; + + /** + * 资金数额 + */ + private BigDecimal fundAmount; + + /** + * 成员数量 + */ + private Integer memberCount; + + /** + * 状态(0-草稿 1-已提交 2-审核中 3-审核通过 4-审核不通过) + */ + private String status; + + /** + * 指导老师意见 + */ + private String advisorOpinion; + + /** + * 学院意见 + */ + private String collegeOpinion; + + /** + * 团委意见 + */ + private String youthLeagueOpinion; + + /** + * 成员信息JSON(用于前端显示) + */ + private String membersJson; + + /** + * 活动信息JSON(用于前端显示) + */ + private String activitiesJson; + + // 继承的基础字段 + private Long createBy; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + private Long updateBy; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglAnnualReviewAuditMapper.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglAnnualReviewAuditMapper.java new file mode 100644 index 00000000..d1f46809 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglAnnualReviewAuditMapper.java @@ -0,0 +1,40 @@ +package org.dromara.stgl.mapper; + +import org.dromara.stgl.domain.StglAnnualReviewAudit; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; + +import java.util.List; + +/** + * 社团年审审批Mapper接口 + * + * @author ruoyi + * @date 2024-01-01 + */ +public interface StglAnnualReviewAuditMapper extends BaseMapperPlus { + + /** + * 根据年审记录ID查询审批记录 + * + * @param annualReviewId 年审记录ID + * @return 审批记录列表 + */ + List selectByAnnualReviewId(Long annualReviewId); + + /** + * 根据年审记录ID和节点代码查询审批记录 + * + * @param annualReviewId 年审记录ID + * @param nodeCode 节点代码 + * @return 审批记录列表 + */ + List selectByAnnualReviewIdAndNodeCode(Long annualReviewId, String nodeCode); + + /** + * 根据审批人ID查询待审批记录 + * + * @param approverId 审批人ID + * @return 审批记录列表 + */ + List selectPendingByApproverId(Long approverId); +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglAuditorMapper.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglAuditorMapper.java new file mode 100644 index 00000000..63f473f0 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglAuditorMapper.java @@ -0,0 +1,66 @@ +package org.dromara.stgl.mapper; + +import org.dromara.stgl.domain.StglAuditor; +import org.dromara.stgl.domain.vo.StglAuditorVo; +import org.dromara.stgl.domain.StglUserWithDeptInfo; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import org.apache.ibatis.annotations.Param; +import java.util.List; + +/** + * 社团管理审核人配置Mapper接口 + * + * @author ruoyi + * @date 2024-01-01 + */ +public interface StglAuditorMapper extends BaseMapperPlus { + + /** + * 查询社团管理审核人配置 + * + * @param id 社团管理审核人配置主键 + * @return 社团管理审核人配置 + */ + StglAuditorVo selectStglAuditorVoById(Long id); + + /** + * 查询社团管理审核人配置列表 + * + * @param stglAuditor 社团管理审核人配置 + * @return 社团管理审核人配置集合 + */ + List selectStglAuditorVoList(StglAuditor stglAuditor); + + /** + * 根据用户ID检查是否已存在审核人配置 + * + * @param userId 用户ID + * @return 记录数 + */ + int checkUserExists(@Param("userId") Long userId); + + /** + * 批量启用审核人 + * + * @param ids 审核人ID列表 + * @return 影响行数 + */ + int enableAuditorsByIds(@Param("ids") List ids); + + /** + * 批量停用审核人 + * + * @param ids 审核人ID列表 + * @return 影响行数 + */ + int disableAuditorsByIds(@Param("ids") List ids); + + /** + * 查询完整的用户信息(包含部门信息)用于批量添加 + * + * @param userIds 用户ID列表 + * @return 用户和部门完整信息列表 + */ + List selectUsersWithDeptInfo(@Param("userIds") List userIds); + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglClubAdvisorHistoryMapper.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglClubAdvisorHistoryMapper.java new file mode 100644 index 00000000..0ec99d83 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglClubAdvisorHistoryMapper.java @@ -0,0 +1,54 @@ +package org.dromara.stgl.mapper; + +import org.dromara.stgl.domain.StglClubAdvisorHistory; +import org.dromara.stgl.domain.vo.StglClubAdvisorHistoryVo; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 社团指导老师任职历史Mapper接口 + * + * @author Cursor AI + * @date 2025-01-03 + */ +public interface StglClubAdvisorHistoryMapper extends BaseMapperPlus { + + /** + * 查询指定社团的当前在任指导老师 + * + * @param clubId 社团ID + * @return 当前在任指导老师列表 + */ + List selectCurrentAdvisorsByClubId(@Param("clubId") Long clubId); + + /** + * 查询指定社团的历史指导老师(已离任) + * + * @param clubId 社团ID + * @return 历史指导老师列表 + */ + List selectHistoryAdvisorsByClubId(@Param("clubId") Long clubId); + + /** + * 查询指定社团的所有指导老师(包含当前和历史) + * + * @param clubId 社团ID + * @return 所有指导老师列表 + */ + List selectAllAdvisorsByClubId(@Param("clubId") Long clubId); + + /** + * 批量设置指导老师为历史状态(离任) + * + * @param clubId 社团ID + * @param advisorNames 指导老师姓名列表 + * @param endTime 离任时间 + * @return 更新记录数 + */ + int batchSetHistoryStatus(@Param("clubId") Long clubId, + @Param("advisorNames") List advisorNames, + @Param("endTime") java.util.Date endTime); + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglClubAnnualReviewActivityMapper.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglClubAnnualReviewActivityMapper.java new file mode 100644 index 00000000..d5be4bdd --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglClubAnnualReviewActivityMapper.java @@ -0,0 +1,27 @@ +package org.dromara.stgl.mapper; + +import org.dromara.stgl.domain.StglClubAnnualReviewActivity; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 社团年审记录活动Mapper接口 + * + * @author Lion Li + * @date 2024-12-31 + */ +public interface StglClubAnnualReviewActivityMapper extends BaseMapperPlus { + + /** + * 根据年审记录ID查询活动列表 + */ + List selectActivitiesByAnnualReviewId(@Param("annualReviewId") Long annualReviewId); + + /** + * 根据年审记录ID删除活动 + */ + int deleteActivitiesByAnnualReviewId(@Param("annualReviewId") Long annualReviewId); + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglClubAnnualReviewMapper.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglClubAnnualReviewMapper.java new file mode 100644 index 00000000..8e7514ab --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglClubAnnualReviewMapper.java @@ -0,0 +1,15 @@ +package org.dromara.stgl.mapper; + +import org.dromara.stgl.domain.StglClubAnnualReview; +import org.dromara.stgl.domain.vo.StglClubAnnualReviewVo; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; + +/** + * 社团年审记录Mapper接口 + * + * @author Cursor AI + * @date 2025-01-15 + */ +public interface StglClubAnnualReviewMapper extends BaseMapperPlus { + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglClubAnnualReviewMemberMapper.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglClubAnnualReviewMemberMapper.java new file mode 100644 index 00000000..5348662f --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglClubAnnualReviewMemberMapper.java @@ -0,0 +1,27 @@ +package org.dromara.stgl.mapper; + +import org.dromara.stgl.domain.StglClubAnnualReviewMember; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 社团年审记录成员Mapper接口 + * + * @author Lion Li + * @date 2024-12-31 + */ +public interface StglClubAnnualReviewMemberMapper extends BaseMapperPlus { + + /** + * 根据年审记录ID查询成员列表 + */ + List selectMembersByAnnualReviewId(@Param("annualReviewId") Long annualReviewId); + + /** + * 根据年审记录ID删除成员 + */ + int deleteMembersByAnnualReviewId(@Param("annualReviewId") Long annualReviewId); + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglAnnualReviewAuditService.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglAnnualReviewAuditService.java new file mode 100644 index 00000000..8c94948e --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglAnnualReviewAuditService.java @@ -0,0 +1,104 @@ +package org.dromara.stgl.service; + +import org.dromara.stgl.domain.StglAnnualReviewAudit; +import org.dromara.stgl.domain.bo.StglAnnualReviewApprovalBo; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; + +import java.util.Collection; +import java.util.List; + +/** + * 社团年审审批Service接口 + * + * @author ruoyi + * @date 2024-01-01 + */ +public interface IStglAnnualReviewAuditService { + + /** + * 查询社团年审审批 + * + * @param id 社团年审审批主键 + * @return 社团年审审批 + */ + StglAnnualReviewAudit queryById(Long id); + + /** + * 根据年审记录ID查询审批记录 + * + * @param annualReviewId 年审记录ID + * @return 审批记录列表 + */ + List queryByAnnualReviewId(Long annualReviewId); + + /** + * 根据年审记录ID和节点代码查询审批记录 + * + * @param annualReviewId 年审记录ID + * @param nodeCode 节点代码 + * @return 审批记录列表 + */ + List queryByAnnualReviewIdAndNodeCode(Long annualReviewId, String nodeCode); + + /** + * 根据审批人ID查询待审批记录 + * + * @param approverId 审批人ID + * @return 审批记录列表 + */ + List queryPendingByApproverId(Long approverId); + + /** + * 新增社团年审审批 + * + * @param stglAnnualReviewAudit 社团年审审批 + * @return 结果 + */ + Boolean insertByBo(StglAnnualReviewAudit stglAnnualReviewAudit); + + /** + * 修改社团年审审批 + * + * @param stglAnnualReviewAudit 社团年审审批 + * @return 结果 + */ + Boolean updateByBo(StglAnnualReviewAudit stglAnnualReviewAudit); + + /** + * 校验并批量删除社团年审审批信息 + * + * @param ids 需要删除的社团年审审批主键集合 + * @param isValid 是否校验,true-删除前校验,false-不校验 + * @return 结果 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 创建指导老师审批记录 + * + * @param annualReviewId 年审记录ID + * @param advisorIds 指导老师ID列表 + * @param advisorNames 指导老师姓名列表 + * @return 结果 + */ + Boolean createAdvisorApprovalRecords(Long annualReviewId, List advisorIds, List advisorNames); + + /** + * 审批年审记录 + * + * @param auditId 审批记录ID + * @param approveResult 审批结果 + * @param approveOpinion 审批意见 + * @return 结果 + */ + Boolean approveAnnualReview(Long auditId, String approveResult, String approveOpinion); + + /** + * 审批年审记录(支持动态审核人选择) + * + * @param approvalBo 审批请求对象 + * @return 结果 + */ + Boolean approveAnnualReview(StglAnnualReviewApprovalBo approvalBo); +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglAuditorService.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglAuditorService.java new file mode 100644 index 00000000..ad34e337 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglAuditorService.java @@ -0,0 +1,102 @@ +package org.dromara.stgl.service; + +import org.dromara.stgl.domain.vo.StglAuditorVo; +import org.dromara.stgl.domain.bo.StglAuditorBo; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; + +import java.util.Collection; +import java.util.List; + +/** + * 社团管理审核人配置Service接口 + * + * @author ruoyi + * @date 2024-01-01 + */ +public interface IStglAuditorService { + + /** + * 查询社团管理审核人配置 + * + * @param id 主键 + * @return 社团管理审核人配置 + */ + StglAuditorVo queryById(Long id); + + /** + * 查询社团管理审核人配置列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 社团管理审核人配置集合 + */ + TableDataInfo queryPageList(StglAuditorBo bo, PageQuery pageQuery); + + /** + * 查询社团管理审核人配置列表 + * + * @param bo 查询条件 + * @return 社团管理审核人配置集合 + */ + List queryList(StglAuditorBo bo); + + /** + * 新增社团管理审核人配置 + * + * @param bo 社团管理审核人配置 + * @return 结果 + */ + Boolean insertByBo(StglAuditorBo bo); + + /** + * 修改社团管理审核人配置 + * + * @param bo 社团管理审核人配置 + * @return 结果 + */ + Boolean updateByBo(StglAuditorBo bo); + + /** + * 校验并批量删除社团管理审核人配置信息 + * + * @param ids 需要删除的主键集合 + * @return 结果 + */ + Boolean deleteWithValidByIds(Collection ids); + + /** + * 批量添加审核人 + * + * @param userIds 用户ID列表 + * @param remark 备注 + * @return 结果 + */ + Boolean batchAddAuditors(List userIds, String remark); + + /** + * 查询我配置的审核人列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 审核人列表 + */ + TableDataInfo queryMyConfigList(StglAuditorBo bo, PageQuery pageQuery); + + /** + * 批量启用审核人 + * + * @param ids 审核人ID列表 + * @return 结果 + */ + Boolean enableAuditors(List ids); + + /** + * 批量停用审核人 + * + * @param ids 审核人ID列表 + * @return 结果 + */ + Boolean disableAuditors(List ids); + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglClubAdvisorHistoryService.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglClubAdvisorHistoryService.java new file mode 100644 index 00000000..33704d76 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglClubAdvisorHistoryService.java @@ -0,0 +1,130 @@ +package org.dromara.stgl.service; + +import org.dromara.stgl.domain.vo.StglClubAdvisorHistoryVo; +import org.dromara.stgl.domain.bo.StglClubAdvisorHistoryBo; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; + +import java.util.Collection; +import java.util.List; + +/** + * 社团指导老师任职历史Service接口 + * + * @author Cursor AI + * @date 2025-01-03 + */ +public interface IStglClubAdvisorHistoryService { + + /** + * 查询社团指导老师任职历史 + */ + StglClubAdvisorHistoryVo queryById(Long id); + + /** + * 查询社团指导老师任职历史列表 + */ + TableDataInfo queryPageList(StglClubAdvisorHistoryBo bo, PageQuery pageQuery); + + /** + * 查询社团指导老师任职历史列表 + */ + List queryList(StglClubAdvisorHistoryBo bo); + + /** + * 新增社团指导老师任职历史 + */ + Boolean insertByBo(StglClubAdvisorHistoryBo bo); + + /** + * 修改社团指导老师任职历史 + */ + Boolean updateByBo(StglClubAdvisorHistoryBo bo); + + /** + * 校验并批量删除社团指导老师任职历史信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 查询指定社团的当前在任指导老师 + * + * @param clubId 社团ID + * @return 当前在任指导老师列表 + */ + List getCurrentAdvisorsByClubId(Long clubId); + + /** + * 查询指定社团的历史指导老师(已离任) + * + * @param clubId 社团ID + * @return 历史指导老师列表 + */ + List getHistoryAdvisorsByClubId(Long clubId); + + /** + * 查询指定社团的所有指导老师(包含当前和历史) + * + * @param clubId 社团ID + * @return 所有指导老师列表 + */ + List getAllAdvisorsByClubId(Long clubId); + + /** + * 更新社团指导老师历史记录 + * 对比新旧指导老师列表,处理新增和离任 + * + * @param clubId 社团ID + * @param newAdvisors 新的指导老师列表 + */ + void updateClubAdvisors(Long clubId, List newAdvisors); + + /** + * 更新社团指导老师历史记录(指定开始时间) + * 对比新旧指导老师列表,处理新增和离任 + * + * @param clubId 社团ID + * @param newAdvisors 新的指导老师列表 + * @param startTime 新指导老师的上任时间(如社团成立日期) + */ + void updateClubAdvisors(Long clubId, List newAdvisors, java.util.Date startTime); + + /** + * 指导老师信息 + */ + class AdvisorInfo { + private String name; + private String dept; + private String phone; + private String email; + private Long userId; + + // 构造方法 + public AdvisorInfo() {} + + public AdvisorInfo(String name, String dept, String phone, String email, Long userId) { + this.name = name; + this.dept = dept; + this.phone = phone; + this.email = email; + this.userId = userId; + } + + // getter和setter方法 + public String getName() { return name; } + public void setName(String name) { this.name = name; } + + public String getDept() { return dept; } + public void setDept(String dept) { this.dept = dept; } + + public String getPhone() { return phone; } + public void setPhone(String phone) { this.phone = phone; } + + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + + public Long getUserId() { return userId; } + public void setUserId(Long userId) { this.userId = userId; } + } + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglClubAnnualReviewService.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglClubAnnualReviewService.java new file mode 100644 index 00000000..fcf91f1b --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglClubAnnualReviewService.java @@ -0,0 +1,89 @@ +package org.dromara.stgl.service; + +import org.dromara.stgl.domain.vo.StglClubAnnualReviewVo; +import org.dromara.stgl.domain.bo.StglClubAnnualReviewBo; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; + +import java.util.Collection; +import java.util.List; + +/** + * 社团年审记录Service接口 + * + * @author Cursor AI + * @date 2025-01-15 + */ +public interface IStglClubAnnualReviewService { + + /** + * 查询社团年审记录 + */ + StglClubAnnualReviewVo queryById(Long id); + + /** + * 查询社团年审记录列表 + */ + TableDataInfo queryPageList(StglClubAnnualReviewBo bo, PageQuery pageQuery); + + /** + * 查询社团年审记录列表 + */ + List queryList(StglClubAnnualReviewBo bo); + + /** + * 新增社团年审记录 + */ + Boolean insertByBo(StglClubAnnualReviewBo bo); + + /** + * 修改社团年审记录 + */ + Boolean updateByBo(StglClubAnnualReviewBo bo); + + /** + * 校验并批量删除社团年审记录信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 提交年审记录 + */ + Boolean submitReview(Long id); + + /** + * 撤回年审记录 + */ + Boolean withdrawReview(Long id); + + /** + * 审核年审记录 + */ + Boolean auditReview(Long id, String auditResult, String auditOpinion, String auditType); + + /** + * 获取当前用户的年审记录列表 + */ + TableDataInfo queryMyPageList(StglClubAnnualReviewBo bo, PageQuery pageQuery); + + /** + * 检查是否可以创建年审记录 + */ + Boolean checkCanCreateReview(Long clubId, Integer reviewYear); + + /** + * 根据社团ID和成立日期获取一年内的活动数据 + * @param clubId 社团ID + * @param establishDate 成立日期字符串 (yyyy-MM-dd格式) + * @return 活动列表 + */ + List getClubActivitiesByReviewYear(Long clubId, Integer reviewYear); + + /** + * 根据年审记录ID获取对应的活动数据 + * @param annualReviewId 年审记录ID + * @return 年审活动列表 + */ + List getAnnualReviewActivities(Long annualReviewId); + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglClubPermissionService.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglClubPermissionService.java new file mode 100644 index 00000000..adae1847 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglClubPermissionService.java @@ -0,0 +1,55 @@ +package org.dromara.stgl.service; + +import org.dromara.stgl.domain.vo.ClubActivityPermissionResult; + +import java.util.List; +import java.util.Map; + +/** + * 社团权限管理Service接口 + * + * @author Cursor AI + * @date 2025-01-15 + */ +public interface IStglClubPermissionService { + + /** + * 检查社团活动权限(实时计算) + * + * @param clubId 社团ID + * @return 权限检查结果 + */ + ClubActivityPermissionResult checkClubActivityPermission(Long clubId); + + /** + * 批量检查社团活动权限 + * + * @param clubIds 社团ID列表 + * @return 权限检查结果映射(key: clubId, value: 权限结果) + */ + Map batchCheckClubActivityPermission(List clubIds); + + /** + * 检查社团是否在年审窗口期内 + * + * @param clubId 社团ID + * @return true-在窗口期内,false-不在窗口期内 + */ + boolean isInAnnualReviewWindow(Long clubId); + + /** + * 获取社团距离下次年审的天数 + * + * @param clubId 社团ID + * @return 距离年审的天数,负数表示已过期 + */ + long getDaysUntilNextAnnualReview(Long clubId); + + /** + * 检查社团是否已在当前年审期内提交年审 + * + * @param clubId 社团ID + * @return true-已提交,false-未提交 + */ + boolean hasSubmittedCurrentYearAnnualReview(Long clubId); +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglActivityServiceImpl.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglActivityServiceImpl.java index a49dd247..c14459b9 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglActivityServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglActivityServiceImpl.java @@ -129,7 +129,7 @@ public class StglActivityServiceImpl extends ServiceImpl params = new HashMap<>(); params.put("registrationStatusList", Arrays.asList("0", "1")); @@ -258,6 +258,9 @@ public class StglActivityServiceImpl extends ServiceImpl deptIds = JsonUtils.parseArray(bo.getRelatedDeptIds(), Long.class); + + if (deptIds != null && !deptIds.isEmpty()) { + // 设置多选部门ID列表(JSON格式) + entity.setRelatedDeptIds(JsonUtils.toJsonString(deptIds)); + + // 设置第一个部门作为主要关联部门(保持兼容性) + entity.setRelatedDeptId(deptIds.get(0)); + + // 如果有多选部门名称,直接使用 + if (StringUtils.isNotBlank(bo.getRelatedDeptNames())) { + entity.setRelatedDeptNames(bo.getRelatedDeptNames()); + + // 设置第一个部门名称作为主要关联部门名称 + String[] deptNames = bo.getRelatedDeptNames().split(","); + if (deptNames.length > 0) { + entity.setRelatedDeptName(deptNames[0].trim()); + } + } + + log.info("处理多选部门数据 - 活动ID: {}, 部门ID列表: {}, 部门名称列表: {}", + entity.getId(), entity.getRelatedDeptIds(), entity.getRelatedDeptNames()); + } + } catch (Exception e) { + log.error("解析多选部门数据失败: {}", bo.getRelatedDeptIds(), e); + // 解析失败时清空多选字段 + entity.setRelatedDeptIds(null); + entity.setRelatedDeptNames(null); + } + } else { + // 没有多选部门数据,清空相关字段 + entity.setRelatedDeptIds(null); + entity.setRelatedDeptNames(null); + } + } } \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglAnnualReviewAuditServiceImpl.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglAnnualReviewAuditServiceImpl.java new file mode 100644 index 00000000..80af87a3 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglAnnualReviewAuditServiceImpl.java @@ -0,0 +1,409 @@ +package org.dromara.stgl.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.utils.MapstructUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.stgl.domain.StglAnnualReviewAudit; +import org.dromara.stgl.domain.StglClubAnnualReview; +import org.dromara.stgl.domain.bo.StglAnnualReviewApprovalBo; +import org.dromara.stgl.mapper.StglAnnualReviewAuditMapper; +import org.dromara.stgl.mapper.StglClubAnnualReviewMapper; +import org.dromara.stgl.service.IStglAnnualReviewAuditService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * 社团年审审批Service业务层处理 + * + * @author ruoyi + * @date 2024-01-01 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class StglAnnualReviewAuditServiceImpl implements IStglAnnualReviewAuditService { + + private final StglAnnualReviewAuditMapper baseMapper; + private final StglClubAnnualReviewMapper annualReviewMapper; + + /** + * 查询社团年审审批 + */ + @Override + public StglAnnualReviewAudit queryById(Long id) { + return baseMapper.selectById(id); + } + + /** + * 根据年审记录ID查询审批记录 + */ + @Override + public List queryByAnnualReviewId(Long annualReviewId) { + return baseMapper.selectByAnnualReviewId(annualReviewId); + } + + /** + * 根据年审记录ID和节点代码查询审批记录 + */ + @Override + public List queryByAnnualReviewIdAndNodeCode(Long annualReviewId, String nodeCode) { + return baseMapper.selectByAnnualReviewIdAndNodeCode(annualReviewId, nodeCode); + } + + /** + * 根据审批人ID查询待审批记录 + */ + @Override + public List queryPendingByApproverId(Long approverId) { + return baseMapper.selectPendingByApproverId(approverId); + } + + /** + * 新增社团年审审批 + */ + @Override + public Boolean insertByBo(StglAnnualReviewAudit bo) { + StglAnnualReviewAudit add = BeanUtil.toBean(bo, StglAnnualReviewAudit.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改社团年审审批 + */ + @Override + public Boolean updateByBo(StglAnnualReviewAudit bo) { + StglAnnualReviewAudit update = BeanUtil.toBean(bo, StglAnnualReviewAudit.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(StglAnnualReviewAudit entity) { + // TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除社团年审审批 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if (isValid) { + // TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteByIds(ids) > 0; + } + + /** + * 创建指导老师审批记录 + */ + @Override + @Transactional + public Boolean createAdvisorApprovalRecords(Long annualReviewId, List advisorIds, List advisorNames) { + if (ObjectUtil.isEmpty(advisorIds) || ObjectUtil.isEmpty(advisorNames) || advisorIds.size() != advisorNames.size()) { + return false; + } + + // 删除已存在的指导老师审批记录 + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(StglAnnualReviewAudit::getAnnualReviewId, annualReviewId) + .eq(StglAnnualReviewAudit::getNodeCode, "advisor_approve"); + baseMapper.delete(wrapper); + + // 创建新的指导老师审批记录 + for (int i = 0; i < advisorIds.size(); i++) { + StglAnnualReviewAudit audit = new StglAnnualReviewAudit(); + audit.setAnnualReviewId(annualReviewId); + audit.setNodeCode("advisor_approve"); + audit.setNodeName("指导老师审批"); + + // 设置节点顺序:单个指导老师为1.0,多个指导老师为1.1, 1.2, 1.3... + if (advisorIds.size() == 1) { + audit.setNodeOrder(new BigDecimal("1.0")); + } else { + audit.setNodeOrder(new BigDecimal("1." + (i + 1))); + } + + // 设置节点状态:第一个为pending,其他为waiting + audit.setNodeStatus(i == 0 ? "pending" : "waiting"); + audit.setApproverId(advisorIds.get(i)); + audit.setApproverName(advisorNames.get(i)); + + baseMapper.insert(audit); + } + + return true; + } + + /** + * 审批年审记录 + */ + @Override + @Transactional + public Boolean approveAnnualReview(Long auditId, String approveResult, String approveOpinion) { + // 1. 获取当前审批记录 + StglAnnualReviewAudit currentAudit = baseMapper.selectById(auditId); + if (currentAudit == null || !"pending".equals(currentAudit.getNodeStatus())) { + return false; + } + + // 2. 更新当前审批记录 + currentAudit.setNodeStatus("approved"); + currentAudit.setApproveResult(approveResult); + currentAudit.setApproveOpinion(approveOpinion); + currentAudit.setApproveTime(new Date()); + baseMapper.updateById(currentAudit); + + // 3. 如果审批不通过,直接更新年审记录状态为4(审核不通过) + if ("不通过".equals(approveResult)) { + StglClubAnnualReview annualReview = annualReviewMapper.selectById(currentAudit.getAnnualReviewId()); + if (annualReview != null) { + annualReview.setStatus("4"); + annualReviewMapper.updateById(annualReview); + } + return true; + } + + // 4. 如果审批通过,检查是否有下一个待审批的同环节审批人 + List samePhaseAudits = baseMapper.selectByAnnualReviewIdAndNodeCode( + currentAudit.getAnnualReviewId(), currentAudit.getNodeCode()); + + // 查找下一个waiting状态的审批记录 + StglAnnualReviewAudit nextAudit = null; + for (StglAnnualReviewAudit audit : samePhaseAudits) { + if ("waiting".equals(audit.getNodeStatus()) && + audit.getNodeOrder().compareTo(currentAudit.getNodeOrder()) > 0) { + if (nextAudit == null || audit.getNodeOrder().compareTo(nextAudit.getNodeOrder()) < 0) { + nextAudit = audit; + } + } + } + + // 5. 如果有下一个审批人,将其状态改为pending + if (nextAudit != null) { + nextAudit.setNodeStatus("pending"); + baseMapper.updateById(nextAudit); + } else { + // 6. 如果同环节所有审批人都审批通过,检查是否是第一个审批节点,决定是否更新状态为2(审核中) + boolean allApproved = samePhaseAudits.stream() + .allMatch(audit -> "approved".equals(audit.getNodeStatus()) && "通过".equals(audit.getApproveResult())); + + if (allApproved) { + // 只有第一个审批节点(node_order=1.1)通过时才设置为2-审核中 + if (isFirstApprovalNode(currentAudit)) { + StglClubAnnualReview annualReview = annualReviewMapper.selectById(currentAudit.getAnnualReviewId()); + if (annualReview != null) { + annualReview.setStatus("2"); + annualReviewMapper.updateById(annualReview); + log.info("第一个审批节点通过,年审记录状态更新为2-审核中,年审记录ID: {}", annualReview.getId()); + } + } else { + log.info("非第一个审批节点通过,保持当前状态,当前节点: {}, node_order: {}", + currentAudit.getNodeName(), currentAudit.getNodeOrder()); + } + } + } + + return true; + } + + /** + * 审批年审记录(支持动态审核人选择) + */ + @Override + @Transactional + public Boolean approveAnnualReview(StglAnnualReviewApprovalBo approvalBo) { + // 调用原有方法进行基础审批 + Boolean result = approveAnnualReview(approvalBo.getAuditId(), + approvalBo.getApproveResult(), + approvalBo.getApproveOpinion()); + + // 如果审批通过且有指定下一步审核人,创建新的审批记录 + if (result && "通过".equals(approvalBo.getApproveResult()) && + approvalBo.getNextAuditors() != null && !approvalBo.getNextAuditors().isEmpty()) { + + // 获取当前审批记录 + StglAnnualReviewAudit currentAudit = baseMapper.selectById(approvalBo.getAuditId()); + if (currentAudit != null) { + // 创建下一步审核人的审批记录 + createNextStepAuditRecords(currentAudit.getAnnualReviewId(), approvalBo.getNextAuditors()); + } + } else if (result && "通过".equals(approvalBo.getApproveResult()) && + (approvalBo.getNextAuditors() == null || approvalBo.getNextAuditors().isEmpty())) { + + // 如果审批通过且没有下一步审核人,检查是否是校团委最后节点,决定是否结束流程 + StglAnnualReviewAudit currentAudit = baseMapper.selectById(approvalBo.getAuditId()); + if (currentAudit != null && isSchoolCommitteeLastNode(currentAudit)) { + // 校团委最后节点审批通过,流程结束,将年审记录状态设置为3(审核通过) + StglClubAnnualReview annualReview = annualReviewMapper.selectById(currentAudit.getAnnualReviewId()); + if (annualReview != null) { + annualReview.setStatus("3"); + annualReviewMapper.updateById(annualReview); + log.info("校团委最后节点审批通过,年审流程结束,年审记录ID: {}, 状态更新为: 3", annualReview.getId()); + } + } + } + + return result; + } + + /** + * 创建下一步审批记录 + */ + private void createNextStepAuditRecords(Long annualReviewId, List nextAuditors) { + // 查询已有的审批记录,确定下一个审批层级 + List existingAudits = baseMapper.selectByAnnualReviewId(annualReviewId); + + // 确定下一个审批层级 + int nextLevel = determineNextApprovalLevel(existingAudits); + + for (int i = 0; i < nextAuditors.size(); i++) { + StglAnnualReviewApprovalBo.NextAuditor auditor = nextAuditors.get(i); + + StglAnnualReviewAudit nextAudit = new StglAnnualReviewAudit(); + nextAudit.setAnnualReviewId(annualReviewId); + nextAudit.setNodeCode(getNodeCodeByLevel(nextLevel)); + nextAudit.setNodeName(getNodeNameByLevel(nextLevel)); + + // 根据层级和顺序设置node_order + if (nextAuditors.size() == 1 && nextLevel == 3) { + // 校团委审批单个审核人为3.0 + nextAudit.setNodeOrder(new BigDecimal(nextLevel + ".0")); + } else { + // 其他情况按 层级.序号 格式设置 + nextAudit.setNodeOrder(new BigDecimal(nextLevel + "." + (i + 1))); + } + + nextAudit.setApproverId(auditor.getUserId()); + nextAudit.setApproverName(auditor.getNickName() != null ? auditor.getNickName() : auditor.getUserName()); + nextAudit.setNodeStatus(i == 0 ? "pending" : "waiting"); // 第一个为pending,其余为waiting + nextAudit.setCreateTime(new Date()); + + baseMapper.insert(nextAudit); + } + } + + /** + * 确定下一个审批层级 + */ + private int determineNextApprovalLevel(List existingAudits) { + int maxLevel = 1; // 默认从指导老师审批层级开始 + + for (StglAnnualReviewAudit audit : existingAudits) { + BigDecimal nodeOrder = audit.getNodeOrder(); + if (nodeOrder != null) { + int level = nodeOrder.intValue(); // 获取整数部分作为层级 + if (level > maxLevel) { + maxLevel = level; + } + } + } + + return maxLevel + 1; // 下一个层级 + } + + /** + * 根据层级获取节点代码 + */ + private String getNodeCodeByLevel(int level) { + switch (level) { + case 1: + return "advisor_approve"; + case 2: + return "college_approve"; + case 3: + return "youth_league_approve"; + default: + return "college_approve"; // 默认为二级学院审批 + } + } + + /** + * 根据层级获取节点名称 + */ + private String getNodeNameByLevel(int level) { + switch (level) { + case 1: + return "指导老师审批"; + case 2: + return "二级学院审批"; + case 3: + return "校团委审批"; + default: + return "二级学院审批"; // 默认为二级学院审批 + } + } + + /** + * 判断是否为第一个审批节点(node_order=1.1) + */ + private boolean isFirstApprovalNode(StglAnnualReviewAudit currentAudit) { + // 检查node_order是否为1.1 + return currentAudit != null && + currentAudit.getNodeOrder() != null && + currentAudit.getNodeOrder().compareTo(new BigDecimal("1.1")) == 0; + } + + /** + * 判断当前是否是校团委审批的最后一个节点 + */ + private boolean isSchoolCommitteeLastNode(StglAnnualReviewAudit currentAudit) { + if (currentAudit == null || currentAudit.getNodeOrder() == null) { + return false; + } + + String currentNodeName = currentAudit.getNodeName(); + BigDecimal currentNodeOrder = currentAudit.getNodeOrder(); + + // 检查是否是校团委审批节点且node_order是3.X格式 + boolean isSchoolCommitteeNode = "校团委审批".equals(currentNodeName); + boolean isNodeOrder3X = currentNodeOrder.compareTo(new BigDecimal("3")) >= 0 && + currentNodeOrder.compareTo(new BigDecimal("4")) < 0; + + if (!isSchoolCommitteeNode || !isNodeOrder3X) { + return false; + } + + // 获取同一环节的所有节点 + List samePhaseNodes = baseMapper.selectByAnnualReviewIdAndNodeCode( + currentAudit.getAnnualReviewId(), currentAudit.getNodeCode()); + + if (samePhaseNodes.isEmpty()) { + return false; + } + + // 找出同一环节中最大的nodeOrder + BigDecimal maxNodeOrder = samePhaseNodes.stream() + .map(StglAnnualReviewAudit::getNodeOrder) + .filter(order -> order != null) + .max(BigDecimal::compareTo) + .orElse(BigDecimal.ZERO); + + // 当前节点的nodeOrder等于同环节最大nodeOrder时,表示是最后一个节点 + boolean isLastNode = currentNodeOrder.compareTo(maxNodeOrder) == 0; + + log.info("校团委最后节点判断: 节点名称={}, nodeOrder={}, 是否校团委={}, 是否3.X={}, 是否最后节点={}", + currentNodeName, currentNodeOrder, isSchoolCommitteeNode, isNodeOrder3X, isLastNode); + + return isLastNode; + } +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglAuditorServiceImpl.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglAuditorServiceImpl.java new file mode 100644 index 00000000..9fc78884 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglAuditorServiceImpl.java @@ -0,0 +1,266 @@ +package org.dromara.stgl.service.impl; + +import org.dromara.common.core.utils.MapstructUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.dromara.stgl.domain.bo.StglAuditorBo; +import org.dromara.stgl.domain.vo.StglAuditorVo; +import org.dromara.stgl.domain.StglAuditor; +import org.dromara.stgl.domain.StglUserWithDeptInfo; +import org.dromara.stgl.mapper.StglAuditorMapper; +import org.dromara.stgl.service.IStglAuditorService; +import org.dromara.system.service.ISysUserService; +import org.dromara.system.domain.vo.SysUserVo; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.common.core.exception.ServiceException; + +import java.util.List; +import java.util.Map; +import java.util.Collection; +import java.util.ArrayList; +import java.util.stream.Collectors; + +/** + * 社团管理审核人配置Service业务层处理 + * + * @author ruoyi + * @date 2024-01-01 + */ +@RequiredArgsConstructor +@Service +public class StglAuditorServiceImpl implements IStglAuditorService { + + private final StglAuditorMapper baseMapper; + private final ISysUserService sysUserService; + + /** + * 查询社团管理审核人配置 + */ + @Override + public StglAuditorVo queryById(Long id) { + StglAuditor entity = baseMapper.selectById(id); + if (entity == null) { + return null; + } + return MapstructUtils.convert(entity, StglAuditorVo.class); + } + + /** + * 查询社团管理审核人配置列表 + */ + @Override + public TableDataInfo queryPageList(StglAuditorBo bo, PageQuery pageQuery) { + // 直接使用查询条件构建器 + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page entityPage = baseMapper.selectPage(pageQuery.build(), lqw); + + // 转换为 VO 对象 + List voList = new ArrayList<>(); + for (StglAuditor entity : entityPage.getRecords()) { + StglAuditorVo vo = MapstructUtils.convert(entity, StglAuditorVo.class); + voList.add(vo); + } + + // 构建分页结果 + Page voPage = new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal()); + voPage.setRecords(voList); + + return TableDataInfo.build(voPage); + } + + /** + * 查询社团管理审核人配置列表 + */ + @Override + public List queryList(StglAuditorBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + List entityList = baseMapper.selectList(lqw); + + // 转换为 VO 对象 + List voList = new ArrayList<>(); + for (StglAuditor entity : entityList) { + StglAuditorVo vo = MapstructUtils.convert(entity, StglAuditorVo.class); + voList.add(vo); + } + + return voList; + } + + private LambdaQueryWrapper buildQueryWrapper(StglAuditorBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(bo.getUserId() != null, StglAuditor::getUserId, bo.getUserId()); + lqw.like(StringUtils.isNotBlank(bo.getUserName()), StglAuditor::getUserName, bo.getUserName()); + lqw.like(StringUtils.isNotBlank(bo.getNickName()), StglAuditor::getNickName, bo.getNickName()); + lqw.eq(bo.getDeptId() != null, StglAuditor::getDeptId, bo.getDeptId()); + lqw.eq(StringUtils.isNotBlank(bo.getStatus()), StglAuditor::getStatus, bo.getStatus()); + lqw.eq(bo.getConfigBy() != null, StglAuditor::getConfigBy, bo.getConfigBy()); + lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null, + StglAuditor::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime")); + lqw.orderByDesc(StglAuditor::getCreateTime); + return lqw; + } + + /** + * 新增社团管理审核人配置 + */ + @Override + public Boolean insertByBo(StglAuditorBo bo) { + StglAuditor add = MapstructUtils.convert(bo, StglAuditor.class); + validEntityBeforeSave(add); + + // 设置配置人信息 + Long currentUserId = LoginHelper.getUserId(); + add.setConfigBy(currentUserId); + + // 获取当前用户信息设置配置人姓名 + try { + SysUserVo currentUser = sysUserService.selectUserById(currentUserId); + if (currentUser != null) { + add.setConfigByName(currentUser.getUserName()); + } + } catch (Exception e) { + // 如果获取失败,使用默认值 + add.setConfigByName("系统管理员"); + } + + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改社团管理审核人配置 + */ + @Override + public Boolean updateByBo(StglAuditorBo bo) { + StglAuditor update = MapstructUtils.convert(bo, StglAuditor.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(StglAuditor entity) { + if (entity.getUserId() != null) { + // 检查用户是否已存在(新增时或修改用户ID时) + if (entity.getId() == null) { + // 新增时检查 + int count = baseMapper.checkUserExists(entity.getUserId()); + if (count > 0) { + throw new ServiceException("该用户已配置为审核人,不能重复添加"); + } + } + } + } + + /** + * 批量删除社团管理审核人配置 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids) { + return baseMapper.deleteBatchIds(ids) > 0; + } + + /** + * 批量添加审核人 + */ + @Override + public Boolean batchAddAuditors(List userIds, String remark) { + if (userIds == null || userIds.isEmpty()) { + throw new ServiceException("用户ID列表不能为空"); + } + + Long currentUserId = LoginHelper.getUserId(); + String currentUserName = "系统管理员"; + + // 获取当前用户信息 + try { + SysUserVo currentUser = sysUserService.selectUserById(currentUserId); + if (currentUser != null) { + currentUserName = currentUser.getUserName(); + } + } catch (Exception e) { + // 使用默认值 + } + + // 1. 获取完整的用户信息(包含部门信息) + List users = baseMapper.selectUsersWithDeptInfo(userIds); + if (users.isEmpty()) { + throw new ServiceException("没有找到有效的用户信息"); + } + + // 2. 过滤已存在的审核人 + List existingUserIds = baseMapper.selectList(Wrappers.lambdaQuery() + .in(StglAuditor::getUserId, userIds)) + .stream().map(StglAuditor::getUserId).collect(Collectors.toList()); + + // 3. 构建新增审核人列表 + List auditorList = new ArrayList<>(); + for (StglUserWithDeptInfo user : users) { + if (!existingUserIds.contains(user.getUserId())) { + StglAuditor auditor = new StglAuditor(); + auditor.setUserId(user.getUserId()); + auditor.setUserName(user.getUserName()); + auditor.setNickName(user.getNickName()); + auditor.setDeptId(user.getDeptId()); + auditor.setDeptName(user.getDeptName() != null ? user.getDeptName() : ""); + auditor.setPhoneNumber(user.getPhonenumber()); + auditor.setEmail(user.getEmail()); + auditor.setStatus("1"); // 默认正常状态 + auditor.setRemark(remark); + auditor.setConfigBy(currentUserId); + auditor.setConfigByName(currentUserName); + auditorList.add(auditor); + } + } + + // 4. 批量保存 + if (auditorList.isEmpty()) { + throw new ServiceException("所有用户都已存在,无需重复添加"); + } + + return baseMapper.insertBatch(auditorList); + } + + /** + * 查询我配置的审核人列表 + */ + @Override + public TableDataInfo queryMyConfigList(StglAuditorBo bo, PageQuery pageQuery) { + bo.setConfigBy(LoginHelper.getUserId()); + return queryPageList(bo, pageQuery); + } + + /** + * 批量启用审核人 + */ + @Override + public Boolean enableAuditors(List ids) { + if (ids == null || ids.isEmpty()) { + return false; + } + return baseMapper.enableAuditorsByIds(ids) > 0; + } + + /** + * 批量停用审核人 + */ + @Override + public Boolean disableAuditors(List ids) { + if (ids == null || ids.isEmpty()) { + return false; + } + return baseMapper.disableAuditorsByIds(ids) > 0; + } + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubAdvisorHistoryServiceImpl.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubAdvisorHistoryServiceImpl.java new file mode 100644 index 00000000..9a3fd4ba --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubAdvisorHistoryServiceImpl.java @@ -0,0 +1,205 @@ +package org.dromara.stgl.service.impl; + +import org.dromara.common.core.utils.MapstructUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.dromara.stgl.domain.bo.StglClubAdvisorHistoryBo; +import org.dromara.stgl.domain.vo.StglClubAdvisorHistoryVo; +import org.dromara.stgl.domain.StglClubAdvisorHistory; +import org.dromara.stgl.mapper.StglClubAdvisorHistoryMapper; +import org.dromara.stgl.service.IStglClubAdvisorHistoryService; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 社团指导老师任职历史Service业务层处理 + * + * @author Cursor AI + * @date 2025-01-03 + */ +@RequiredArgsConstructor +@Service +public class StglClubAdvisorHistoryServiceImpl implements IStglClubAdvisorHistoryService { + + private final StglClubAdvisorHistoryMapper baseMapper; + + /** + * 查询社团指导老师任职历史 + */ + @Override + public StglClubAdvisorHistoryVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询社团指导老师任职历史列表 + */ + @Override + public TableDataInfo queryPageList(StglClubAdvisorHistoryBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询社团指导老师任职历史列表 + */ + @Override + public List queryList(StglClubAdvisorHistoryBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(StglClubAdvisorHistoryBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(bo.getClubId() != null, StglClubAdvisorHistory::getClubId, bo.getClubId()); + lqw.like(StringUtils.isNotBlank(bo.getAdvisorName()), StglClubAdvisorHistory::getAdvisorName, bo.getAdvisorName()); + lqw.like(StringUtils.isNotBlank(bo.getAdvisorDept()), StglClubAdvisorHistory::getAdvisorDept, bo.getAdvisorDept()); + lqw.eq(StringUtils.isNotBlank(bo.getAdvisorPhone()), StglClubAdvisorHistory::getAdvisorPhone, bo.getAdvisorPhone()); + lqw.like(StringUtils.isNotBlank(bo.getAdvisorEmail()), StglClubAdvisorHistory::getAdvisorEmail, bo.getAdvisorEmail()); + lqw.eq(bo.getAdvisorUserId() != null, StglClubAdvisorHistory::getAdvisorUserId, bo.getAdvisorUserId()); + lqw.eq(bo.getStatus() != null, StglClubAdvisorHistory::getStatus, bo.getStatus()); + lqw.eq(bo.getStartTime() != null, StglClubAdvisorHistory::getStartTime, bo.getStartTime()); + lqw.eq(bo.getEndTime() != null, StglClubAdvisorHistory::getEndTime, bo.getEndTime()); + return lqw; + } + + /** + * 新增社团指导老师任职历史 + */ + @Override + public Boolean insertByBo(StglClubAdvisorHistoryBo bo) { + StglClubAdvisorHistory add = MapstructUtils.convert(bo, StglClubAdvisorHistory.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改社团指导老师任职历史 + */ + @Override + public Boolean updateByBo(StglClubAdvisorHistoryBo bo) { + StglClubAdvisorHistory update = MapstructUtils.convert(bo, StglClubAdvisorHistory.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(StglClubAdvisorHistory entity){ + // TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除社团指导老师任职历史 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + // TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } + + /** + * 查询指定社团的当前在任指导老师 + */ + @Override + public List getCurrentAdvisorsByClubId(Long clubId) { + return baseMapper.selectCurrentAdvisorsByClubId(clubId); + } + + /** + * 查询指定社团的历史指导老师(已离任) + */ + @Override + public List getHistoryAdvisorsByClubId(Long clubId) { + return baseMapper.selectHistoryAdvisorsByClubId(clubId); + } + + /** + * 查询指定社团的所有指导老师(包含当前和历史) + */ + @Override + public List getAllAdvisorsByClubId(Long clubId) { + return baseMapper.selectAllAdvisorsByClubId(clubId); + } + + /** + * 更新社团指导老师历史记录 + * 对比新旧指导老师列表,处理新增和离任 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void updateClubAdvisors(Long clubId, List newAdvisors) { + updateClubAdvisors(clubId, newAdvisors, new Date()); + } + + /** + * 更新社团指导老师历史记录(指定开始时间) + * 对比新旧指导老师列表,处理新增和离任 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void updateClubAdvisors(Long clubId, List newAdvisors, Date startTime) { + Date currentDate = new Date(); + + // 1. 获取当前指导老师列表 + List currentRecords = getCurrentAdvisorsByClubId(clubId); + + // 2. 建立姓名映射 + Set currentNames = currentRecords.stream() + .map(StglClubAdvisorHistoryVo::getAdvisorName) + .collect(Collectors.toSet()); + + Set newNames = newAdvisors.stream() + .map(AdvisorInfo::getName) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + // 3. 处理被移除的指导老师 + List removedNames = currentNames.stream() + .filter(name -> !newNames.contains(name)) + .collect(Collectors.toList()); + + if (!removedNames.isEmpty()) { + baseMapper.batchSetHistoryStatus(clubId, removedNames, currentDate); + } + + // 4. 处理新增的指导老师 + for (AdvisorInfo newAdvisor : newAdvisors) { + if (StringUtils.isNotBlank(newAdvisor.getName()) && + !currentNames.contains(newAdvisor.getName())) { + + StglClubAdvisorHistory newRecord = new StglClubAdvisorHistory(); + newRecord.setClubId(clubId); + newRecord.setAdvisorName(newAdvisor.getName()); + newRecord.setAdvisorDept(newAdvisor.getDept()); + newRecord.setAdvisorPhone(newAdvisor.getPhone()); + newRecord.setAdvisorEmail(newAdvisor.getEmail()); + newRecord.setAdvisorUserId(newAdvisor.getUserId()); + newRecord.setStatus(1); + // 使用传入的开始时间,而不是当前时间 + newRecord.setStartTime(startTime != null ? startTime : currentDate); + newRecord.setEndTime(null); + + baseMapper.insert(newRecord); + } + } + } + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubAnnualReviewServiceImpl.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubAnnualReviewServiceImpl.java new file mode 100644 index 00000000..b8c77147 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubAnnualReviewServiceImpl.java @@ -0,0 +1,740 @@ +package org.dromara.stgl.service.impl; + +import org.dromara.common.core.utils.MapstructUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.satoken.utils.LoginHelper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import cn.hutool.core.bean.BeanUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.dromara.stgl.domain.bo.StglClubAnnualReviewBo; +import org.dromara.stgl.domain.vo.StglClubAnnualReviewVo; +import org.dromara.stgl.domain.StglClubAnnualReview; +import org.dromara.stgl.domain.StglClubAnnualReviewMember; +import org.dromara.stgl.domain.StglClubAnnualReviewActivity; +import org.dromara.stgl.mapper.StglClubAnnualReviewMapper; +import org.dromara.stgl.mapper.StglClubAnnualReviewMemberMapper; +import org.dromara.stgl.mapper.StglClubAnnualReviewActivityMapper; +import org.dromara.stgl.service.IStglClubAnnualReviewService; +import org.dromara.stgl.service.IStglActivityService; +import org.dromara.stgl.service.IStglClubService; +import org.dromara.stgl.service.IStglAnnualReviewAuditService; +import org.dromara.stgl.domain.bo.StglActivityBo; +import org.dromara.stgl.domain.vo.StglClubVo; +import org.dromara.stgl.domain.StglClub; + +import java.util.List; +import java.util.Map; +import java.util.Collection; +import java.util.Set; +import java.util.HashSet; +import java.util.ArrayList; +import java.util.stream.Collectors; +import java.util.Date; +import java.text.SimpleDateFormat; +import java.text.ParseException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * 社团年审记录Service业务层处理 + * + * @author Cursor AI + * @date 2025-01-15 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class StglClubAnnualReviewServiceImpl implements IStglClubAnnualReviewService { + + private final StglClubAnnualReviewMapper baseMapper; + private final StglClubAnnualReviewMemberMapper memberMapper; + private final StglClubAnnualReviewActivityMapper activityMapper; + private final IStglActivityService stglActivityService; + private final IStglClubService stglClubService; + private final IStglAnnualReviewAuditService auditService; + private final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * 查询社团年审记录 + */ + @Override + public StglClubAnnualReviewVo queryById(Long id){ + StglClubAnnualReviewVo vo = baseMapper.selectVoById(id); + if (vo == null) { + log.warn("未找到年审记录,ID: {}", id); + return null; + } + + try { + // 查询关联的成员数据 + List members = memberMapper.selectMembersByAnnualReviewId(id); + + // 查询关联的活动数据 + List activities = activityMapper.selectActivitiesByAnnualReviewId(id); + + // 将成员和活动数据转为JSON格式返回给前端 + if (!members.isEmpty()) { + // 转换成员数据格式,匹配前端期望的字段名 + List> membersList = new ArrayList<>(); + for (StglClubAnnualReviewMember member : members) { + Map memberMap = new HashMap<>(); + memberMap.put("name", member.getMemberName()); + memberMap.put("className", member.getClassName()); + memberMap.put("role", member.getRole()); + memberMap.put("phone", member.getContactInfo()); + membersList.add(memberMap); + } + vo.setMembersJson(objectMapper.writeValueAsString(membersList)); + } else { + vo.setMembersJson("[]"); // 设置空数组而不是null + } + + if (!activities.isEmpty()) { + // 转换活动数据格式,匹配前端期望的字段名 + List> activitiesList = new ArrayList<>(); + for (StglClubAnnualReviewActivity activity : activities) { + Map activityMap = new HashMap<>(); + activityMap.put("activityName", activity.getActivityName()); + activityMap.put("location", activity.getActivityLocation()); + activityMap.put("participantCount", activity.getParticipantCount()); + + // 格式化活动时间显示 - 优先使用新的开始/结束时间字段 + if (activity.getActivityStartTime() != null) { + activityMap.put("activityStartTime", activity.getActivityStartTime().toString()); + } + if (activity.getActivityEndTime() != null) { + activityMap.put("activityEndTime", activity.getActivityEndTime().toString()); + } + // 保持向后兼容 + if (activity.getActivityTime() != null) { + activityMap.put("activityDate", activity.getActivityTime().toString()); + } else { + activityMap.put("activityDate", ""); + } + + activitiesList.add(activityMap); + } + vo.setActivitiesJson(objectMapper.writeValueAsString(activitiesList)); + } else { + vo.setActivitiesJson("[]"); // 设置空数组而不是null + } + + } catch (Exception e) { + log.error("处理年审记录关联数据失败,ID: {}", id, e); + // 确保JSON字段不为null + if (vo.getMembersJson() == null) { + vo.setMembersJson("[]"); + } + if (vo.getActivitiesJson() == null) { + vo.setActivitiesJson("[]"); + } + } + + return vo; + } + + /** + * 查询社团年审记录列表 + */ + @Override + public TableDataInfo queryPageList(StglClubAnnualReviewBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询社团年审记录列表 + */ + @Override + public List queryList(StglClubAnnualReviewBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(StglClubAnnualReviewBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(bo.getClubId() != null, StglClubAnnualReview::getClubId, bo.getClubId()); + lqw.eq(bo.getReviewYear() != null, StglClubAnnualReview::getReviewYear, bo.getReviewYear()); + lqw.between(params.get("beginReviewStartDate") != null && params.get("endReviewStartDate") != null, + StglClubAnnualReview::getReviewStartDate, params.get("beginReviewStartDate"), params.get("endReviewStartDate")); + lqw.between(params.get("beginReviewEndDate") != null && params.get("endReviewEndDate") != null, + StglClubAnnualReview::getReviewEndDate, params.get("beginReviewEndDate"), params.get("endReviewEndDate")); + lqw.like(StringUtils.isNotBlank(bo.getClubName()), StglClubAnnualReview::getClubName, bo.getClubName()); + lqw.eq(StringUtils.isNotBlank(bo.getClubType()), StglClubAnnualReview::getClubType, bo.getClubType()); + lqw.eq(StringUtils.isNotBlank(bo.getStatus()), StglClubAnnualReview::getStatus, bo.getStatus()); + lqw.orderByDesc(StglClubAnnualReview::getCreateTime); + return lqw; + } + + /** + * 新增社团年审记录 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean insertByBo(StglClubAnnualReviewBo bo) { + // 检查是否可以创建年审记录 + if (!checkCanCreateReview(bo.getClubId(), bo.getReviewYear())) { + throw new RuntimeException("该社团在该年度已存在年审记录"); + } + + StglClubAnnualReview add = MapstructUtils.convert(bo, StglClubAnnualReview.class); + // 设置默认状态为草稿 + if (StringUtils.isBlank(add.getStatus())) { + add.setStatus("0"); + } + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + + // 保存成员数据 + saveMembers(add.getId(), bo.getMembersJson()); + + // 保存活动数据 + saveActivities(add.getId(), bo.getActivitiesJson()); + } + return flag; + } + + /** + * 修改社团年审记录 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean updateByBo(StglClubAnnualReviewBo bo) { + StglClubAnnualReview update = MapstructUtils.convert(bo, StglClubAnnualReview.class); + validEntityBeforeSave(update); + boolean flag = baseMapper.updateById(update) > 0; + + if (flag) { + // 保存成员数据 + saveMembers(bo.getId(), bo.getMembersJson()); + + // 保存活动数据 + saveActivities(bo.getId(), bo.getActivitiesJson()); + } + + return flag; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(StglClubAnnualReview entity){ + // 可以在此处进行额外的业务校验 + if (entity.getReviewStartDate() != null && entity.getReviewEndDate() != null) { + if (entity.getReviewStartDate().after(entity.getReviewEndDate())) { + throw new RuntimeException("年审开始日期不能晚于结束日期"); + } + } + + // 自动填充社长信息(如果社长姓名为空且有社团ID) + if (entity.getClubId() != null && StringUtils.isBlank(entity.getPresidentName())) { + StglClubVo clubVo = stglClubService.queryById(entity.getClubId()); + if (clubVo != null) { + entity.setPresidentName(clubVo.getPresidentName()); + } + } + } + + /** + * 保存成员数据 - 优化版本:精确删除而非全量重建 + */ + private void saveMembers(Long annualReviewId, String membersJson) { + if (StringUtils.isBlank(membersJson)) { + return; + } + try { + // 1. 获取现有成员数据 + List existingMembers = + memberMapper.selectMembersByAnnualReviewId(annualReviewId); + + // 2. 解析前端提交的数据 + List> newMembersList = objectMapper.readValue(membersJson, + new TypeReference>>() {}); + + // 3. 构建要保留的成员标识集合 + Set keepMemberKeys = newMembersList.stream() + .map(this::generateMemberKey) + .collect(Collectors.toSet()); + + // 4. 只逻辑删除不在保留列表中的成员 + for (StglClubAnnualReviewMember existingMember : existingMembers) { + String existingKey = generateMemberKey(existingMember); + if (!keepMemberKeys.contains(existingKey)) { + // 逻辑删除该成员 + memberMapper.deleteById(existingMember.getId()); + log.info("删除年审成员记录: annualReviewId={}, memberName={}, className={}", + annualReviewId, existingMember.getMemberName(), existingMember.getClassName()); + } + } + + // 5. 新增不存在的成员(如果有的话 - 虽然当前页面只支持删除) + Set existingMemberKeys = existingMembers.stream() + .map(this::generateMemberKey) + .collect(Collectors.toSet()); + + for (Map memberData : newMembersList) { + String memberKey = generateMemberKey(memberData); + if (!existingMemberKeys.contains(memberKey)) { + // 新增成员 + StglClubAnnualReviewMember member = new StglClubAnnualReviewMember(); + member.setAnnualReviewId(annualReviewId); + member.setMemberName((String) memberData.get("name")); + member.setClassName((String) memberData.get("className")); + member.setRole((String) memberData.get("role")); + member.setContactInfo((String) memberData.get("phone")); + + // 注意:userId字段已从数据库表中删除,不再设置 + + memberMapper.insert(member); + log.info("新增年审成员记录: annualReviewId={}, memberName={}, className={}", + annualReviewId, member.getMemberName(), member.getClassName()); + } + } + + } catch (Exception e) { + log.error("保存成员数据失败: annualReviewId={}", annualReviewId, e); + throw new RuntimeException("保存成员数据失败: " + e.getMessage()); + } + } + + /** + * 生成成员唯一标识 - 优先使用userId,其次使用姓名+班级组合 + */ + private String generateMemberKey(Map memberData) { + // 如果有user_id,优先使用 + Object userId = memberData.get("userId"); + if (userId != null && StringUtils.isNotBlank(userId.toString())) { + return "user_" + userId.toString(); + } + // 否则使用姓名+班级的组合 + String name = (String) memberData.get("name"); + String className = (String) memberData.get("className"); + return (name != null ? name : "") + "_" + (className != null ? className : ""); + } + + /** + * 生成成员唯一标识 - 实体对象版本 + */ + private String generateMemberKey(StglClubAnnualReviewMember member) { + // userId字段已删除,直接使用姓名+班级的组合 + String name = member.getMemberName(); + String className = member.getClassName(); + return (name != null ? name : "") + "_" + (className != null ? className : ""); + } + + /** + * 保存活动数据 - 基于ID的精确匹配版本 + */ + private void saveActivities(Long annualReviewId, String activitiesJson) { + if (StringUtils.isBlank(activitiesJson)) { + return; + } + try { + // 1. 获取现有活动数据 + List existingActivities = + activityMapper.selectActivitiesByAnnualReviewId(annualReviewId); + + // 2. 解析前端提交的数据 + List> newActivitiesList = objectMapper.readValue(activitiesJson, + new TypeReference>>() {}); + + // 3. 构建要保留的活动ID集合(只包含有ID的活动,即数据库中已存在的) + Set keepActivityIds = new HashSet<>(); + List> newActivities = new ArrayList<>(); + + for (Map activity : newActivitiesList) { + Object idObj = activity.get("id"); + if (idObj != null) { + // 现有活动,添加到保留ID集合 + Long activityId = Long.valueOf(idObj.toString()); + keepActivityIds.add(activityId); + } else { + // 新活动,添加到新增列表 + newActivities.add(activity); + } + } + + // 4. 删除不在保留列表中的活动 + for (StglClubAnnualReviewActivity existingActivity : existingActivities) { + if (!keepActivityIds.contains(existingActivity.getId())) { + // 逻辑删除该活动 + activityMapper.deleteById(existingActivity.getId()); + } + } + + // 5. 新增活动 + for (Map activityData : newActivities) { + StglClubAnnualReviewActivity activity = new StglClubAnnualReviewActivity(); + activity.setAnnualReviewId(annualReviewId); + activity.setActivityName((String) activityData.get("activityName")); + activity.setActivityLocation((String) activityData.get("location")); + activity.setParticipantCount(activityData.get("participantCount") != null ? + Integer.parseInt(activityData.get("participantCount").toString()) : 0); + + // 处理时间字段 + parseAndSetActivityTimes(activity, activityData); + + activityMapper.insert(activity); + } + + } catch (Exception e) { + log.error("保存活动数据失败: annualReviewId={}", annualReviewId, e); + throw new RuntimeException("保存活动数据失败: " + e.getMessage()); + } + } + + /** + * 解析并设置活动时间 + */ + private void parseAndSetActivityTimes(StglClubAnnualReviewActivity activity, Map activityData) { + // 处理活动日期 (保持兼容性) + if (activityData.get("activityDate") != null) { + String dateStr = activityData.get("activityDate").toString(); + if (StringUtils.isNotBlank(dateStr)) { + try { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + Date activityDate = sdf.parse(dateStr); + activity.setActivityTime(activityDate); + } catch (ParseException e) { + log.warn("解析活动时间失败: {}", dateStr, e); + activity.setActivityTime(null); + } + } + } + + // 处理活动开始时间 + if (activityData.get("activityStartTime") != null) { + String startTimeStr = activityData.get("activityStartTime").toString(); + if (StringUtils.isNotBlank(startTimeStr)) { + try { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + Date startTime = sdf.parse(startTimeStr); + activity.setActivityStartTime(startTime); + } catch (ParseException e) { + try { + SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + Date startTime = sdf2.parse(startTimeStr); + activity.setActivityStartTime(startTime); + } catch (ParseException e2) { + log.warn("解析活动开始时间失败: {}", startTimeStr, e2); + activity.setActivityStartTime(null); + } + } + } + } + + // 处理活动结束时间 + if (activityData.get("activityEndTime") != null) { + String endTimeStr = activityData.get("activityEndTime").toString(); + if (StringUtils.isNotBlank(endTimeStr)) { + try { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + Date endTime = sdf.parse(endTimeStr); + activity.setActivityEndTime(endTime); + } catch (ParseException e) { + try { + SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + Date endTime = sdf2.parse(endTimeStr); + activity.setActivityEndTime(endTime); + } catch (ParseException e2) { + log.warn("解析活动结束时间失败: {}", endTimeStr, e2); + activity.setActivityEndTime(null); + } + } + } + } + } + + /** + * 生成活动唯一标识 - 优先使用activityId,其次使用活动名称+时间+地点组合 + */ + private String generateActivityKey(Map activityData) { + // 如果有activity_id,优先使用 + Object activityId = activityData.get("activityId"); + if (activityId != null && StringUtils.isNotBlank(activityId.toString())) { + return "activity_" + activityId.toString(); + } + // 否则使用活动名称+时间+地点的组合 + String name = (String) activityData.get("activityName"); + String location = (String) activityData.get("location"); + String time = ""; + + // 优先使用开始时间,其次使用活动日期 + if (activityData.get("activityStartTime") != null) { + time = activityData.get("activityStartTime").toString(); + } else if (activityData.get("activityDate") != null) { + time = activityData.get("activityDate").toString(); + } + + return (name != null ? name : "") + "_" + time + "_" + (location != null ? location : ""); + } + + /** + * 生成活动唯一标识 - 实体对象版本 + */ + private String generateActivityKey(StglClubAnnualReviewActivity activity) { + // activityId字段已删除,直接使用活动名称+时间+地点的组合 + String name = activity.getActivityName(); + String location = activity.getActivityLocation(); + String time = ""; + + // 优先使用开始时间,其次使用活动时间 + if (activity.getActivityStartTime() != null) { + time = activity.getActivityStartTime().toString(); + } else if (activity.getActivityTime() != null) { + time = activity.getActivityTime().toString(); + } + + return (name != null ? name : "") + "_" + time + "_" + (location != null ? location : ""); + } + + /** + * 批量删除社团年审记录 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + // 做一些业务上的校验,判断是否需要校验 + for (Long id : ids) { + StglClubAnnualReviewVo vo = queryById(id); + if (vo != null && !"0".equals(vo.getStatus())) { + throw new RuntimeException("只能删除草稿状态的年审记录"); + } + } + } + + // 删除关联的成员和活动数据 + for (Long id : ids) { + memberMapper.deleteMembersByAnnualReviewId(id); + activityMapper.deleteActivitiesByAnnualReviewId(id); + } + + return baseMapper.deleteBatchIds(ids) > 0; + } + + /** + * 提交年审记录 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean submitReview(Long id) { + StglClubAnnualReview entity = baseMapper.selectById(id); + if (entity == null) { + throw new RuntimeException("年审记录不存在"); + } + if (!"0".equals(entity.getStatus())) { + throw new RuntimeException("只能提交草稿状态的年审记录"); + } + + // 获取社团信息,提取指导老师信息 + StglClubVo club = stglClubService.queryById(entity.getClubId()); + if (club == null) { + throw new RuntimeException("社团信息不存在"); + } + + // 提取指导老师信息 + List advisorIds = new ArrayList<>(); + List advisorNames = new ArrayList<>(); + + try { + // 优先从JSON字段读取指导老师数据 + if (StringUtils.isNotBlank(club.getAdvisorsJson())) { + ObjectMapper mapper = new ObjectMapper(); + List> advisors = mapper.readValue(club.getAdvisorsJson(), + new com.fasterxml.jackson.core.type.TypeReference>>() {}); + + for (Map advisor : advisors) { + // 提取用户ID和姓名 + Object userIdObj = null; + String advisorName = null; + + // 尝试直接从advisor对象获取userId和name(兼容旧格式) + if (advisor.containsKey("userId")) { + userIdObj = advisor.get("userId"); + advisorName = (String) advisor.get("name"); + } + // 尝试从teacher子对象获取userId和name(新格式) + else if (advisor.containsKey("teacher")) { + Object teacherObj = advisor.get("teacher"); + if (teacherObj instanceof Map) { + @SuppressWarnings("unchecked") + Map teacher = (Map) teacherObj; + userIdObj = teacher.get("userId"); + advisorName = (String) teacher.get("nickName"); + if (StringUtils.isBlank(advisorName)) { + advisorName = (String) teacher.get("userName"); + } + } + } + + if (userIdObj != null && StringUtils.isNotBlank(advisorName)) { + Long userId = Long.valueOf(userIdObj.toString()); + advisorIds.add(userId); + advisorNames.add(advisorName); + } + } + } + + // 如果没有JSON数据,尝试从单一字段获取 + if (advisorIds.isEmpty() && StringUtils.isNotBlank(club.getAdvisorName())) { + // 这种情况下无法获取用户ID,暂时跳过审批流程 + log.warn("社团 {} 的指导老师信息不完整,无法创建审批记录", club.getClubName()); + } + + } catch (Exception e) { + log.error("解析指导老师信息失败", e); + throw new RuntimeException("解析指导老师信息失败"); + } + + // 更新年审记录状态 + entity.setStatus("1"); + entity.setReviewTime(new Date()); + boolean updateResult = baseMapper.updateById(entity) > 0; + + // 如果有指导老师信息,创建审批记录 + if (!advisorIds.isEmpty() && updateResult) { + try { + auditService.createAdvisorApprovalRecords(id, advisorIds, advisorNames); + } catch (Exception e) { + log.error("创建审批记录失败", e); + throw new RuntimeException("创建审批记录失败"); + } + } + + return updateResult; + } + + /** + * 撤回年审记录 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean withdrawReview(Long id) { + StglClubAnnualReview entity = baseMapper.selectById(id); + if (entity == null) { + throw new RuntimeException("年审记录不存在"); + } + if (!"1".equals(entity.getStatus())) { + throw new RuntimeException("只能撤回已提交状态的年审记录"); + } + + entity.setStatus("0"); + return baseMapper.updateById(entity) > 0; + } + + /** + * 审核年审记录 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean auditReview(Long id, String auditResult, String auditOpinion, String auditType) { + StglClubAnnualReview entity = baseMapper.selectById(id); + if (entity == null) { + throw new RuntimeException("年审记录不存在"); + } + + // 根据审核类型设置意见 + switch (auditType) { + case "advisor": + entity.setAdvisorOpinion(auditOpinion); + break; + case "college": + entity.setCollegeOpinion(auditOpinion); + break; + case "youth_league": + entity.setYouthLeagueOpinion(auditOpinion); + break; + default: + throw new RuntimeException("无效的审核类型"); + } + + // 设置审核结果状态 + entity.setStatus("3".equals(auditResult) ? "3" : "4"); + + return baseMapper.updateById(entity) > 0; + } + + /** + * 获取当前用户的年审记录列表 + */ + @Override + public TableDataInfo queryMyPageList(StglClubAnnualReviewBo bo, PageQuery pageQuery) { + // 这里需要根据实际业务逻辑,通过用户ID查询其所属社团的年审记录 + // 暂时使用相同的查询逻辑,实际应该加上用户权限过滤 + return queryPageList(bo, pageQuery); + } + + /** + * 检查是否可以创建年审记录 + */ + @Override + public Boolean checkCanCreateReview(Long clubId, Integer reviewYear) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(StglClubAnnualReview::getClubId, clubId); + lqw.eq(StglClubAnnualReview::getReviewYear, reviewYear); + return baseMapper.selectCount(lqw) == 0; + } + + /** + * 根据社团ID和年审年度获取该年度的活动数据 + * @param clubId 社团ID + * @param reviewYear 年审年度 (例如:2024) + * @return 活动列表 - 只包含已通过、进行中、已结束的活动 + */ + @Override + public List getClubActivitiesByReviewYear(Long clubId, Integer reviewYear) { + // 计算年审年度的时间范围:年审年度的1月1日到12月31日 + LocalDate startDate = LocalDate.of(reviewYear, 1, 1); + LocalDate endDate = LocalDate.of(reviewYear, 12, 31); + + // 构建查询条件 + StglActivityBo bo = new StglActivityBo(); + bo.setRelatedClubId(clubId); // 设置社团ID + bo.setRelatedType(1); // 关联类型为社团(1表示社团,2表示志愿团队) + + // 设置时间范围查询条件(查询活动开始时间在该年度内的活动) + bo.setStartTime(java.sql.Date.valueOf(startDate)); + bo.setEndTime(java.sql.Date.valueOf(endDate)); + + // 查询活动列表 + List allActivities = stglActivityService.queryList(bo); + + // 过滤活动状态:只保留已通过(3)、进行中(4)、已结束(5)的活动 + return allActivities.stream() + .filter(activity -> { + String status = activity.getActivityStatus(); + return "3".equals(status) || "4".equals(status) || "5".equals(status); + }) + .collect(java.util.stream.Collectors.toList()); + } + + /** + * 根据年审记录ID获取对应的活动数据 + */ + @Override + public List getAnnualReviewActivities(Long annualReviewId) { + LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); + lqw.eq(StglClubAnnualReviewActivity::getAnnualReviewId, annualReviewId); + lqw.orderByAsc(StglClubAnnualReviewActivity::getActivityStartTime); + + List activities = activityMapper.selectList(lqw); + return BeanUtil.copyToList(activities, org.dromara.stgl.domain.vo.StglClubAnnualReviewActivityVo.class); + } + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubNotificationScheduler.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubNotificationScheduler.java new file mode 100644 index 00000000..363eda2f --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubNotificationScheduler.java @@ -0,0 +1,263 @@ +package org.dromara.stgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.stgl.domain.StglClub; +import org.dromara.stgl.domain.vo.ClubActivityPermissionResult; +import org.dromara.stgl.mapper.StglClubMapper; +import org.dromara.stgl.service.IStglClubPermissionService; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * 社团通知定时任务 + * 仅用于发送年审提醒通知,不管理权限状态 + * + * @author Cursor AI + * @date 2025-01-15 + */ +@Slf4j +@RequiredArgsConstructor +@Component +@ConditionalOnProperty(name = "stgl.club.notification.enabled", havingValue = "true", matchIfMissing = true) +public class StglClubNotificationScheduler { + + private final StglClubMapper clubMapper; + private final IStglClubPermissionService clubPermissionService; + + private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + /** + * 每天早上8点发送年审相关通知 + * 通过实时权限检查确定通知类型,不存储状态 + */ + @Scheduled(cron = "0 0 8 * * ?") + public void sendDailyAnnualReviewReminders() { + log.info("开始执行社团年审通知任务"); + + try { + Date today = new Date(); + + // 获取所有正常状态的社团 + List allClubs = getAllActiveClubs(); + log.info("获取到活跃社团数量: {}", allClubs.size()); + + int todayLimitedCount = 0; + int urgentReminderCount = 0; + int permanentlyLimitedCount = 0; + + for (StglClub club : allClubs) { + try { + // 实时检查权限状态 + ClubActivityPermissionResult permission = + clubPermissionService.checkClubActivityPermission(club.getId()); + + switch (permission.getStatus()) { + case "TEMPORARILY_LIMITED": + handleTemporarilyLimitedClub(club, permission); + if (isFirstDayOfRestriction(club, today)) { + todayLimitedCount++; + } + if (isUrgentReminder(permission)) { + urgentReminderCount++; + } + break; + + case "PERMANENTLY_LIMITED": + if (isFirstDayOfPermanentRestriction(club, today)) { + handlePermanentlyLimitedClub(club); + permanentlyLimitedCount++; + } + break; + + case "NORMAL": + // 正常状态无需通知 + break; + + default: + log.warn("未知的权限状态: {}, 社团ID: {}", permission.getStatus(), club.getId()); + } + } catch (Exception e) { + log.error("处理社团通知失败,社团ID: {}", club.getId(), e); + } + } + + log.info("社团年审通知任务完成 - 今日开始限制: {}, 紧急提醒: {}, 永久限制: {}", + todayLimitedCount, urgentReminderCount, permanentlyLimitedCount); + + } catch (Exception e) { + log.error("执行社团年审通知任务失败", e); + } + } + + /** + * 处理临时限制状态的社团 + */ + private void handleTemporarilyLimitedClub(StglClub club, ClubActivityPermissionResult permission) { + if (permission.getRemainingDays() != null) { + if (permission.getRemainingDays() <= 3) { + // 剩余3天内,发送紧急提醒 + sendUrgentAnnualReviewReminder(club, permission); + } else if (permission.getRemainingDays() >= 12) { + // 刚开始限制(剩余12天以上),发送开始通知 + sendAnnualReviewStartNotification(club, permission); + } + } + } + + /** + * 处理永久限制状态的社团 + */ + private void handlePermanentlyLimitedClub(StglClub club) { + sendPermanentRestrictionNotification(club); + } + + /** + * 发送年审开始通知 + */ + private void sendAnnualReviewStartNotification(StglClub club, ClubActivityPermissionResult permission) { + String message = String.format( + "【年审提醒】尊敬的%s社团,您的社团从今天开始进入年审期,活动发起功能已限制。" + + "请在%d天内完成年审提交,截止时间:%s。" + + "提醒:超过期限将永久限制活动发起功能。", + club.getClubName(), + permission.getRemainingDays(), + dateFormat.format(permission.getDeadlineTime()) + ); + + log.info("发送年审开始通知 - 社团: {}, 剩余天数: {}", club.getClubName(), permission.getRemainingDays()); + sendNotificationToClub(club, "年审开始通知", message); + } + + /** + * 发送紧急年审提醒 + */ + private void sendUrgentAnnualReviewReminder(StglClub club, ClubActivityPermissionResult permission) { + String message = String.format( + "【紧急提醒】%s社团,您的年审即将到期!" + + "仅剩%d天%d小时,截止时间:%s。" + + "请立即提交年审,否则将永久限制活动发起功能!", + club.getClubName(), + permission.getRemainingDays(), + permission.getRemainingHours() % 24, + dateFormat.format(permission.getDeadlineTime()) + ); + + log.warn("发送紧急年审提醒 - 社团: {}, 剩余: {}天{}小时", + club.getClubName(), permission.getRemainingDays(), permission.getRemainingHours() % 24); + sendNotificationToClub(club, "紧急年审提醒", message); + } + + /** + * 发送永久限制通知 + */ + private void sendPermanentRestrictionNotification(StglClub club) { + String message = String.format( + "【权限限制】%s社团,很遗憾通知您,由于超过年审提交期限," + + "您的社团活动发起功能已被永久限制。即使现在补交年审也无法恢复权限。" + + "如有疑问,请联系管理员。", + club.getClubName() + ); + + log.warn("发送永久限制通知 - 社团: {}", club.getClubName()); + sendNotificationToClub(club, "活动权限永久限制", message); + } + + /** + * 发送通知到社团(实际发送逻辑) + */ + private void sendNotificationToClub(StglClub club, String title, String message) { + try { + // TODO: 集成实际的通知系统 + // 可以是站内信、邮件、短信等 + log.info("通知发送 - 社团: {}, 标题: {}, 内容: {}", club.getClubName(), title, message); + + // 示例:记录通知日志 + recordNotificationLog(club.getId(), title, message); + } catch (Exception e) { + log.error("发送通知失败 - 社团: {}, 标题: {}", club.getClubName(), title, e); + } + } + + /** + * 记录通知日志 + */ + private void recordNotificationLog(Long clubId, String title, String message) { + // TODO: 将通知记录保存到数据库,用于审计和跟踪 + log.debug("记录通知日志 - 社团ID: {}, 标题: {}", clubId, title); + } + + /** + * 获取所有活跃的社团 + */ + private List getAllActiveClubs() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(StglClub::getStatus, 1) // 1-正常状态 + .isNotNull(StglClub::getEstablishDate); // 必须有成立日期 + return clubMapper.selectList(wrapper); + } + + /** + * 判断是否是限制的第一天 + */ + private boolean isFirstDayOfRestriction(StglClub club, Date today) { + try { + ClubActivityPermissionResult permission = + clubPermissionService.checkClubActivityPermission(club.getId()); + + if (!"TEMPORARILY_LIMITED".equals(permission.getStatus())) { + return false; + } + + // 如果剩余天数接近15天,说明是刚开始限制 + return permission.getRemainingDays() != null && permission.getRemainingDays() >= 14; + } catch (Exception e) { + log.error("判断限制第一天失败,社团ID: {}", club.getId(), e); + return false; + } + } + + /** + * 判断是否需要紧急提醒 + */ + private boolean isUrgentReminder(ClubActivityPermissionResult permission) { + return permission.getRemainingDays() != null && permission.getRemainingDays() <= 3; + } + + /** + * 判断是否是永久限制的第一天 + */ + private boolean isFirstDayOfPermanentRestriction(StglClub club, Date today) { + try { + // 检查昨天的状态是否还是临时限制 + Date yesterday = new Date(today.getTime() - TimeUnit.DAYS.toMillis(1)); + + // 通过计算年审截止时间来判断 + // 如果今天刚好是截止后的第一天,就发送通知 + Date establishDate = club.getEstablishDate(); + if (establishDate == null) return false; + + Calendar cal = Calendar.getInstance(); + cal.setTime(establishDate); + cal.set(Calendar.YEAR, Calendar.getInstance().get(Calendar.YEAR)); + cal.add(Calendar.DAY_OF_MONTH, 15); + + Date deadlineDate = cal.getTime(); + + // 如果今天是截止日期后的1-2天内,认为是刚刚永久限制 + long daysSinceDeadline = TimeUnit.MILLISECONDS.toDays( + today.getTime() - deadlineDate.getTime()); + + return daysSinceDeadline >= 0 && daysSinceDeadline <= 1; + } catch (Exception e) { + log.error("判断永久限制第一天失败,社团ID: {}", club.getId(), e); + return false; + } + } +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubPermissionServiceImpl.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubPermissionServiceImpl.java new file mode 100644 index 00000000..1c75a121 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubPermissionServiceImpl.java @@ -0,0 +1,240 @@ +package org.dromara.stgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.stgl.domain.StglClub; +import org.dromara.stgl.domain.StglClubAnnualReview; +import org.dromara.stgl.domain.vo.ClubActivityPermissionResult; +import org.dromara.stgl.mapper.StglClubMapper; +import org.dromara.stgl.mapper.StglClubAnnualReviewMapper; +import org.dromara.stgl.service.IStglClubPermissionService; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * 社团权限管理Service业务层处理 + * + * @author Cursor AI + * @date 2025-01-15 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class StglClubPermissionServiceImpl implements IStglClubPermissionService { + + private final StglClubMapper clubMapper; + private final StglClubAnnualReviewMapper annualReviewMapper; + + /** + * 年审窗口期天数(15天) + */ + private static final int ANNUAL_REVIEW_WINDOW_DAYS = 15; + + /** + * 检查社团活动权限(实时计算,精确到秒级) + */ + @Override + public ClubActivityPermissionResult checkClubActivityPermission(Long clubId) { + log.debug("开始检查社团权限,clubId: {}", clubId); + + // 1. 获取社团基础信息 + StglClub clubInfo = clubMapper.selectById(clubId); + if (clubInfo == null) { + throw new ServiceException("社团不存在,ID: " + clubId); + } + + Date establishDate = clubInfo.getEstablishDate(); + if (establishDate == null) { + throw new ServiceException("社团成立日期不能为空"); + } + + Date currentTime = new Date(); // 当前精确时间(到毫秒) + + // 2. 计算关键时间点(精确到秒) + Date annualReviewStartTime = calculateAnnualReviewStartTime(establishDate, currentTime); + Date annualReviewDeadlineTime = addDays(annualReviewStartTime, ANNUAL_REVIEW_WINDOW_DAYS); + + log.debug("社团ID: {}, 成立时间: {}, 年审开始时间: {}, 年审截止时间: {}, 当前时间: {}", + clubId, establishDate, annualReviewStartTime, annualReviewDeadlineTime, currentTime); + + // 3. 时间段判断(精确比较) + if (currentTime.before(annualReviewStartTime)) { + // 未到年审时间,正常状态 + log.debug("社团ID: {} 未到年审时间,状态正常", clubId); + return ClubActivityPermissionResult.allowed(); + } + + if (currentTime.after(annualReviewDeadlineTime)) { + // 超过15天期限,永久限制 + log.debug("社团ID: {} 已超过年审期限,永久限制", clubId); + return ClubActivityPermissionResult.permanentlyDenied( + "已超过年审提交期限(15天),无法发起活动"); + } + + // 4. 在15天窗口期内,检查年审提交情况(精确查询) + boolean hasValidSubmission = checkAnnualReviewSubmissionWithPrecision( + clubId, annualReviewStartTime, currentTime); + + if (hasValidSubmission) { + log.debug("社团ID: {} 已在窗口期内提交年审,权限恢复", clubId); + return ClubActivityPermissionResult.allowed(); + } else { + // 计算剩余时间 + long remainingMillis = annualReviewDeadlineTime.getTime() - currentTime.getTime(); + long remainingHours = TimeUnit.MILLISECONDS.toHours(remainingMillis); + long remainingDays = TimeUnit.MILLISECONDS.toDays(remainingMillis); + + String message = String.format("需要先提交年审,剩余时间:%d天%d小时", + remainingDays, remainingHours % 24); + + log.debug("社团ID: {} 在年审窗口期内但未提交,剩余{}天{}小时", + clubId, remainingDays, remainingHours % 24); + + return ClubActivityPermissionResult.temporarilyDenied( + message, annualReviewDeadlineTime, annualReviewStartTime, + remainingHours, remainingDays); + } + } + + /** + * 批量检查社团活动权限 + */ + @Override + public Map batchCheckClubActivityPermission(List clubIds) { + if (clubIds == null || clubIds.isEmpty()) { + return new HashMap<>(); + } + + return clubIds.stream() + .collect(Collectors.toMap( + clubId -> clubId, + this::checkClubActivityPermission, + (existing, replacement) -> existing + )); + } + + /** + * 检查社团是否在年审窗口期内 + */ + @Override + public boolean isInAnnualReviewWindow(Long clubId) { + ClubActivityPermissionResult result = checkClubActivityPermission(clubId); + return "TEMPORARILY_LIMITED".equals(result.getStatus()); + } + + /** + * 获取社团距离下次年审的天数 + */ + @Override + public long getDaysUntilNextAnnualReview(Long clubId) { + StglClub clubInfo = clubMapper.selectById(clubId); + if (clubInfo == null || clubInfo.getEstablishDate() == null) { + return -1; + } + + Date currentTime = new Date(); + Date nextAnnualReviewTime = calculateAnnualReviewStartTime(clubInfo.getEstablishDate(), currentTime); + + // 如果已经过了今年的年审时间,计算明年的 + if (currentTime.after(nextAnnualReviewTime)) { + Calendar cal = Calendar.getInstance(); + cal.setTime(nextAnnualReviewTime); + cal.add(Calendar.YEAR, 1); + nextAnnualReviewTime = cal.getTime(); + } + + long diffMillis = nextAnnualReviewTime.getTime() - currentTime.getTime(); + return TimeUnit.MILLISECONDS.toDays(diffMillis); + } + + /** + * 检查社团是否已在当前年审期内提交年审 + */ + @Override + public boolean hasSubmittedCurrentYearAnnualReview(Long clubId) { + StglClub clubInfo = clubMapper.selectById(clubId); + if (clubInfo == null || clubInfo.getEstablishDate() == null) { + return false; + } + + Date currentTime = new Date(); + Date annualReviewStartTime = calculateAnnualReviewStartTime(clubInfo.getEstablishDate(), currentTime); + Date annualReviewDeadlineTime = addDays(annualReviewStartTime, ANNUAL_REVIEW_WINDOW_DAYS); + + // 如果当前不在年审窗口期,返回false + if (currentTime.before(annualReviewStartTime) || currentTime.after(annualReviewDeadlineTime)) { + return false; + } + + return checkAnnualReviewSubmissionWithPrecision(clubId, annualReviewStartTime, currentTime); + } + + /** + * 计算年审开始时间(精确到具体时刻) + * + * @param establishDate 社团成立时间 + * @param currentDate 当前时间 + * @return 今年的年审开始时间 + */ + private Date calculateAnnualReviewStartTime(Date establishDate, Date currentDate) { + Calendar establishCal = Calendar.getInstance(); + establishCal.setTime(establishDate); + + Calendar currentCal = Calendar.getInstance(); + currentCal.setTime(currentDate); + + // 构造今年同月同日的时间,保持原有的时分秒毫秒 + Calendar annualReviewCal = Calendar.getInstance(); + annualReviewCal.set(Calendar.YEAR, currentCal.get(Calendar.YEAR)); + annualReviewCal.set(Calendar.MONTH, establishCal.get(Calendar.MONTH)); + annualReviewCal.set(Calendar.DAY_OF_MONTH, establishCal.get(Calendar.DAY_OF_MONTH)); + annualReviewCal.set(Calendar.HOUR_OF_DAY, establishCal.get(Calendar.HOUR_OF_DAY)); + annualReviewCal.set(Calendar.MINUTE, establishCal.get(Calendar.MINUTE)); + annualReviewCal.set(Calendar.SECOND, establishCal.get(Calendar.SECOND)); + annualReviewCal.set(Calendar.MILLISECOND, establishCal.get(Calendar.MILLISECOND)); + + return annualReviewCal.getTime(); + } + + /** + * 精确检查年审提交情况(关键改进 - 支持秒级精度) + * + * @param clubId 社团ID + * @param windowStartTime 窗口开始时间 + * @param queryTime 查询时间点 + * @return 是否已提交年审 + */ + private boolean checkAnnualReviewSubmissionWithPrecision(Long clubId, Date windowStartTime, Date queryTime) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(StglClubAnnualReview::getClubId, clubId) + .ge(StglClubAnnualReview::getCreateTime, windowStartTime) // 大于等于窗口开始时间 + .le(StglClubAnnualReview::getCreateTime, queryTime) // 小于等于当前查询时间 + .ne(StglClubAnnualReview::getStatus, "0"); // 非草稿状态 + + long count = annualReviewMapper.selectCount(wrapper); + + log.debug("年审提交检查 - 社团ID: {}, 窗口开始: {}, 查询时间: {}, 提交记录数: {}", + clubId, windowStartTime, queryTime, count); + + return count > 0; + } + + /** + * 日期加天数工具方法 + * + * @param date 原始日期 + * @param days 要添加的天数 + * @return 新日期 + */ + private Date addDays(Date date, int days) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.add(Calendar.DAY_OF_MONTH, days); + return cal.getTime(); + } +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubServiceImpl.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubServiceImpl.java index 70ef2fac..0d18877f 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubServiceImpl.java @@ -17,6 +17,7 @@ import org.dromara.stgl.domain.StglMember; import org.dromara.stgl.mapper.StglClubMapper; import org.dromara.stgl.mapper.StglMemberMapper; import org.dromara.stgl.service.IStglClubService; +import org.dromara.stgl.service.IStglClubAdvisorHistoryService; import org.dromara.stgl.utils.AdvisorRoleAssigner; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.json.utils.JsonUtils; @@ -71,6 +72,8 @@ public class StglClubServiceImpl implements IStglClubService { private final AdvisorRoleAssigner advisorRoleAssigner; // 添加用户角色Mapper private final SysUserRoleMapper sysUserRoleMapper; + // 添加指导老师历史记录Service + private final IStglClubAdvisorHistoryService advisorHistoryService; /** * 查询社团信息 @@ -342,6 +345,8 @@ public class StglClubServiceImpl implements IStglClubService { assignAdvisorRoles(add); // 为社长分配角色 assignPresidentRole(add); + // 创建指导老师历史记录 + createAdvisorHistoryRecords(add); } return flag; } @@ -372,6 +377,8 @@ public class StglClubServiceImpl implements IStglClubService { syncVicePresidentsInMembers(update); // 处理指导老师角色分配 handleAdvisorRoleAssignment(oldClub, update); + // 更新指导老师历史记录 + updateAdvisorHistoryRecords(oldClub, update); } return updateResult; @@ -1347,4 +1354,104 @@ public class StglClubServiceImpl implements IStglClubService { } } + /** + * 创建指导老师历史记录(新增社团时) + * + * @param club 社团信息 + */ + private void createAdvisorHistoryRecords(StglClub club) { + try { + List advisors = parseAdvisorsFromClub(club); + if (!advisors.isEmpty()) { + // 新建社团时,指导老师的上任时间应该是社团成立日期,而不是当前时间 + Date startTime = club.getEstablishDate() != null ? club.getEstablishDate() : new Date(); + advisorHistoryService.updateClubAdvisors(club.getId(), advisors, startTime); + log.info("为社团 {} 创建指导老师历史记录成功,指导老师数量: {},上任时间: {}", + club.getClubName(), advisors.size(), startTime); + } + } catch (Exception e) { + log.error("为社团 {} 创建指导老师历史记录失败", club.getClubName(), e); + // 不抛出异常,避免影响主流程 + } + } + + /** + * 更新指导老师历史记录(更新社团时) + * + * @param oldClub 旧的社团信息 + * @param newClub 新的社团信息 + */ + private void updateAdvisorHistoryRecords(StglClub oldClub, StglClub newClub) { + try { + List advisors = parseAdvisorsFromClub(newClub); + advisorHistoryService.updateClubAdvisors(newClub.getId(), advisors); + log.info("为社团 {} 更新指导老师历史记录成功,指导老师数量: {}", newClub.getClubName(), advisors.size()); + } catch (Exception e) { + log.error("为社团 {} 更新指导老师历史记录失败", newClub.getClubName(), e); + // 不抛出异常,避免影响主流程 + } + } + + /** + * 从社团信息中解析指导老师列表 + * + * @param club 社团信息 + * @return 指导老师列表 + */ + private List parseAdvisorsFromClub(StglClub club) { + List advisors = new ArrayList<>(); + + // 优先从JSON字段解析 + if (StringUtils.isNotBlank(club.getAdvisorsJson())) { + try { + ObjectMapper mapper = new ObjectMapper(); + List> advisorMaps = mapper.readValue( + club.getAdvisorsJson(), + new TypeReference>>() {} + ); + + for (Map advisorMap : advisorMaps) { + String name = (String) advisorMap.get("name"); + String dept = (String) advisorMap.get("dept"); + String phone = (String) advisorMap.get("phone"); + String email = (String) advisorMap.get("email"); + Long userId = null; + + // 尝试获取用户ID + Object userIdObj = advisorMap.get("userId"); + if (userIdObj != null) { + if (userIdObj instanceof Number) { + userId = ((Number) userIdObj).longValue(); + } else if (userIdObj instanceof String) { + try { + userId = Long.parseLong((String) userIdObj); + } catch (NumberFormatException e) { + // 忽略解析错误 + } + } + } + + if (StringUtils.isNotBlank(name)) { + advisors.add(new IStglClubAdvisorHistoryService.AdvisorInfo(name, dept, phone, email, userId)); + } + } + } catch (Exception e) { + log.warn("解析指导老师JSON数据失败: {}", e.getMessage()); + } + } + + // 如果JSON解析失败或为空,回退到单一字段 + if (advisors.isEmpty() && StringUtils.isNotBlank(club.getAdvisorName())) { + advisors.add(new IStglClubAdvisorHistoryService.AdvisorInfo( + club.getAdvisorName(), + club.getAdvisorDept(), + club.getAdvisorPhone(), + club.getAdvisorEmail(), + null + )); + } + + return advisors; + } + } \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/StglMyMapper.xml b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/StglMyMapper.xml index 2df4193b..9715a314 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/StglMyMapper.xml +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/StglMyMapper.xml @@ -159,6 +159,8 @@ a.end_time, a.location, a.organizer_name, + a.related_dept_name, + a.related_dept_names, CAST(a.related_type AS CHAR) as related_type, CASE WHEN a.related_type = 1 THEN c.club_name diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglActivityMapper.xml b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglActivityMapper.xml index f94ee3f5..03af3451 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglActivityMapper.xml +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglActivityMapper.xml @@ -14,6 +14,10 @@ + + + + @@ -55,22 +59,25 @@ SELECT sa.id, sa.activity_name, sa.activity_type, sa.activity_theme, sa.start_time, sa.end_time, sa.location, sa.description, - sa.poster_url, sa.related_club_id, sa.related_type, sa.organizer_name, sa.organizer_type, + sa.poster_url, sa.related_dept_id, sa.related_dept_name, sa.related_dept_ids, sa.related_dept_names, + sa.related_club_id, sa.related_type, sa.organizer_name, sa.organizer_type, sa.organizer_contact, sa.organizer_phone, sa.co_organizers, sa.instructors, sa.registration_type, sa.registration_start, sa.registration_end, - -- 动态计算报名状态:优先根据活动状态,再根据报名时间范围 + -- 动态计算报名状态:只有审批通过的活动才根据时间计算报名状态 CASE - -- 首先根据活动状态强制设置(优先级最高) - WHEN sa.activity_status = '0' THEN '0' -- 草稿 → 未开始(固定) - WHEN sa.activity_status = '1' THEN '0' -- 未开始 → 未开始(固定) - WHEN sa.activity_status = '2' THEN '0' -- 待审批 → 未开始(固定) - WHEN sa.activity_status = '5' THEN '2' -- 已结束 → 报名结束(固定) - WHEN sa.activity_status = '6' THEN '3' -- 已取消 → 已关闭(固定) - WHEN sa.activity_status = '7' THEN '0' -- 已驳回 → 未开始(固定) + -- 草稿、待审批、已驳回状态不映射报名状态,保持为空 + WHEN sa.activity_status IN ('0', '2', '7') THEN NULL - -- 然后根据活动状态和报名时间判断 - WHEN sa.activity_status = '3' OR sa.activity_status = '4' THEN + -- 已取消的活动设置为已关闭 + WHEN sa.activity_status = '6' THEN '3' -- 已取消 → 已关闭(固定) + + -- 只有审批通过的活动(已通过、进行中、已结束)才根据时间计算报名状态 + WHEN sa.activity_status IN ('3', '4', '5') THEN CASE + -- 已结束的活动强制设置报名结束 + WHEN sa.activity_status = '5' THEN '2' -- 已结束 → 报名结束(固定) + + -- 已通过和进行中的活动根据报名时间判断 WHEN sa.registration_start IS NULL OR sa.registration_end IS NULL THEN sa.registration_status = sa.registration_start AND NOW() <= sa.registration_end THEN '1']]> @@ -119,9 +126,9 @@ END ELSE NULL END AS related_club_name, - -- 统计报名人数(子查询) + -- 统计报名人数(子查询)- 只统计审核通过的报名记录 (SELECT COUNT(*) FROM stgl_activity_registration sar - WHERE sar.activity_id = sa.id AND sar.deleted = 0) AS registration_count, + WHERE sar.activity_id = sa.id AND sar.deleted = 0 AND sar.approval_status = 1) AS registration_count, -- 统计平均评分(子查询) (SELECT ROUND(AVG(rating_score), 1) FROM stgl_activity_rating sar2 WHERE sar2.activity_id = sa.id AND sar2.deleted = 0) AS average_rating @@ -156,17 +163,19 @@ AND ( CASE - -- 首先根据活动状态强制设置(优先级最高) - WHEN sa.activity_status = '0' THEN '0' -- 草稿 → 未开始(固定) - WHEN sa.activity_status = '1' THEN '0' -- 未开始 → 未开始(固定) - WHEN sa.activity_status = '2' THEN '0' -- 待审批 → 未开始(固定) - WHEN sa.activity_status = '5' THEN '2' -- 已结束 → 报名结束(固定) - WHEN sa.activity_status = '6' THEN '3' -- 已取消 → 已关闭(固定) - WHEN sa.activity_status = '7' THEN '0' -- 已驳回 → 未开始(固定) + -- 草稿、待审批、已驳回状态不映射报名状态,保持为空 + WHEN sa.activity_status IN ('0', '2', '7') THEN NULL - -- 然后根据活动状态和报名时间判断 - WHEN sa.activity_status = '3' OR sa.activity_status = '4' THEN + -- 已取消的活动设置为已关闭 + WHEN sa.activity_status = '6' THEN '3' -- 已取消 → 已关闭(固定) + + -- 只有审批通过的活动(已通过、进行中、已结束)才根据时间计算报名状态 + WHEN sa.activity_status IN ('3', '4', '5') THEN CASE + -- 已结束的活动强制设置报名结束 + WHEN sa.activity_status = '5' THEN '2' -- 已结束 → 报名结束(固定) + + -- 已通过和进行中的活动根据报名时间判断 WHEN sa.registration_start IS NULL OR sa.registration_end IS NULL THEN sa.registration_status = sa.registration_start AND NOW() <= sa.registration_end THEN '1']]> @@ -191,6 +200,9 @@ AND sa.registration_end <= #{registrationEnd} + + AND sa.related_dept_id = #{relatedDeptId} + AND sa.related_club_id = #{relatedClubId} @@ -258,17 +270,19 @@ AND ( CASE - -- 首先根据活动状态强制设置(优先级最高) - WHEN sa.activity_status = '0' THEN '0' -- 草稿 → 未开始(固定) - WHEN sa.activity_status = '1' THEN '0' -- 未开始 → 未开始(固定) - WHEN sa.activity_status = '2' THEN '0' -- 待审批 → 未开始(固定) - WHEN sa.activity_status = '5' THEN '2' -- 已结束 → 报名结束(固定) - WHEN sa.activity_status = '6' THEN '3' -- 已取消 → 已关闭(固定) - WHEN sa.activity_status = '7' THEN '0' -- 已驳回 → 未开始(固定) + -- 草稿、待审批、已驳回状态不映射报名状态,保持为空 + WHEN sa.activity_status IN ('0', '2', '7') THEN NULL - -- 然后根据活动状态和报名时间判断 - WHEN sa.activity_status = '3' OR sa.activity_status = '4' THEN + -- 已取消的活动设置为已关闭 + WHEN sa.activity_status = '6' THEN '3' -- 已取消 → 已关闭(固定) + + -- 只有审批通过的活动(已通过、进行中、已结束)才根据时间计算报名状态 + WHEN sa.activity_status IN ('3', '4', '5') THEN CASE + -- 已结束的活动强制设置报名结束 + WHEN sa.activity_status = '5' THEN '2' -- 已结束 → 报名结束(固定) + + -- 已通过和进行中的活动根据报名时间判断 WHEN sa.registration_start IS NULL OR sa.registration_end IS NULL THEN sa.registration_status = sa.registration_start AND NOW() <= sa.registration_end THEN '1']]> @@ -285,17 +299,19 @@ AND ( CASE - -- 首先根据活动状态强制设置(优先级最高) - WHEN sa.activity_status = '0' THEN '0' -- 草稿 → 未开始(固定) - WHEN sa.activity_status = '1' THEN '0' -- 未开始 → 未开始(固定) - WHEN sa.activity_status = '2' THEN '0' -- 待审批 → 未开始(固定) - WHEN sa.activity_status = '5' THEN '2' -- 已结束 → 报名结束(固定) - WHEN sa.activity_status = '6' THEN '3' -- 已取消 → 已关闭(固定) - WHEN sa.activity_status = '7' THEN '0' -- 已驳回 → 未开始(固定) + -- 草稿、待审批、已驳回状态不映射报名状态,保持为空 + WHEN sa.activity_status IN ('0', '2', '7') THEN NULL - -- 然后根据活动状态和报名时间判断 - WHEN sa.activity_status = '3' OR sa.activity_status = '4' THEN + -- 已取消的活动设置为已关闭 + WHEN sa.activity_status = '6' THEN '3' -- 已取消 → 已关闭(固定) + + -- 只有审批通过的活动(已通过、进行中、已结束)才根据时间计算报名状态 + WHEN sa.activity_status IN ('3', '4', '5') THEN CASE + -- 已结束的活动强制设置报名结束 + WHEN sa.activity_status = '5' THEN '2' -- 已结束 → 报名结束(固定) + + -- 已通过和进行中的活动根据报名时间判断 WHEN sa.registration_start IS NULL OR sa.registration_end IS NULL THEN sa.registration_status = sa.registration_start AND NOW() <= sa.registration_end THEN '1']]> @@ -323,6 +339,12 @@ AND sa.tenant_id = #{activity.tenantId} + + AND sa.related_dept_id = #{activity.relatedDeptId} + + + AND sa.related_club_id = #{activity.relatedClubId} + AND ( @@ -383,7 +405,8 @@ INSERT INTO stgl_activity ( id, tenant_id, create_dept, create_by, create_time, update_by, update_time, activity_name, activity_type, activity_theme, start_time, end_time, location, - description, poster_url, related_club_id, organizer_name, organizer_type, + description, poster_url, related_dept_id, related_dept_name, related_dept_ids, related_dept_names, + related_club_id, organizer_name, organizer_type, organizer_contact, organizer_phone, co_organizers, instructors, registration_type, registration_start, registration_end, registration_status, max_participants, max_teams, team_size_min, team_size_max, need_approval, @@ -394,7 +417,8 @@ #{newActivity.createBy}, #{newActivity.createTime}, #{newActivity.updateBy}, #{newActivity.updateTime}, #{newActivity.activityName}, activity_type, activity_theme, #{newActivity.startTime}, #{newActivity.endTime}, location, - description, poster_url, related_club_id, organizer_name, organizer_type, + description, poster_url, related_dept_id, related_dept_name, related_dept_ids, related_dept_names, + related_club_id, organizer_name, organizer_type, organizer_contact, organizer_phone, co_organizers, instructors, registration_type, #{newActivity.registrationStart}, #{newActivity.registrationEnd}, #{newActivity.registrationStatus}, max_participants, max_teams, team_size_min, team_size_max, need_approval, diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglAnnualReviewAuditMapper.xml b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglAnnualReviewAuditMapper.xml new file mode 100644 index 00000000..be65fd52 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglAnnualReviewAuditMapper.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + select id, tenant_id, create_dept, create_by, create_time, update_by, update_time, deleted, + annual_review_id, node_code, node_name, node_order, audit_status, + approver_id, approver_name, approve_result, audit_opinion, audit_time + from stgl_annual_review_audit + + + + + + + + + + + + \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglAuditorMapper.xml b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglAuditorMapper.xml new file mode 100644 index 00000000..e5cb2b93 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglAuditorMapper.xml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + SELECT + sa.id, + sa.user_id, + sa.user_name, + sa.nick_name, + sa.dept_id, + sa.dept_name, + sa.phone_number, + sa.email, + sa.status, + sa.remark, + sa.config_by, + sa.config_by_name, + sa.create_by, + sa.create_time, + sa.update_by, + sa.update_time + FROM stgl_auditor sa + + + + + + + + + + UPDATE stgl_auditor + SET status = '1', update_time = NOW() + WHERE id IN + + #{id} + + AND deleted = 0 + + + + UPDATE stgl_auditor + SET status = '0', update_time = NOW() + WHERE id IN + + #{id} + + AND deleted = 0 + + + + + + \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglClassMapper.xml b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglClassMapper.xml index 06ebe6c7..4e353556 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglClassMapper.xml +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglClassMapper.xml @@ -14,6 +14,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + + @@ -52,6 +55,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" COALESCE(club_stats.club_count, 0) as club_count, COALESCE(volunteer_stats.volunteer_team_count, 0) as volunteer_team_count, COALESCE(activity_stats.activity_count, 0) as activity_count, + today_activity_stats.activity_name as today_activity_name, + today_activity_stats.start_time as today_activity_start_time, + today_activity_stats.end_time as today_activity_end_time, s.create_by, s.create_time, s.update_by, @@ -60,6 +66,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" s.deleted FROM xg_student_msg s LEFT JOIN xg_class_msg c ON s.class_id = c.id + AND c.tenant_id = '000000' LEFT JOIN ( SELECT m.user_id, @@ -68,6 +75,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" WHERE m.member_type = 1 AND m.deleted = 0 AND m.status = 2 + AND m.tenant_id = '000000' GROUP BY m.user_id ) club_stats ON s.id = club_stats.user_id LEFT JOIN ( @@ -78,6 +86,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" WHERE m.member_type = 2 AND m.deleted = 0 AND m.status = 2 + AND m.tenant_id = '000000' GROUP BY m.user_id ) volunteer_stats ON s.id = volunteer_stats.user_id LEFT JOIN ( @@ -125,9 +134,132 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND user_id != '' GROUP BY user_id ) activity_stats ON s.id = activity_stats.user_id + LEFT JOIN ( + -- 查询学生今日活动信息(审核通过且时间为今天的活动,只取最早的一个) + -- 使用兼容MySQL 5.7的方式,通过MIN函数和相关子查询实现 + SELECT + ta.user_id, + ta.activity_name, + DATE_FORMAT(ta.start_time, '%H:%i') as start_time, + DATE_FORMAT(ta.end_time, '%H:%i') as end_time + FROM ( + -- 个人报名的今日活动 + SELECT + ar.user_id, + a.activity_name, + a.start_time, + a.end_time + FROM stgl_activity_registration ar + INNER JOIN stgl_activity a ON ar.activity_id = a.id + AND ar.tenant_id = '000000' + AND a.tenant_id = '000000' + WHERE ar.deleted = 0 + AND ar.approval_status = 1 + AND ar.tenant_id = '000000' + AND a.deleted = 0 + AND DATE(a.start_time) = CURDATE() -- 今日活动 + AND a.tenant_id = '000000' + + UNION + + -- 团队报名的今日活动(通过团队成员信息) + SELECT + JSON_UNQUOTE(JSON_EXTRACT(ati.team_members, CONCAT('$[', numbers.n, '].userId'))) as user_id, + a.activity_name, + a.start_time, + a.end_time + FROM stgl_activity_team_info ati + CROSS JOIN ( + SELECT 0 as n UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION + SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION + SELECT 10 UNION SELECT 11 UNION SELECT 12 UNION SELECT 13 UNION SELECT 14 UNION + SELECT 15 UNION SELECT 16 UNION SELECT 17 UNION SELECT 18 UNION SELECT 19 + ) numbers + INNER JOIN stgl_activity a ON ati.activity_id = a.id + AND ati.tenant_id = '000000' + AND a.tenant_id = '000000' + WHERE ati.deleted = 0 + AND ati.tenant_id = '000000' + AND a.deleted = 0 + AND DATE(a.start_time) = CURDATE() -- 今日活动 + AND a.tenant_id = '000000' + AND JSON_UNQUOTE(JSON_EXTRACT(ati.team_members, CONCAT('$[', numbers.n, '].userId'))) IS NOT NULL + AND JSON_UNQUOTE(JSON_EXTRACT(ati.team_members, CONCAT('$[', numbers.n, '].userId'))) != '' + AND EXISTS ( + SELECT 1 FROM stgl_activity_registration team_ar + WHERE team_ar.team_id = ati.id + AND team_ar.deleted = 0 + AND team_ar.approval_status = 1 + AND team_ar.registration_type = 2 + AND team_ar.tenant_id = '000000' + AND team_ar.tenant_id = '000000' + ) + AND ati.tenant_id = '000000' + ) ta + WHERE ta.user_id IS NOT NULL + AND ta.user_id != '' + -- 只取每个用户最早的活动(通过相关子查询实现,兼容MySQL 5.7) + AND ta.start_time = ( + SELECT MIN(ta2.start_time) + FROM ( + -- 个人报名的今日活动 + SELECT + ar2.user_id, + a2.start_time + FROM stgl_activity_registration ar2 + INNER JOIN stgl_activity a2 ON ar2.activity_id = a2.id + AND ar2.tenant_id = '000000' + AND a2.tenant_id = '000000' + WHERE ar2.deleted = 0 + AND ar2.approval_status = 1 + AND ar2.tenant_id = '000000' + AND a2.deleted = 0 + AND DATE(a2.start_time) = CURDATE() + AND a2.tenant_id = '000000' + + UNION + + -- 团队报名的今日活动 + SELECT + JSON_UNQUOTE(JSON_EXTRACT(ati2.team_members, CONCAT('$[', numbers2.n, '].userId'))) as user_id, + a2.start_time + FROM stgl_activity_team_info ati2 + CROSS JOIN ( + SELECT 0 as n UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION + SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION + SELECT 10 UNION SELECT 11 UNION SELECT 12 UNION SELECT 13 UNION SELECT 14 UNION + SELECT 15 UNION SELECT 16 UNION SELECT 17 UNION SELECT 18 UNION SELECT 19 + ) numbers2 + INNER JOIN stgl_activity a2 ON ati2.activity_id = a2.id + AND ati2.tenant_id = '000000' + AND a2.tenant_id = '000000' + WHERE ati2.deleted = 0 + AND ati2.tenant_id = '000000' + AND a2.deleted = 0 + AND DATE(a2.start_time) = CURDATE() + AND a2.tenant_id = '000000' + AND JSON_UNQUOTE(JSON_EXTRACT(ati2.team_members, CONCAT('$[', numbers2.n, '].userId'))) IS NOT NULL + AND JSON_UNQUOTE(JSON_EXTRACT(ati2.team_members, CONCAT('$[', numbers2.n, '].userId'))) != '' + AND EXISTS ( + SELECT 1 FROM stgl_activity_registration team_ar2 + WHERE team_ar2.team_id = ati2.id + AND team_ar2.deleted = 0 + AND team_ar2.approval_status = 1 + AND team_ar2.registration_type = 2 + AND team_ar2.tenant_id = '000000' + AND team_ar2.tenant_id = '000000' + ) + AND ati2.tenant_id = '000000' + ) ta2 + WHERE ta2.user_id = ta.user_id + AND ta2.user_id IS NOT NULL + AND ta2.user_id != '' + ) + ) today_activity_stats ON s.id = today_activity_stats.user_id s.deleted = 0 AND s.student_status = '1' + AND s.tenant_id = '000000' AND s.class_id = #{classId} @@ -151,6 +283,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" s.deleted = 0 AND s.student_status = '1' + AND s.tenant_id = '000000' AND s.class_id = #{classId} @@ -185,7 +318,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND m.member_type = 1 AND m.deleted = 0 AND m.status = 2 + AND m.tenant_id = '000000' AND c.deleted = 0 + AND c.tenant_id = '000000' ORDER BY c.create_time DESC @@ -209,7 +344,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND m.member_type = 2 AND m.deleted = 0 AND m.status = 2 + AND m.tenant_id = '000000' AND vt.deleted = 0 + AND vt.tenant_id = '000000' ORDER BY vt.create_time DESC diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglClubAdvisorHistoryMapper.xml b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglClubAdvisorHistoryMapper.xml new file mode 100644 index 00000000..5d4b1dfc --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglClubAdvisorHistoryMapper.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + select h.id, h.club_id, h.advisor_name, h.advisor_dept, h.advisor_phone, h.advisor_email, + h.advisor_user_id, h.status, h.start_time, h.end_time, + h.create_by, h.create_time, h.update_by, h.update_time, + c.club_name + from stgl_club_advisor_history h + left join stgl_club c on h.club_id = c.id + + + + + + + + + + + + + + update stgl_club_advisor_history + set status = 0, end_time = #{endTime}, update_time = now() + where club_id = #{clubId} and status = 1 and deleted = 0 + + and advisor_name in + + #{name} + + + + + \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglClubAnnualReviewActivityMapper.xml b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglClubAnnualReviewActivityMapper.xml new file mode 100644 index 00000000..6545ffd6 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglClubAnnualReviewActivityMapper.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + select id, tenant_id, create_dept, create_by, create_time, update_by, update_time, deleted, annual_review_id, activity_name, activity_time, activity_start_time, activity_end_time, activity_location, activity_type, participant_count, activity_description from stgl_club_annual_review_activity + + + + + + + + update stgl_club_annual_review_activity set deleted = 1 + where annual_review_id = #{annualReviewId} and deleted = 0 + + + \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglClubAnnualReviewMapper.xml b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglClubAnnualReviewMapper.xml new file mode 100644 index 00000000..8152d445 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglClubAnnualReviewMapper.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select + ar.id, ar.club_id, ar.review_year, ar.review_start_date, ar.review_end_date, ar.review_time, + ar.club_name, ar.establish_date, ar.club_type, ar.advisor_name, ar.advisor_dept, + ar.has_charter, ar.charter_url, ar.has_funds, ar.fund_amount, ar.member_count, + ar.status, ar.advisor_opinion, ar.college_opinion, ar.youth_league_opinion, + ar.members_json, ar.activities_json, + ar.create_by, ar.create_time, ar.update_by, ar.update_time, ar.tenant_id, + c.president_name + from stgl_club_annual_review ar + left join stgl_club c on ar.club_id = c.id + + + + + + + + + \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglClubAnnualReviewMemberMapper.xml b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglClubAnnualReviewMemberMapper.xml new file mode 100644 index 00000000..e61337a7 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglClubAnnualReviewMemberMapper.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + select id, tenant_id, create_dept, create_by, create_time, update_by, update_time, deleted, annual_review_id, member_name, class_name, role, contact_info from stgl_club_annual_review_member + + + + + + + + update stgl_club_annual_review_member set deleted = 1 + where annual_review_id = #{annualReviewId} and deleted = 0 + + + \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormApplicationController.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormApplicationController.java new file mode 100644 index 00000000..47eac861 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormApplicationController.java @@ -0,0 +1,140 @@ +package org.dromara.xg.controller; + +import java.util.List; + +import lombok.RequiredArgsConstructor; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.*; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.web.core.BaseController; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.xg.domain.vo.XgDormApplicationVo; +import org.dromara.xg.domain.bo.XgDormApplicationBo; +import org.dromara.xg.domain.vo.StudentOptionVo; +import org.dromara.xg.domain.bo.StudentOptionBo; +import org.dromara.xg.service.IXgDormApplicationService; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import java.util.Map; +import java.util.HashMap; + +/** + * 学生宿舍申请单表(调宿+退宿) + * + * @author zlwu + * @date 2025-01-15 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/dormApplication/dormApplication") +public class XgDormApplicationController extends BaseController { + + private final IXgDormApplicationService xgDormApplicationService; + + /** + * 查询学生宿舍申请单表(调宿+退宿)列表 + */ + @SaCheckPermission("dormApplication:dormApplication:list") + @GetMapping("/list") + public TableDataInfo list(XgDormApplicationBo bo, PageQuery pageQuery) { + return xgDormApplicationService.queryPageList(bo, pageQuery); + } + + /** + * 获取学生选择器选项列表(支持按学号、姓名检索) + */ + @SaCheckPermission("dormApplication:dormApplication:list") + @GetMapping("/studentOptions") + public TableDataInfo getStudentOptions(StudentOptionBo bo, PageQuery pageQuery) { + return xgDormApplicationService.getStudentOptions(bo, pageQuery); + } + + /** + * 导出学生宿舍申请单表(调宿+退宿)列表 + */ + @SaCheckPermission("dormApplication:dormApplication:export") + @Log(title = "学生宿舍申请单表(调宿+退宿)", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(XgDormApplicationBo bo, HttpServletResponse response) { + List list = xgDormApplicationService.queryList(bo); + ExcelUtil.exportExcel(list, "学生宿舍申请单表(调宿+退宿)", XgDormApplicationVo.class, response); + } + + /** + * 获取学生宿舍申请单表(调宿+退宿)详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("dormApplication:dormApplication:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) { + return R.ok(xgDormApplicationService.queryById(id)); + } + + /** + * 新增学生宿舍申请单表(调宿+退宿) + */ + @SaCheckPermission("dormApplication:dormApplication:add") + @Log(title = "学生宿舍申请单表(调宿+退宿)", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody XgDormApplicationBo bo) { + XgDormApplicationVo result = xgDormApplicationService.insertByBo(bo); + return R.ok(result); + } + + /** + * 修改学生宿舍申请单表(调宿+退宿) + */ + @SaCheckPermission("dormApplication:dormApplication:edit") + @Log(title = "学生宿舍申请单表(调宿+退宿)", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody XgDormApplicationBo bo) { + return toAjax(xgDormApplicationService.updateByBo(bo)); + } + + /** + * 删除学生宿舍申请单表(调宿+退宿) + * + * @param ids 主键串 + */ + @SaCheckPermission("dormApplication:dormApplication:remove") + @Log(title = "学生宿舍申请单表(调宿+退宿)", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) { + return toAjax(xgDormApplicationService.deleteWithValidByIds(List.of(ids), true)); + } + + /** + * 获取二级学院审批人列表 + * + * @param collegeId 学院ID + */ + @SaCheckPermission("dormApplication:dormApplication:list") + @GetMapping("/getCollegeApprovers") + public R>> getCollegeApprovers(@RequestParam Long collegeId) { + List> approvers = xgDormApplicationService.getCollegeApprovers(collegeId); + return R.ok(approvers); + } + + /** + * 获取学工处审批人列表 + */ + @SaCheckPermission("dormApplication:dormApplication:list") + @GetMapping("/getStudentAffairsApprovers") + public R>> getStudentAffairsApprovers() { + List> approvers = xgDormApplicationService.getStudentAffairsApprovers(); + return R.ok(approvers); + } + +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBedStudentController.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBedStudentController.java index 2c02ae9f..2ebc45b4 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBedStudentController.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBedStudentController.java @@ -332,4 +332,26 @@ public class XgDormBedStudentController extends BaseController { return R.ok(roomStudents); } + /** + * 根据学生ID获取当前宿舍信息 + * 包含校区、宿舍楼、房间号、床位号等信息 + */ + @SaCheckPermission("dormBedStudent:dormBedStudent:list") + @GetMapping("/currentDormInfo/{studentId}") + public R getCurrentDormInfo(@NotNull(message = "学生ID不能为空") @PathVariable Long studentId) { + Object currentDormInfo = xgDormBedStudentService.getCurrentDormInfo(studentId); + return R.ok(currentDormInfo); + } + + /** + * 根据床位ID获取该床位的学生信息 + * 包含学生基本信息、学院、专业、班级等信息 + */ + @SaCheckPermission("dormBedStudent:dormBedStudent:list") + @GetMapping("/bedStudentInfo/{bedId}") + public R getBedStudentInfo(@NotNull(message = "床位ID不能为空") @PathVariable Long bedId) { + Object bedStudentInfo = xgDormBedStudentService.getBedStudentInfo(bedId); + return R.ok(bedStudentInfo); + } + } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormApplication.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormApplication.java new file mode 100644 index 00000000..8976421c --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormApplication.java @@ -0,0 +1,216 @@ +package org.dromara.xg.domain; + +import org.dromara.common.mybatis.core.domain.BaseEntity; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +/** + * 学生宿舍申请单表(调宿+退宿)对象 xg_dorm_application + * + * @author zlwu + * @date 2025-01-15 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("xg_dorm_application") +public class XgDormApplication extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID(雪花ID) + */ + @TableId(value = "id") + private Long id; + + /** + * 申请名称 + */ + private String applicationName; + + /** + * 申请类型(1:调宿申请 2:退宿申请) + */ + private String applicationType; + + /** + * 审批状态:cancel-已撤销 , draft-草稿, waiting-待审核, finish-已完成, invalid-已作废, back-已退回, + * termination-已终止 + */ + private String applicationStatus; + + /** + * 申请学生ID + */ + private Long studentId; + + /** + * 学号 + */ + private String studentNo; + + /** + * 学生姓名 + */ + private String studentName; + + /** + * 学生联系电话 + */ + private String studentPhone; + + /** + * 学院ID + */ + private Long collegeId; + + /** + * 学院名称 + */ + private String collegeName; + + /** + * 专业ID + */ + private Long majorId; + + /** + * 专业名称 + */ + private String majorName; + + /** + * 班级ID + */ + private Long classId; + + /** + * 班级名称 + */ + private String className; + + /** + * 当前校区ID + */ + private Long currentCampusId; + + /** + * 当前校区名称 + */ + private String currentCampusName; + + /** + * 当前宿舍楼ID + */ + private Long currentBuildingId; + + /** + * 当前宿舍楼名称 + */ + private String currentBuildingName; + + /** + * 当前房间ID + */ + private Long currentRoomId; + + /** + * 当前房间号 + */ + private String currentRoomNo; + + /** + * 当前床位ID + */ + private Long currentBedId; + + /** + * 当前床位号 + */ + private String currentBedNo; + + /** + * 目标校区ID + */ + private Long targetCampusId; + + /** + * 目标校区名称 + */ + private String targetCampusName; + + /** + * 目标宿舍楼ID + */ + private Long targetBuildingId; + + /** + * 目标宿舍楼名称 + */ + private String targetBuildingName; + + /** + * 目标房间ID + */ + private Long targetRoomId; + + /** + * 目标房间号 + */ + private String targetRoomNo; + + /** + * 目标床位ID + */ + private Long targetBedId; + + /** + * 目标床位号 + */ + private String targetBedNo; + + /** + * 详细原因说明 + */ + private String reasonDetail; + + /** + * 申请时间 + */ + private Date applyTime; + + /** + * 附件OSS文件ID,多个用逗号分隔 + */ + private String attachmentOssIds; + + /** + * 备注信息 + */ + private String remark; + + /** + * 扩展字段1 + */ + private String extField1; + + /** + * 扩展字段2 + */ + private String extField2; + + /** + * 扩展字段3 + */ + private String extField3; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + private String deleted; + +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormApplicationBo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormApplicationBo.java new file mode 100644 index 00000000..6763e434 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormApplicationBo.java @@ -0,0 +1,226 @@ +package org.dromara.xg.domain.bo; + +import org.dromara.xg.domain.XgDormApplication; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.*; + +import java.util.Date; + +/** + * 学生宿舍申请单表(调宿+退宿)业务对象 xg_dorm_application + * + * @author zlwu + * @date 2025-01-15 + */ + +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = XgDormApplication.class, reverseConvertGenerate = false) +public class XgDormApplicationBo extends BaseEntity { + + /** + * 主键ID(雪花ID) + */ + @NotNull(message = "主键ID不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 申请名称 + */ + @NotBlank(message = "申请名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String applicationName; + + /** + * 申请类型(1:调宿申请 2:退宿申请) + */ + @NotBlank(message = "申请类型不能为空", groups = { AddGroup.class, EditGroup.class }) + private String applicationType; + + /** + * 审批状态:cancel-已撤销 , draft-草稿, waiting-待审核, finish-已完成, invalid-已作废, back-已退回, + * termination-已终止 + */ + private String applicationStatus; + + /** + * 申请学生ID + */ + @NotNull(message = "申请学生ID不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long studentId; + + /** + * 学号 + */ + @NotBlank(message = "学号不能为空", groups = { AddGroup.class, EditGroup.class }) + private String studentNo; + + /** + * 学生姓名 + */ + @NotBlank(message = "学生姓名不能为空", groups = { AddGroup.class, EditGroup.class }) + private String studentName; + + /** + * 学生联系电话 + */ + private String studentPhone; + + /** + * 学院ID + */ + @NotNull(message = "学院ID不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long collegeId; + + /** + * 学院名称 + */ + private String collegeName; + + /** + * 专业ID + */ + private Long majorId; + + /** + * 专业名称 + */ + private String majorName; + + /** + * 班级ID + */ + private Long classId; + + /** + * 班级名称 + */ + private String className; + + /** + * 当前校区ID + */ + private Long currentCampusId; + + /** + * 当前校区名称 + */ + private String currentCampusName; + + /** + * 当前宿舍楼ID + */ + private Long currentBuildingId; + + /** + * 当前宿舍楼名称 + */ + private String currentBuildingName; + + /** + * 当前房间ID + */ + private Long currentRoomId; + + /** + * 当前房间号 + */ + private String currentRoomNo; + + /** + * 当前床位ID + */ + private Long currentBedId; + + /** + * 当前床位号 + */ + private String currentBedNo; + + /** + * 目标校区ID + */ + private Long targetCampusId; + + /** + * 目标校区名称 + */ + private String targetCampusName; + + /** + * 目标宿舍楼ID + */ + private Long targetBuildingId; + + /** + * 目标宿舍楼名称 + */ + private String targetBuildingName; + + /** + * 目标房间ID + */ + private Long targetRoomId; + + /** + * 目标房间号 + */ + private String targetRoomNo; + + /** + * 目标床位ID + */ + private Long targetBedId; + + /** + * 目标床位号 + */ + private String targetBedNo; + + /** + * 详细原因说明 + */ + @NotBlank(message = "详细原因说明不能为空", groups = { AddGroup.class, EditGroup.class }) + private String reasonDetail; + + /** + * 申请时间 + */ + @NotNull(message = "申请时间不能为空", groups = { AddGroup.class, EditGroup.class }) + private Date applyTime; + + /** + * 附件OSS文件ID,多个用逗号分隔 + */ + private String attachmentOssIds; + + /** + * 备注信息 + */ + private String remark; + + /** + * 扩展字段1 + */ + private String extField1; + + /** + * 扩展字段2 + */ + private String extField2; + + /** + * 扩展字段3 + */ + private String extField3; + + /** + * 删除标志(0代表存在 1代表删除) + */ + private String deleted; + +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormApplicationVo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormApplicationVo.java new file mode 100644 index 00000000..27f63f4f --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormApplicationVo.java @@ -0,0 +1,237 @@ +package org.dromara.xg.domain.vo; + +import org.dromara.xg.domain.XgDormApplication; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.io.Serializable; +import java.util.Date; + +/** + * 学生宿舍申请单表(调宿+退宿)视图对象 xg_dorm_application + * + * @author zlwu + * @date 2025-01-15 + */ +@Data +@AutoMapper(target = XgDormApplication.class) +public class XgDormApplicationVo implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID(雪花ID) + */ + private Long id; + + /** + * 申请名称 + */ + private String applicationName; + + /** + * 申请类型(1:调宿申请 2:退宿申请) + */ + private String applicationType; + + /** + * 审批状态:cancel-已撤销 , draft-草稿, waiting-待审核, finish-已完成, invalid-已作废, back-已退回, + * termination-已终止 + */ + private String applicationStatus; + + /** + * 申请学生ID + */ + private Long studentId; + + /** + * 学号 + */ + private String studentNo; + + /** + * 学生姓名 + */ + private String studentName; + + /** + * 学生联系电话 + */ + private String studentPhone; + + /** + * 学院ID + */ + private Long collegeId; + + /** + * 学院名称 + */ + private String collegeName; + + /** + * 专业ID + */ + private Long majorId; + + /** + * 专业名称 + */ + private String majorName; + + /** + * 班级ID + */ + private Long classId; + + /** + * 班级名称 + */ + private String className; + + /** + * 当前校区ID + */ + private Long currentCampusId; + + /** + * 当前校区名称 + */ + private String currentCampusName; + + /** + * 当前宿舍楼ID + */ + private Long currentBuildingId; + + /** + * 当前宿舍楼名称 + */ + private String currentBuildingName; + + /** + * 当前房间ID + */ + private Long currentRoomId; + + /** + * 当前房间号 + */ + private String currentRoomNo; + + /** + * 当前床位ID + */ + private Long currentBedId; + + /** + * 当前床位号 + */ + private String currentBedNo; + + /** + * 目标校区ID + */ + private Long targetCampusId; + + /** + * 目标校区名称 + */ + private String targetCampusName; + + /** + * 目标宿舍楼ID + */ + private Long targetBuildingId; + + /** + * 目标宿舍楼名称 + */ + private String targetBuildingName; + + /** + * 目标房间ID + */ + private Long targetRoomId; + + /** + * 目标房间号 + */ + private String targetRoomNo; + + /** + * 目标床位ID + */ + private Long targetBedId; + + /** + * 目标床位号 + */ + private String targetBedNo; + + /** + * 详细原因说明 + */ + private String reasonDetail; + + /** + * 申请时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date applyTime; + + /** + * 附件OSS文件ID,多个用逗号分隔 + */ + private String attachmentOssIds; + + /** + * 备注信息 + */ + private String remark; + + /** + * 扩展字段1 + */ + private String extField1; + + /** + * 扩展字段2 + */ + private String extField2; + + /** + * 扩展字段3 + */ + private String extField3; + + /** + * 删除标志(0代表存在 1代表删除) + */ + private String deleted; + + /** + * 创建者 + */ + private Long createBy; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** + * 更新者 + */ + private Long updateBy; + + /** + * 更新时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgDormApplicationMapper.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgDormApplicationMapper.java new file mode 100644 index 00000000..1aca4735 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgDormApplicationMapper.java @@ -0,0 +1,29 @@ +package org.dromara.xg.mapper; + +import org.dromara.xg.domain.XgDormApplication; +import org.dromara.xg.domain.vo.XgDormApplicationVo; +import org.dromara.xg.domain.vo.StudentOptionVo; +import org.dromara.xg.domain.bo.StudentOptionBo; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Param; + +/** + * 学生宿舍申请单表(调宿+退宿)Mapper接口 + * + * @author zlwu + * @date 2025-01-15 + */ +public interface XgDormApplicationMapper extends BaseMapperPlus { + + /** + * 查询学生选择器选项列表 + * + * @param page 分页参数 + * @param bo 查询条件 + * @return 学生选择器选项分页列表 + */ + Page selectStudentOptions(IPage page, @Param("bo") StudentOptionBo bo); + +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgDormBedStudentMapper.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgDormBedStudentMapper.java index 7bd4d31d..cc7d3b58 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgDormBedStudentMapper.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgDormBedStudentMapper.java @@ -188,4 +188,95 @@ public interface XgDormBedStudentMapper extends BaseMapperPlus> selectRoomStudents(@Param("roomId") Long roomId); + /** + * 根据学生ID获取当前宿舍信息 + * 包含校区、宿舍楼、房间号、床位号等信息 + * + * @param studentId 学生ID + * @return 当前宿舍信息 + */ + @Select(""" + SELECT + -- 校区信息 + campus.id as campus_id, + campus.campus_name, + + -- 宿舍区信息 + area.id as area_id, + area.area_name, + + -- 楼栋信息 + building.id as building_id, + building.building_name, + building.building_code, + + -- 房间信息 + r.id as room_id, + r.room_name, + r.room_code as room_no, + + -- 床位信息 + b.id as bed_id, + b.bed_name, + b.bed_code as bed_no + + FROM xg_dorm_bed_student bs + INNER JOIN xg_dorm_bed b ON bs.bed_id = b.id AND b.deleted = '0' + INNER JOIN xg_dorm_room r ON b.room_id = r.id AND r.deleted = '0' + INNER JOIN xg_dorm_building building ON r.building_id = building.id AND building.deleted = '0' + INNER JOIN xg_dorm_area area ON building.area_id = area.id AND area.deleted = '0' + INNER JOIN xg_dorm_campus campus ON area.campus_id = campus.id AND campus.deleted = '0' + + WHERE bs.student_id = #{studentId} + AND bs.student_id IS NOT NULL + AND bs.status = '0' + AND bs.deleted = '0' + + LIMIT 1 + """) + Map selectCurrentDormInfo(@Param("studentId") Long studentId); + + /** + * 根据床位ID获取该床位的学生信息 + * 包含学生基本信息、学院、专业、班级等信息 + * + * @param bedId 床位ID + * @return 床位学生信息 + */ + @Select(""" + SELECT + bs.student_id, + bs.bed_id, + + -- 学生基本信息 + s.student_no, + s.name as student_name, + s.gender, + s.phone, + + -- 班级信息 + c.class_name, + + -- 学院信息 + d.dept_name as college_name, + + -- 专业信息 + major.dept_name as major_name + + FROM xg_dorm_bed_student bs + INNER JOIN xg_student_msg s ON bs.student_id = s.id AND s.deleted = 0 + LEFT JOIN xg_class_msg c ON s.class_id = c.id AND c.deleted = 0 + LEFT JOIN sys_dept d ON c.college_id = d.dept_id AND d.del_flag = '0' + LEFT JOIN sys_dept major ON c.major_id = major.dept_id AND major.del_flag = '0' + + WHERE bs.bed_id = #{bedId} + AND bs.student_id IS NOT NULL + AND bs.status = '0' + AND bs.deleted = '0' + AND s.student_status = '1' + + LIMIT 1 + """) + Map selectBedStudentInfo(@Param("bedId") Long bedId); + } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormApplicationService.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormApplicationService.java new file mode 100644 index 00000000..03559e59 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormApplicationService.java @@ -0,0 +1,96 @@ +package org.dromara.xg.service; + +import org.dromara.xg.domain.vo.XgDormApplicationVo; +import org.dromara.xg.domain.bo.XgDormApplicationBo; +import org.dromara.xg.domain.vo.StudentOptionVo; +import org.dromara.xg.domain.bo.StudentOptionBo; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 学生宿舍申请单表(调宿+退宿)Service接口 + * + * @author zlwu + * @date 2025-01-15 + */ +public interface IXgDormApplicationService { + + /** + * 查询学生宿舍申请单表(调宿+退宿) + * + * @param id 学生宿舍申请单表(调宿+退宿)主键 + * @return 学生宿舍申请单表(调宿+退宿) + */ + XgDormApplicationVo queryById(Long id); + + /** + * 分页查询学生宿舍申请单表(调宿+退宿)列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 学生宿舍申请单表(调宿+退宿)分页列表 + */ + TableDataInfo queryPageList(XgDormApplicationBo bo, PageQuery pageQuery); + + /** + * 查询符合条件的学生宿舍申请单表(调宿+退宿)列表 + * + * @param bo 查询条件 + * @return 学生宿舍申请单表(调宿+退宿)列表 + */ + List queryList(XgDormApplicationBo bo); + + /** + * 新增学生宿舍申请单表(调宿+退宿) + * + * @param bo 学生宿舍申请单表(调宿+退宿) + * @return 新增成功的学生宿舍申请单表(调宿+退宿) + */ + XgDormApplicationVo insertByBo(XgDormApplicationBo bo); + + /** + * 修改学生宿舍申请单表(调宿+退宿) + * + * @param bo 学生宿舍申请单表(调宿+退宿) + * @return 是否修改成功 + */ + Boolean updateByBo(XgDormApplicationBo bo); + + /** + * 校验并批量删除学生宿舍申请单表(调宿+退宿)信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 获取学生选择器选项列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 学生选择器选项分页列表 + */ + TableDataInfo getStudentOptions(StudentOptionBo bo, PageQuery pageQuery); + + /** + * 获取二级学院审批人列表 + * + * @param collegeId 学院ID + * @return 审批人列表 + */ + List> getCollegeApprovers(Long collegeId); + + /** + * 获取学工处审批人列表 + * + * @return 审批人列表 + */ + List> getStudentAffairsApprovers(); + +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBedStudentService.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBedStudentService.java index 92c7523c..645faf1d 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBedStudentService.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBedStudentService.java @@ -242,4 +242,22 @@ public interface IXgDormBedStudentService { * @return 房间学生信息列表 */ List getRoomStudents(Long roomId); + + /** + * 根据学生ID获取当前宿舍信息 + * 包含校区、宿舍楼、房间号、床位号等信息 + * + * @param studentId 学生ID + * @return 当前宿舍信息 + */ + Object getCurrentDormInfo(Long studentId); + + /** + * 根据床位ID获取该床位的学生信息 + * 包含学生基本信息、学院、专业、班级等信息 + * + * @param bedId 床位ID + * @return 床位学生信息 + */ + Object getBedStudentInfo(Long bedId); } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormApplicationServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormApplicationServiceImpl.java new file mode 100644 index 00000000..3eaa50f5 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormApplicationServiceImpl.java @@ -0,0 +1,698 @@ +package org.dromara.xg.service.impl; + +import org.dromara.common.core.utils.MapstructUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.dromara.xg.domain.bo.XgDormApplicationBo; +import org.dromara.xg.domain.vo.XgDormApplicationVo; +import org.dromara.xg.domain.vo.StudentOptionVo; +import org.dromara.xg.domain.bo.StudentOptionBo; +import org.dromara.xg.domain.XgDormApplication; +import org.dromara.xg.mapper.XgDormApplicationMapper; +import org.dromara.xg.service.IXgDormApplicationService; +import org.dromara.workflow.common.ConditionalOnEnable; +import org.springframework.context.event.EventListener; +import org.springframework.transaction.annotation.Transactional; +import org.dromara.common.core.domain.event.ProcessTaskEvent; +import org.dromara.common.core.domain.event.ProcessDeleteEvent; +import org.dromara.common.core.domain.event.ProcessEvent; +import org.dromara.common.core.enums.BusinessStatusEnum; +import org.dromara.common.core.service.WorkflowService; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjectUtil; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.system.mapper.SysDeptMapper; +import org.dromara.system.mapper.SysUserMapper; +import org.dromara.system.mapper.SysUserRoleMapper; +import org.dromara.system.mapper.SysRoleMapper; +import org.dromara.system.domain.SysDept; +import org.dromara.system.domain.SysUser; +import org.dromara.system.domain.SysRole; +import org.dromara.xg.mapper.XgStudentManagerMapper; +import org.dromara.xg.mapper.XgStudentManagerSourceMapper; +import org.dromara.xg.mapper.XgClassMsgMapper; +import org.dromara.xg.mapper.XgDormBedStudentMapper; +import org.dromara.xg.service.IXgDormBedStudentService; +import org.dromara.xg.domain.XgStudentManager; +import org.dromara.xg.domain.XgStudentManagerSource; +import org.dromara.xg.domain.XgClassMsg; +import org.dromara.xg.domain.XgDormBedStudent; + +import java.util.List; +import java.util.Map; +import java.util.Collection; +import java.util.ArrayList; +import java.util.Set; +import java.util.HashSet; +import java.util.stream.Collectors; + +/** + * 学生宿舍申请单表(调宿+退宿)Service业务层处理 + * + * @author zlwu + * @date 2025-01-15 + */ +@ConditionalOnEnable +@Slf4j +@RequiredArgsConstructor +@Service +public class XgDormApplicationServiceImpl implements IXgDormApplicationService { + + private final XgDormApplicationMapper baseMapper; + private final WorkflowService workflowService; + private final XgDormBedStudentMapper xgDormBedStudentMapper; + private final IXgDormBedStudentService xgDormBedStudentService; + // 添加权限过滤需要的依赖 + private final SysDeptMapper sysDeptMapper; + private final SysUserMapper sysUserMapper; + private final SysUserRoleMapper sysUserRoleMapper; + private final SysRoleMapper sysRoleMapper; + private final XgStudentManagerMapper xgStudentManagerMapper; + private final XgStudentManagerSourceMapper xgStudentManagerSourceMapper; + private final XgClassMsgMapper xgClassMsgMapper; + + /** + * 查询学生宿舍申请单表(调宿+退宿) + * + * @param id 主键 + * @return 学生宿舍申请单表(调宿+退宿) + */ + @Override + public XgDormApplicationVo queryById(Long id) { + return baseMapper.selectVoById(id); + } + + /** + * 分页查询学生宿舍申请单表(调宿+退宿)列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 学生宿舍申请单表(调宿+退宿)分页列表 + */ + @Override + public TableDataInfo queryPageList(XgDormApplicationBo bo, PageQuery pageQuery) { + // 增加班级数据权限的过滤功能 + // 第一步:调用权限过滤方法,获取过滤后的班级IDs(传入101作为根组织ID) + List filteredDeptIds = filterDeptIdsByPermission(101L); + log.info("权限过滤后的班级ID列表: {}", filteredDeptIds); + + // 第二步:使用带权限过滤的查询条件构建器 + LambdaQueryWrapper lqw = buildQueryWrapperWithPermission(bo, filteredDeptIds); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询符合条件的学生宿舍申请单表(调宿+退宿)列表 + * + * @param bo 查询条件 + * @return 学生宿舍申请单表(调宿+退宿)列表 + */ + @Override + public List queryList(XgDormApplicationBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + /** + * 权限过滤方法 + * 根据当前用户的权限范围过滤部门ID列表 + * + * @param deptId 入参的部门ID + * @return 过滤后的部门ID列表 + */ + private List filterDeptIdsByPermission(Long deptId) { + // 获取当前登录用户ID + Long currentUserId = LoginHelper.getUserId(); + log.info("当前登录用户ID: {}", currentUserId); + + // 第一步:判断当前用户是否为全校管理员 + boolean isSchoolManager = checkIsSchoolManager(currentUserId); + if (isSchoolManager) { + log.info("用户 {} 是全校管理员,不进行权限过滤", currentUserId); + // 全校管理员不进行过滤,返回包含入参deptId及其子组织的列表 + return getDeptIdsWithChildren(deptId); + } + + // 第二步:获取用户的总权限集合 + Set userAuthDeptIds = getUserAuthDeptIds(currentUserId); + log.info("用户 {} 的权限范围: {}", currentUserId, userAuthDeptIds); + + // 第三步:判断入参deptId是否在用户权限范围内 + if (deptId != null && userAuthDeptIds.contains(deptId)) { + log.info("入参deptId {} 在用户权限范围内,不进行过滤", deptId); + // 在权限范围内,返回包含入参deptId及其子组织的列表 + return getDeptIdsWithChildren(deptId); + } else { + log.info("入参deptId {} 不在用户权限范围内,进行权限过滤", deptId); + // 不在权限范围内,过滤deptIds,只保留在权限范围内的 + List filteredDeptIds = new ArrayList<>(); + if (deptId != null) { + // 如果入参deptId不为空,检查其子组织是否在权限范围内 + List deptIdsWithChildren = getDeptIdsWithChildren(deptId); + for (Long deptIdWithChild : deptIdsWithChildren) { + if (userAuthDeptIds.contains(deptIdWithChild)) { + filteredDeptIds.add(deptIdWithChild); + } + } + } else { + // 如果入参deptId为空,直接使用用户权限范围内的所有部门 + filteredDeptIds.addAll(userAuthDeptIds); + } + + // 对过滤后的deptIds,查找所有子组织包括他们自己 + Set finalDeptIds = new HashSet<>(); + for (Long filteredDeptId : filteredDeptIds) { + List deptIdsWithChildren = getDeptIdsWithChildren(filteredDeptId); + finalDeptIds.addAll(deptIdsWithChildren); + } + + return new ArrayList<>(finalDeptIds); + } + } + + /** + * 检查用户是否为全校管理员 + * + * @param userId 用户ID + * @return 是否为全校管理员 + */ + private boolean checkIsSchoolManager(Long userId) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(XgStudentManager::getUserId, userId) + .eq(XgStudentManager::getDeleted, "0") + .eq(XgStudentManager::getStatus, "0") + .eq(XgStudentManager::getManagerType, "1"); + + return xgStudentManagerMapper.selectCount(lqw) > 0; + } + + /** + * 获取用户的总权限集合 + * + * @param userId 用户ID + * @return 用户权限范围内的部门ID集合 + */ + private Set getUserAuthDeptIds(Long userId) { + Set authDeptIds = new HashSet<>(); + + // 第三步:通过xg_student_manager和xg_student_manager_source关联,获取用户的权限范围 + LambdaQueryWrapper managerWrapper = Wrappers.lambdaQuery(); + managerWrapper.eq(XgStudentManager::getUserId, userId) + .eq(XgStudentManager::getDeleted, "0") + .eq(XgStudentManager::getStatus, "0"); + + List managers = xgStudentManagerMapper.selectList(managerWrapper); + + for (XgStudentManager manager : managers) { + LambdaQueryWrapper sourceWrapper = Wrappers.lambdaQuery(); + sourceWrapper.eq(XgStudentManagerSource::getManagerId, manager.getId()) + .eq(XgStudentManagerSource::getDeleted, "0") + .eq(XgStudentManagerSource::getStatus, "0"); + + List sources = xgStudentManagerSourceMapper.selectList(sourceWrapper); + for (XgStudentManagerSource source : sources) { + if (source.getAuthSource() != null) { + authDeptIds.add(source.getAuthSource()); + } + } + } + + // 第四步:通过xg_class_msg表,class_teacher_id=当前登录人id,deleted='0',class_status='1',获取id + LambdaQueryWrapper classWrapper = Wrappers.lambdaQuery(); + classWrapper.eq(XgClassMsg::getClassTeacherId, userId) + .eq(XgClassMsg::getDeleted, 0L) + .eq(XgClassMsg::getClassStatus, "1"); + + List classes = xgClassMsgMapper.selectList(classWrapper); + for (XgClassMsg classMsg : classes) { + if (classMsg.getId() != null) { + authDeptIds.add(classMsg.getId()); + } + } + + return authDeptIds; + } + + /** + * 获取部门ID及其所有子组织的ID列表 + * + * @param deptId 部门ID + * @return 包含部门ID及其子组织的ID列表 + */ + private List getDeptIdsWithChildren(Long deptId) { + List deptIds = new ArrayList<>(); + if (deptId != null) { + deptIds.add(deptId); // 包含自己 + + // 查找所有子组织 + List childDepts = sysDeptMapper.selectListByParentId(deptId); + if (!childDepts.isEmpty()) { + List childDeptIds = childDepts.stream() + .map(SysDept::getDeptId) + .collect(Collectors.toList()); + deptIds.addAll(childDeptIds); + } + } + return deptIds; + } + + /** + * 构建带权限过滤的查询条件包装器 + */ + private LambdaQueryWrapper buildQueryWrapperWithPermission(XgDormApplicationBo bo, + List filteredDeptIds) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.orderByDesc(XgDormApplication::getCreateTime); + + // 获取当前登录用户ID + Long currentUserId = LoginHelper.getUserId(); + + // 添加权限过滤条件:(班级ID必须在权限范围内) OR (是自己创建的申请单) + lqw.and(wrapper -> { + if (filteredDeptIds != null && !filteredDeptIds.isEmpty()) { + wrapper.in(XgDormApplication::getClassId, filteredDeptIds); + } else { + // 如果用户没有任何权限,使用一个不存在的ID + wrapper.eq(XgDormApplication::getClassId, -1L); + } + // OR 用户可以查看自己创建的申请单 + wrapper.or().eq(XgDormApplication::getStudentId, currentUserId); + }); + + // 原有的查询条件 + lqw.like(StringUtils.isNotBlank(bo.getApplicationName()), XgDormApplication::getApplicationName, + bo.getApplicationName()); + lqw.eq(StringUtils.isNotBlank(bo.getApplicationType()), XgDormApplication::getApplicationType, + bo.getApplicationType()); + lqw.eq(StringUtils.isNotBlank(bo.getApplicationStatus()), XgDormApplication::getApplicationStatus, + bo.getApplicationStatus()); + lqw.eq(bo.getStudentId() != null, XgDormApplication::getStudentId, bo.getStudentId()); + lqw.like(StringUtils.isNotBlank(bo.getStudentName()), XgDormApplication::getStudentName, bo.getStudentName()); + lqw.eq(StringUtils.isNotBlank(bo.getStudentNo()), XgDormApplication::getStudentNo, bo.getStudentNo()); + lqw.eq(bo.getClassId() != null, XgDormApplication::getClassId, bo.getClassId()); + lqw.like(StringUtils.isNotBlank(bo.getClassName()), XgDormApplication::getClassName, bo.getClassName()); + lqw.eq(bo.getMajorId() != null, XgDormApplication::getMajorId, bo.getMajorId()); + lqw.like(StringUtils.isNotBlank(bo.getMajorName()), XgDormApplication::getMajorName, bo.getMajorName()); + lqw.eq(bo.getCollegeId() != null, XgDormApplication::getCollegeId, bo.getCollegeId()); + lqw.like(StringUtils.isNotBlank(bo.getCollegeName()), XgDormApplication::getCollegeName, bo.getCollegeName()); + lqw.eq(StringUtils.isNotBlank(bo.getDeleted()), XgDormApplication::getDeleted, bo.getDeleted()); + return lqw; + } + + private LambdaQueryWrapper buildQueryWrapper(XgDormApplicationBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.orderByDesc(XgDormApplication::getCreateTime); + + lqw.like(StringUtils.isNotBlank(bo.getApplicationName()), XgDormApplication::getApplicationName, + bo.getApplicationName()); + lqw.eq(StringUtils.isNotBlank(bo.getApplicationType()), XgDormApplication::getApplicationType, + bo.getApplicationType()); + lqw.eq(StringUtils.isNotBlank(bo.getApplicationStatus()), XgDormApplication::getApplicationStatus, + bo.getApplicationStatus()); + lqw.eq(bo.getStudentId() != null, XgDormApplication::getStudentId, bo.getStudentId()); + lqw.like(StringUtils.isNotBlank(bo.getStudentName()), XgDormApplication::getStudentName, bo.getStudentName()); + lqw.eq(StringUtils.isNotBlank(bo.getStudentNo()), XgDormApplication::getStudentNo, bo.getStudentNo()); + lqw.eq(bo.getClassId() != null, XgDormApplication::getClassId, bo.getClassId()); + lqw.like(StringUtils.isNotBlank(bo.getClassName()), XgDormApplication::getClassName, bo.getClassName()); + lqw.eq(bo.getMajorId() != null, XgDormApplication::getMajorId, bo.getMajorId()); + lqw.like(StringUtils.isNotBlank(bo.getMajorName()), XgDormApplication::getMajorName, bo.getMajorName()); + lqw.eq(bo.getCollegeId() != null, XgDormApplication::getCollegeId, bo.getCollegeId()); + lqw.like(StringUtils.isNotBlank(bo.getCollegeName()), XgDormApplication::getCollegeName, bo.getCollegeName()); + lqw.eq(StringUtils.isNotBlank(bo.getDeleted()), XgDormApplication::getDeleted, bo.getDeleted()); + return lqw; + } + + /** + * 新增学生宿舍申请单表(调宿+退宿) + * + * @param bo 学生宿舍申请单表(调宿+退宿) + * @return 新增成功的学生宿舍申请单表(调宿+退宿) + */ + @Override + public XgDormApplicationVo insertByBo(XgDormApplicationBo bo) { + XgDormApplication add = MapstructUtils.convert(bo, XgDormApplication.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + // 返回新增成功的记录信息 + return MapstructUtils.convert(add, XgDormApplicationVo.class); + } + return null; + } + + /** + * 修改学生宿舍申请单表(调宿+退宿) + * + * @param bo 学生宿舍申请单表(调宿+退宿) + * @return 是否修改成功 + */ + @Override + public Boolean updateByBo(XgDormApplicationBo bo) { + XgDormApplication update = MapstructUtils.convert(bo, XgDormApplication.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(XgDormApplication entity) { + // TODO 做一些数据校验,如唯一约束 + } + + /** + * 校验并批量删除学生宿舍申请单表(调宿+退宿)信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if (isValid) { + // TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteByIds(ids) > 0; + } + + /** + * 总体流程监听(例如: 草稿,撤销,退回,作废,终止,已完成,单任务完成等) + * 正常使用只需#processEvent.flowCode=='hzdyjs-tsts' + * + * @param processEvent 参数 + */ + @EventListener(condition = "#processEvent.flowCode=='hzdyjs-tsts'") + public void processHandler(ProcessEvent processEvent) { + log.info("当前任务执行了{}", processEvent.toString()); + XgDormApplication xgDormApplication = baseMapper.selectById(Long.valueOf(processEvent.getBusinessId())); + xgDormApplication.setApplicationStatus(processEvent.getStatus()); + + // 用于例如审批附件 审批意见等 存储到业务表内 自行根据业务实现存储流程 + Map params = processEvent.getParams(); + if (MapUtil.isNotEmpty(params)) { + // 历史任务扩展(通常为附件) + String hisTaskExt = Convert.toStr(params.get("hisTaskExt")); + // 办理人 + String handler = Convert.toStr(params.get("handler")); + // 办理意见 + String message = Convert.toStr(params.get("message")); + } + + if (processEvent.getSubmit()) { + xgDormApplication.setApplicationStatus(BusinessStatusEnum.WAITING.getStatus()); + } + + // 当审批完成时,执行调宿/退宿操作 + if ("finish".equals(processEvent.getStatus())) { + executeApplicationOperation(xgDormApplication); + } + + baseMapper.updateById(xgDormApplication); + } + + /** + * 执行调宿/退宿操作 + * + * @param application 申请单 + */ + private void executeApplicationOperation(XgDormApplication application) { + try { + if ("1".equals(application.getApplicationType())) { + // 调宿申请 - 区分两种场景处理 + log.info("执行调宿操作: 学生ID={}, 目标床位ID={}", application.getStudentId(), application.getTargetBedId()); + + // 1. 根据目标床位ID查找当前占用该床位的学生ID + Long targetStudentId = findStudentByBedId(application.getTargetBedId()); + + Boolean operationResult = false; + + if (targetStudentId != null) { + // 场景1:目标床位有学生 - 两个学生互换床位 + log.info("目标床位已有学生,执行床位互换: 申请学生ID={}, 目标学生ID={}", + application.getStudentId(), targetStudentId); + + operationResult = xgDormBedStudentService.classTeacherExchangeBeds( + application.getStudentId(), // 源学生ID(申请人) + targetStudentId, // 目标学生ID(目标床位当前占用者) + application.getClassId() // 班级ID + ); + + if (operationResult) { + log.info("床位互换操作执行成功: 申请学生ID={}, 目标学生ID={}, 班级ID={}", + application.getStudentId(), targetStudentId, application.getClassId()); + } else { + log.error("床位互换操作执行失败: 申请学生ID={}, 目标学生ID={}, 班级ID={}", + application.getStudentId(), targetStudentId, application.getClassId()); + throw new RuntimeException("床位互换操作执行失败"); + } + + } else { + // 场景2:目标床位为空 - 学生移动到新床位 + log.info("目标床位为空,执行床位移动: 申请学生ID={}, 目标床位ID={}", + application.getStudentId(), application.getTargetBedId()); + + operationResult = xgDormBedStudentService.classTeacherMoveBed( + application.getStudentId(), // 源学生ID(申请人) + application.getTargetBedId(), // 目标床位ID + application.getClassId() // 班级ID + ); + + if (operationResult) { + log.info("床位移动操作执行成功: 申请学生ID={}, 目标床位ID={}, 班级ID={}", + application.getStudentId(), application.getTargetBedId(), application.getClassId()); + } else { + log.error("床位移动操作执行失败: 申请学生ID={}, 目标床位ID={}, 班级ID={}", + application.getStudentId(), application.getTargetBedId(), application.getClassId()); + throw new RuntimeException("床位移动操作执行失败"); + } + } + + } else if ("2".equals(application.getApplicationType())) { + // 退宿申请 - 调用退宿接口 + log.info("执行退宿操作: 学生ID={}, 当前床位ID={}", application.getStudentId(), application.getCurrentBedId()); + + // 构建退宿参数并调用接口 + List bedIds = List.of(application.getCurrentBedId()); + Boolean stayawayResult = xgDormBedStudentService.classTeacherStayawaySelectedBeds( + bedIds, // 床位ID列表 + application.getClassId() // 班级ID + ); + + if (stayawayResult) { + log.info("退宿操作执行成功: 学生ID={}, 床位ID={}, 班级ID={}", + application.getStudentId(), application.getCurrentBedId(), application.getClassId()); + } else { + log.error("退宿操作执行失败: 学生ID={}, 床位ID={}, 班级ID={}", + application.getStudentId(), application.getCurrentBedId(), application.getClassId()); + throw new RuntimeException("退宿操作执行失败"); + } + } else { + log.error("未知的申请类型: {}", application.getApplicationType()); + throw new RuntimeException("未知的申请类型: " + application.getApplicationType()); + } + + log.info("宿舍申请操作执行成功,申请ID: {}", application.getId()); + + } catch (Exception e) { + log.error("宿舍申请操作执行失败,申请ID: {}, 错误信息: {}", application.getId(), e.getMessage(), e); + // 抛出异常,让调用方处理 + throw new RuntimeException("宿舍申请操作执行失败: " + e.getMessage(), e); + } + } + + /** + * 根据床位ID查找当前占用该床位的学生ID + * + * @param bedId 床位ID + * @return 学生ID,如果没有找到则返回null + */ + private Long findStudentByBedId(Long bedId) { + if (bedId == null) { + return null; + } + try { + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); + queryWrapper.eq(XgDormBedStudent::getBedId, bedId) + .eq(XgDormBedStudent::getStatus, "0") // 0-正常状态 + .eq(XgDormBedStudent::getDeleted, 0L); // 未删除 + + XgDormBedStudent bedStudent = xgDormBedStudentMapper.selectOne(queryWrapper); + if (bedStudent != null) { + log.info("根据床位ID: {} 找到学生ID: {}", bedId, bedStudent.getStudentId()); + return bedStudent.getStudentId(); + } else { + log.warn("床位ID: {} 没有找到对应的学生", bedId); + return null; + } + } catch (Exception e) { + log.error("根据床位ID: {} 查找学生失败: {}", bedId, e.getMessage(), e); + return null; + } + } + + /** + * 执行任务创建监听 + * 示例:也可通过 @EventListener(condition = + * "#processTaskEvent.flowCode=='hzdyjs-tsts'")进行判断 + * 在方法中判断流程节点key + * if ("xxx".equals(processTaskEvent.getNodeCode())) { + * //执行业务逻辑 + * } + * + * @param processTaskEvent 参数 + */ + @EventListener(condition = "#processTaskEvent.flowCode=='hzdyjs-tsts'") + public void processTaskHandler(ProcessTaskEvent processTaskEvent) { + log.info("当前任务创建了{}", processTaskEvent.toString()); + } + + /** + * 监听删除流程事件 + * 正常使用只需#processDeleteEvent.flowCode=='hzdyjs-tsts' + * + * @param processDeleteEvent 参数 + */ + @EventListener(condition = "#processDeleteEvent.flowCode=='hzdyjs-tsts'") + public void processDeleteHandler(ProcessDeleteEvent processDeleteEvent) { + log.info("监听删除流程事件,当前任务执行了{}", processDeleteEvent.toString()); + XgDormApplication xgDormApplication = baseMapper.selectById(Long.valueOf(processDeleteEvent.getBusinessId())); + if (ObjectUtil.isNull(xgDormApplication)) { + return; + } + baseMapper.deleteById(xgDormApplication.getId()); + } + + /** + * 获取学生选择器选项列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 学生选择器选项分页列表 + */ + @Override + public TableDataInfo getStudentOptions(StudentOptionBo bo, PageQuery pageQuery) { + Page result = baseMapper.selectStudentOptions(pageQuery.build(), bo); + return TableDataInfo.build(result); + } + + /** + * 获取二级学院审批人列表 + * + * @param collegeId 学院ID + * @return 审批人列表 + */ + @Override + public List> getCollegeApprovers(Long collegeId) { + try { + // 查询角色key为'ejxyssgly'的角色ID + LambdaQueryWrapper roleWrapper = new LambdaQueryWrapper<>(); + roleWrapper.eq(SysRole::getRoleKey, "ejxyssgly"); + SysRole role = sysRoleMapper.selectOne(roleWrapper); + + if (role == null) { + log.warn("未找到角色key为'ejxyssgly'的角色"); + return new ArrayList<>(); + } + + // 通过角色ID查询用户角色关系表 + LambdaQueryWrapper userRoleWrapper = new LambdaQueryWrapper<>(); + userRoleWrapper.eq(org.dromara.system.domain.SysUserRole::getRoleId, role.getRoleId()); + List userRoles = sysUserRoleMapper.selectList(userRoleWrapper); + + if (userRoles.isEmpty()) { + return new ArrayList<>(); + } + + // 获取用户ID列表 + List userIds = userRoles.stream() + .map(org.dromara.system.domain.SysUserRole::getUserId) + .collect(Collectors.toList()); + + // 查询用户信息,过滤指定学院的用户 + LambdaQueryWrapper userWrapper = new LambdaQueryWrapper<>(); + userWrapper.in(SysUser::getUserId, userIds) + .eq(SysUser::getDeptId, collegeId) + .eq(SysUser::getStatus, "0"); // 只查询启用状态的用户 + List users = sysUserMapper.selectList(userWrapper); + + // 转换为Map格式返回 + return users.stream().map(user -> { + Map approver = MapUtil.newHashMap(); + approver.put("userId", user.getUserId()); + approver.put("userName", user.getUserName()); + approver.put("nickName", user.getNickName()); + approver.put("deptId", user.getDeptId()); + return approver; + }).collect(Collectors.toList()); + } catch (Exception e) { + log.error("获取二级学院审批人列表失败", e); + return new ArrayList<>(); + } + } + + /** + * 获取学工处审批人列表 + * + * @return 审批人列表 + */ + @Override + public List> getStudentAffairsApprovers() { + try { + // 查询角色key为'xscxsxxgly'的角色ID + LambdaQueryWrapper roleWrapper = new LambdaQueryWrapper<>(); + roleWrapper.eq(SysRole::getRoleKey, "xscxsxxgly"); + SysRole role = sysRoleMapper.selectOne(roleWrapper); + + if (role == null) { + log.warn("未找到角色key为'xscxsxxgly'的角色"); + return new ArrayList<>(); + } + + // 通过角色ID查询用户角色关系表 + LambdaQueryWrapper userRoleWrapper = new LambdaQueryWrapper<>(); + userRoleWrapper.eq(org.dromara.system.domain.SysUserRole::getRoleId, role.getRoleId()); + List userRoles = sysUserRoleMapper.selectList(userRoleWrapper); + + if (userRoles.isEmpty()) { + return new ArrayList<>(); + } + + // 获取用户ID列表 + List userIds = userRoles.stream() + .map(org.dromara.system.domain.SysUserRole::getUserId) + .collect(Collectors.toList()); + + // 查询用户信息 + LambdaQueryWrapper userWrapper = new LambdaQueryWrapper<>(); + userWrapper.in(SysUser::getUserId, userIds) + .eq(SysUser::getStatus, "0"); // 只查询启用状态的用户 + List users = sysUserMapper.selectList(userWrapper); + + // 转换为Map格式返回 + return users.stream().map(user -> { + Map approver = MapUtil.newHashMap(); + approver.put("userId", user.getUserId()); + approver.put("userName", user.getUserName()); + approver.put("nickName", user.getNickName()); + approver.put("deptId", user.getDeptId()); + return approver; + }).collect(Collectors.toList()); + } catch (Exception e) { + log.error("获取学工处审批人列表失败", e); + return new ArrayList<>(); + } + } + +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java index fa0226b2..397d2ef9 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java @@ -2500,4 +2500,131 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { } } + /** + * 根据学生ID获取当前宿舍信息 + * 包含校区、宿舍楼、房间号、床位号等信息 + * + * @param studentId 学生ID + * @return 当前宿舍信息 + */ + @Override + public Object getCurrentDormInfo(Long studentId) { + try { + log.info("开始获取学生当前宿舍信息,学生ID: {}", studentId); + + if (studentId == null) { + log.warn("学生ID为空,无法获取宿舍信息"); + return null; + } + + // 使用SQL关联查询,获取学生当前宿舍信息 + Map dormInfo = baseMapper.selectCurrentDormInfo(studentId); + + if (dormInfo == null || dormInfo.isEmpty()) { + log.warn("学生ID: {} 未找到宿舍分配信息", studentId); + return null; + } + + // 组装返回结果 + Map result = new LinkedHashMap<>(); + + // 校区信息 + result.put("campusId", dormInfo.get("campus_id")); + result.put("campusName", dormInfo.get("campus_name")); + + // 宿舍区信息 + result.put("areaId", dormInfo.get("area_id")); + result.put("areaName", dormInfo.get("area_name")); + + // 楼栋信息 + result.put("buildingId", dormInfo.get("building_id")); + result.put("buildingName", dormInfo.get("building_name")); + result.put("buildingCode", dormInfo.get("building_code")); + + // 房间信息 + result.put("roomId", dormInfo.get("room_id")); + result.put("roomName", dormInfo.get("room_name")); + result.put("roomNo", dormInfo.get("room_no")); + result.put("roomNumber", dormInfo.get("room_no")); // 兼容字段 + + // 床位信息 + result.put("bedId", dormInfo.get("bed_id")); + result.put("bedName", dormInfo.get("bed_name")); + result.put("bedNo", dormInfo.get("bed_no")); + result.put("bedNumber", dormInfo.get("bed_no")); // 兼容字段 + + log.info("获取学生当前宿舍信息成功,学生ID: {}, 宿舍信息: {}校区-{}楼-{}房间-{}床位", + studentId, + result.get("campusName"), + result.get("buildingName"), + result.get("roomNo"), + result.get("bedNo")); + + return result; + + } catch (Exception e) { + log.error("获取学生当前宿舍信息失败,学生ID: {}", studentId, e); + return null; + } + } + + /** + * 根据床位ID获取该床位的学生信息 + * 包含学生基本信息、学院、专业、班级等信息 + * + * @param bedId 床位ID + * @return 床位学生信息 + */ + @Override + public Object getBedStudentInfo(Long bedId) { + try { + log.info("开始获取床位学生信息,床位ID: {}", bedId); + + if (bedId == null) { + log.warn("床位ID为空,无法获取学生信息"); + return null; + } + + // 使用SQL关联查询,获取床位学生信息 + Map studentInfo = baseMapper.selectBedStudentInfo(bedId); + + if (studentInfo == null || studentInfo.isEmpty()) { + log.warn("床位ID: {} 未找到学生信息", bedId); + return null; + } + + // 组装返回结果 + Map result = new LinkedHashMap<>(); + + // 学生基本信息 + result.put("studentId", studentInfo.get("student_id")); + result.put("studentNo", studentInfo.get("student_no")); + result.put("studentName", studentInfo.get("student_name")); + result.put("gender", studentInfo.get("gender")); + result.put("phone", studentInfo.get("phone")); + + // 学院、专业、班级信息 + result.put("collegeName", studentInfo.get("college_name")); + result.put("majorName", studentInfo.get("major_name")); + result.put("className", studentInfo.get("class_name")); + + // 床位信息 + result.put("bedId", studentInfo.get("bed_id")); + + log.info("获取床位学生信息成功,床位ID: {}, 学生信息: {}({})-{}-{}-{}", + bedId, + result.get("studentName"), + result.get("studentNo"), + result.get("collegeName"), + result.get("majorName"), + result.get("className")); + + return result; + + } catch (Exception e) { + log.error("获取床位学生信息失败,床位ID: {}", bedId, e); + return null; + } + } + } diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/controller/XljkConsultationAppointmentController.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/controller/XljkConsultationAppointmentController.java index 46130a4a..9bd7a300 100644 --- a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/controller/XljkConsultationAppointmentController.java +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/controller/XljkConsultationAppointmentController.java @@ -6,6 +6,7 @@ import org.dromara.common.web.core.BaseController; import org.dromara.xljk.domain.bo.XljkConsultationAppointmentBo; import org.dromara.xljk.domain.vo.XljkConsultationAppointmentVo; import org.dromara.xljk.domain.vo.TimeSlotDetailVo; +import org.dromara.xljk.domain.vo.ScheduleWithStatusVo; import org.dromara.xljk.service.IXljkConsultationAppointmentService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.format.annotation.DateTimeFormat; @@ -161,52 +162,10 @@ public class XljkConsultationAppointmentController extends BaseController { // 从请求体中获取参数 String status = requestBody.get("status").toString(); - Date actualVisitTime = null; Integer communicationDuration = null; String consultationContent = ""; String counselorSuggestion = ""; - // 处理actualVisitTime - if (requestBody.get("actualVisitTime") != null) { - try { - String actualVisitTimeStr = requestBody.get("actualVisitTime").toString(); - log.info("解析实际到访时间字符串: {}", actualVisitTimeStr); - - // 尝试多种日期格式 - SimpleDateFormat[] dateFormats = { - new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), - new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"), - new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"), - new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"), - new SimpleDateFormat("yyyy-MM-dd") - }; - - boolean parsed = false; - for (SimpleDateFormat sdf : dateFormats) { - try { - actualVisitTime = sdf.parse(actualVisitTimeStr); - parsed = true; - log.info("成功解析实际到访时间: {}, 使用格式: {}", actualVisitTime, sdf.toPattern()); - break; - } catch (Exception e) { - // 继续尝试下一个格式 - } - } - - if (!parsed) { - log.error("无法解析实际到访时间: {}", actualVisitTimeStr); - // 使用当前时间作为默认值 - actualVisitTime = new Date(); - log.info("使用当前时间作为默认值: {}", actualVisitTime); - } - } catch (Exception e) { - log.error("解析actualVisitTime失败", e); - // 使用当前时间作为默认值 - actualVisitTime = new Date(); - log.info("使用当前时间作为默认值: {}", actualVisitTime); - } - } - // 处理communicationDuration if (requestBody.get("communicationDuration") != null) { try { @@ -227,8 +186,47 @@ public class XljkConsultationAppointmentController extends BaseController { counselorSuggestion = requestBody.get("counselorSuggestion").toString(); } - boolean result = service.recordConsultation(id, actualVisitTime, communicationDuration, - consultationContent, counselorSuggestion, status); + // 处理心理病史相关字段 + Integer psychologicalHistory = null; + String psychologicalHistoryDetail = ""; + String basicSituation = ""; + String majorLifeEvents = ""; + Integer needReferral = null; + + if (requestBody.get("psychologicalHistory") != null) { + try { + psychologicalHistory = Integer.parseInt(requestBody.get("psychologicalHistory").toString()); + } catch (Exception e) { + log.error("解析psychologicalHistory失败", e); + } + } + + if (requestBody.get("psychologicalHistoryDetail") != null) { + psychologicalHistoryDetail = requestBody.get("psychologicalHistoryDetail").toString(); + } + + if (requestBody.get("basicSituation") != null) { + basicSituation = requestBody.get("basicSituation").toString(); + } + + if (requestBody.get("majorLifeEvents") != null) { + majorLifeEvents = requestBody.get("majorLifeEvents").toString(); + } + + if (requestBody.get("needReferral") != null) { + try { + needReferral = Integer.parseInt(requestBody.get("needReferral").toString()); + } catch (Exception e) { + log.error("解析needReferral失败", e); + } + } + + log.info("解析的心理病史相关字段: psychologicalHistory={}, psychologicalHistoryDetail={}, basicSituation={}, majorLifeEvents={}, needReferral={}", + psychologicalHistory, psychologicalHistoryDetail, basicSituation, majorLifeEvents, needReferral); + + boolean result = service.recordConsultation(id, communicationDuration, + consultationContent, counselorSuggestion, status, + psychologicalHistory, psychologicalHistoryDetail, basicSituation, majorLifeEvents, needReferral); if (result) { return R.ok(true); } else { @@ -284,7 +282,7 @@ public class XljkConsultationAppointmentController extends BaseController { XSSFSheet sheet = workbook.createSheet("预约记录"); // 表头 - String[] headers = {"序号", "学生姓名", "学号", "院系", "专业", "班级", "咨询原因", "咨询师", "预约日期", "预约时段", "状态", "咨询结果", "实际到访时间", "沟通时长", "咨询内容", "咨询师建议"}; + String[] headers = {"序号", "学生姓名", "学号", "院系", "专业", "班级", "咨询主题", "咨询原因", "咨询师", "预约日期", "预约时段", "状态", "咨询结果", "沟通时长", "心理病史", "心理病史详情", "基本情况", "重大生活事件", "本次咨询过程", "是否需要转介", "本学期到访次数"}; XSSFRow headerRow = sheet.createRow(0); for (int i = 0; i < headers.length; i++) { headerRow.createCell(i).setCellValue(headers[i]); @@ -303,6 +301,7 @@ public class XljkConsultationAppointmentController extends BaseController { row.createCell(col++).setCellValue(vo.getCollegeName()); row.createCell(col++).setCellValue(vo.getMajorName()); row.createCell(col++).setCellValue(vo.getClassName()); + row.createCell(col++).setCellValue(vo.getConsultTheme() != null ? vo.getConsultTheme() : ""); // 咨询主题 row.createCell(col++).setCellValue(vo.getReason()); row.createCell(col++).setCellValue(vo.getCounselorName()); row.createCell(col++).setCellValue(vo.getAppointmentDate() != null ? dateFormat.format(vo.getAppointmentDate()) : ""); @@ -315,15 +314,43 @@ public class XljkConsultationAppointmentController extends BaseController { case 1: status = "已到访"; break; case 2: status = "已取消"; break; case 3: status = "未到访"; break; + case 4: status = "待审核"; break; + case 5: status = "已驳回"; break; default: status = "未知状态"; } } row.createCell(col++).setCellValue(status); row.createCell(col++).setCellValue(status); // 咨询结果 - row.createCell(col++).setCellValue(vo.getActualVisitTime() != null ? dateTimeFormat.format(vo.getActualVisitTime()) : ""); - row.createCell(col++).setCellValue(vo.getCommunicationDuration() != null ? vo.getCommunicationDuration().toString() : ""); - row.createCell(col++).setCellValue(vo.getConsultationContent()); - row.createCell(col++).setCellValue(vo.getCounselorSuggestion()); + row.createCell(col++).setCellValue(vo.getCommunicationDuration() != null ? vo.getCommunicationDuration().toString() + "分钟" : ""); + + // 心理病史 + String psychologicalHistory = ""; + if (vo.getPsychologicalHistory() != null) { + psychologicalHistory = vo.getPsychologicalHistory() == 2 ? "是" : "否"; + } + row.createCell(col++).setCellValue(psychologicalHistory); + + // 心理病史详情 + row.createCell(col++).setCellValue(vo.getPsychologicalHistoryDetail() != null ? vo.getPsychologicalHistoryDetail() : ""); + + // 基本情况 + row.createCell(col++).setCellValue(vo.getBasicSituation() != null ? vo.getBasicSituation() : ""); + + // 重大生活事件 + row.createCell(col++).setCellValue(vo.getMajorLifeEvents() != null ? vo.getMajorLifeEvents() : ""); + + // 本次咨询过程(原咨询内容) + row.createCell(col++).setCellValue(vo.getConsultationContent() != null ? vo.getConsultationContent() : ""); + + // 是否需要转介 + String needReferral = ""; + if (vo.getNeedReferral() != null) { + needReferral = vo.getNeedReferral() == 2 ? "是" : "否"; + } + row.createCell(col++).setCellValue(needReferral); + + // 本学期到访次数 + row.createCell(col++).setCellValue(vo.getSemesterVisitCount() != null ? vo.getSemesterVisitCount().toString() + "次" : "0次"); } // 写入字节流 @@ -350,6 +377,15 @@ public class XljkConsultationAppointmentController extends BaseController { .body(data); } + /** + * 获取日历视图的排班数据(带预约状态) + */ + @GetMapping("/calendar-schedules") + public R> getCalendarSchedules(@RequestParam String startDate, @RequestParam String endDate) { + List schedules = service.getCalendarSchedules(startDate, endDate); + return R.ok(schedules); + } + /** * 转义CSV字段中的特殊字符 */ diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/controller/XljkConsultationScheduleController.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/controller/XljkConsultationScheduleController.java index 12c3f6f1..5d3cc5be 100644 --- a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/controller/XljkConsultationScheduleController.java +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/controller/XljkConsultationScheduleController.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.dromara.xljk.domain.vo.XljkConsultationScheduleSummaryVo; @RestController @@ -57,10 +58,14 @@ public class XljkConsultationScheduleController { batchBo.setCounselorId(Long.valueOf(requestMap.get("counselorId").toString())); } + if (requestMap.containsKey("office")) { + batchBo.setOffice(requestMap.get("office").toString()); + } + batchBo.setStartDate(requestMap.get("startDate").toString()); batchBo.setEndDate(requestMap.get("endDate").toString()); - // 设置上午时段 + // 设置中午时段(字段名保持morning以兼容前端) if (requestMap.containsKey("morningEnabled")) { batchBo.setMorningEnabled(Boolean.valueOf(requestMap.get("morningEnabled").toString())); } @@ -73,6 +78,26 @@ public class XljkConsultationScheduleController { batchBo.setMorningEndTime(requestMap.get("morningEndTime").toString()); } + // 设置中午时段(新版本) + if (requestMap.containsKey("noonEnabled")) { + batchBo.setNoonEnabled(Boolean.valueOf(requestMap.get("noonEnabled").toString())); + } + + if (requestMap.containsKey("noonTimeSlots")) { + List> noonSlots = (List>) requestMap.get("noonTimeSlots"); + if (noonSlots != null) { + List timeSlots = noonSlots.stream() + .map(slot -> { + XljkConsultationScheduleBatchBo.TimeSlot timeSlot = new XljkConsultationScheduleBatchBo.TimeSlot(); + timeSlot.setStartTime(slot.get("startTime").toString()); + timeSlot.setEndTime(slot.get("endTime").toString()); + return timeSlot; + }) + .collect(Collectors.toList()); + batchBo.setNoonTimeSlots(timeSlots); + } + } + // 设置下午时段 if (requestMap.containsKey("afternoonEnabled")) { batchBo.setAfternoonEnabled(Boolean.valueOf(requestMap.get("afternoonEnabled").toString())); @@ -86,6 +111,21 @@ public class XljkConsultationScheduleController { batchBo.setAfternoonEndTime(requestMap.get("afternoonEndTime").toString()); } + if (requestMap.containsKey("afternoonTimeSlots")) { + List> afternoonSlots = (List>) requestMap.get("afternoonTimeSlots"); + if (afternoonSlots != null) { + List timeSlots = afternoonSlots.stream() + .map(slot -> { + XljkConsultationScheduleBatchBo.TimeSlot timeSlot = new XljkConsultationScheduleBatchBo.TimeSlot(); + timeSlot.setStartTime(slot.get("startTime").toString()); + timeSlot.setEndTime(slot.get("endTime").toString()); + return timeSlot; + }) + .collect(Collectors.toList()); + batchBo.setAfternoonTimeSlots(timeSlots); + } + } + // 设置其他字段 if (requestMap.containsKey("status")) { batchBo.setStatus(requestMap.get("status").toString()); @@ -166,10 +206,13 @@ public class XljkConsultationScheduleController { */ @GetMapping("/options/counselors") public R>> getCounselorOptions() { - String sql = "SELECT u.user_id as id, u.nick_name as name FROM sys_user u " + + String sql = "SELECT u.user_id as id, u.nick_name as name, c.office as office " + + "FROM sys_user u " + "JOIN sys_user_role ur ON u.user_id = ur.user_id " + "JOIN sys_role r ON ur.role_id = r.role_id " + - "WHERE r.role_key = 'xljk_consultant' AND u.del_flag = '0'"; + "LEFT JOIN xljk_consulter c ON u.user_id = c.user_id AND c.deleted = 0 " + + "WHERE r.role_key = 'xljk_consultant' AND u.del_flag = '0' " + + "ORDER BY u.nick_name"; List> counselorOptions = jdbcTemplate.queryForList(sql); return R.ok(counselorOptions); diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/controller/XljkConsulterController.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/controller/XljkConsulterController.java new file mode 100644 index 00000000..c45e75da --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/controller/XljkConsulterController.java @@ -0,0 +1,179 @@ +package org.dromara.xljk.controller; + +import java.util.List; + +import lombok.RequiredArgsConstructor; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.*; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.web.core.BaseController; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.xljk.domain.vo.XljkConsulterVo; +import org.dromara.xljk.domain.bo.XljkConsulterBo; +import org.dromara.xljk.service.IXljkConsulterService; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.system.service.ISysUserService; +import org.dromara.system.domain.vo.SysUserVo; + +/** + * 心理咨询师配置 + * + * @author ruoyi + * @date 2024-01-01 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/xljk/consulter") +public class XljkConsulterController extends BaseController { + + private final IXljkConsulterService xljkConsulterService; + private final ISysUserService sysUserService; + + /** + * 查询心理咨询师配置列表 + */ + @SaCheckPermission("xljk:consulter:list") + @GetMapping("/list") + public TableDataInfo list(XljkConsulterBo bo, PageQuery pageQuery) { + return xljkConsulterService.queryPageList(bo, pageQuery); + } + + /** + * 导出心理咨询师配置列表 + */ + @SaCheckPermission("xljk:consulter:export") + @Log(title = "心理咨询师配置", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(XljkConsulterBo bo, HttpServletResponse response) { + List list = xljkConsulterService.queryList(bo); + ExcelUtil.exportExcel(list, "心理咨询师配置", XljkConsulterVo.class, response); + } + + /** + * 获取心理咨询师配置详细信息 + */ + @SaCheckPermission("xljk:consulter:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(xljkConsulterService.queryById(id)); + } + + /** + * 新增心理咨询师配置 + */ + @SaCheckPermission("xljk:consulter:add") + @Log(title = "心理咨询师配置", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody XljkConsulterBo bo) { + return toAjax(xljkConsulterService.insertByBo(bo)); + } + + /** + * 修改心理咨询师配置 + */ + @SaCheckPermission("xljk:consulter:edit") + @Log(title = "心理咨询师配置", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody XljkConsulterBo bo) { + return toAjax(xljkConsulterService.updateByBo(bo)); + } + + /** + * 删除心理咨询师配置 + */ + @SaCheckPermission("xljk:consulter:remove") + @Log(title = "心理咨询师配置", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(xljkConsulterService.deleteWithValidByIds(List.of(ids))); + } + + /** + * 批量添加咨询师 + */ + @SaCheckPermission("xljk:consulter:add") + @Log(title = "批量添加咨询师", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/batchAdd") + public R batchAdd(@RequestBody BatchAddRequest request) { + return toAjax(xljkConsulterService.batchAddConsulters(request.getUserIds(), request.getRemark())); + } + + /** + * 查询我配置的咨询师列表 + */ + @SaCheckPermission("xljk:consulter:list") + @GetMapping("/myConfig") + public TableDataInfo myConfigList(XljkConsulterBo bo, PageQuery pageQuery) { + return xljkConsulterService.queryMyConfigList(bo, pageQuery); + } + + /** + * 批量启用咨询师 + */ + @SaCheckPermission("xljk:consulter:edit") + @Log(title = "启用咨询师", businessType = BusinessType.UPDATE) + @PostMapping("/enable") + public R enable(@RequestBody List ids) { + return toAjax(xljkConsulterService.enableConsulters(ids)); + } + + /** + * 批量停用咨询师 + */ + @SaCheckPermission("xljk:consulter:edit") + @Log(title = "停用咨询师", businessType = BusinessType.UPDATE) + @PostMapping("/disable") + public R disable(@RequestBody List ids) { + return toAjax(xljkConsulterService.disableConsulters(ids)); + } + + /** + * 根据部门ID获取用户列表 + */ + @SaCheckPermission("xljk:consulter:list") + @GetMapping("/users/{deptId}") + public R> listUserByDeptId(@PathVariable String deptId) { + List users = sysUserService.selectUserListByDept(Long.valueOf(deptId)); + return R.ok(users); + } + + /** + * 批量添加请求对象 + */ + public static class BatchAddRequest { + private List userIds; + private String remark; + + public List getUserIds() { + return userIds; + } + + public void setUserIds(List userIds) { + this.userIds = userIds; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + } + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/XljkConsultationAppointment.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/XljkConsultationAppointment.java index c6deca93..216b5108 100644 --- a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/XljkConsultationAppointment.java +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/XljkConsultationAppointment.java @@ -3,6 +3,7 @@ package org.dromara.xljk.domain; import java.util.Date; import java.io.Serializable; import lombok.Data; +import com.baomidou.mybatisplus.annotation.TableField; @Data public class XljkConsultationAppointment implements Serializable { @@ -21,9 +22,20 @@ public class XljkConsultationAppointment implements Serializable { private String appointmentTime; private String appointmentDetailTime; // 具体预约时间段,如"08:30-09:00" private String reason; + + @TableField("consult_theme") + private String consultTheme; // 咨询主题 + private String status; - private Date actualVisitTime; private Integer communicationDuration; private String consultationContent; private String counselorSuggestion; + + // 新增心理健康相关字段 + private String studentName; // 学生姓名(冗余字段) + private Integer psychologicalHistory; // 心理病史(1-否,2-是) + private String psychologicalHistoryDetail; // 心理病史详情 + private String basicSituation; // 基本情况 + private String majorLifeEvents; // 重大生活事件 + private Integer needReferral; // 是否需要转介(1-不需要,2-需要) } \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/XljkConsultationSchedule.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/XljkConsultationSchedule.java index 165f98b0..2350471a 100644 --- a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/XljkConsultationSchedule.java +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/XljkConsultationSchedule.java @@ -15,8 +15,9 @@ public class XljkConsultationSchedule implements Serializable { private Date updateTime; private Integer deleted; private Long counselorId; + private String office; // 办公室 private Date scheduleDate; - private String timeSlot; // 0上午 1下午 + private String timeSlot; // 0中午 1下午 private String startTime; // 开始时间 HH:mm 格式 private String endTime; // 结束时间 HH:mm 格式 private String status; // 0正常 1停用 diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/XljkConsulter.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/XljkConsulter.java new file mode 100644 index 00000000..3936187f --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/XljkConsulter.java @@ -0,0 +1,97 @@ +package org.dromara.xljk.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.tenant.core.TenantEntity; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 心理咨询师配置对象 xljk_consulter + * + * @author ruoyi + * @date 2024-01-01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("xljk_consulter") +public class XljkConsulter extends TenantEntity implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id") + private Long id; + + /** + * 用户ID + */ + private Long userId; + + /** + * 用户姓名 + */ + private String userName; + + /** + * 用户昵称 + */ + private String nickName; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 联系电话 + */ + private String phoneNumber; + + /** + * 办公室 + */ + private String office; + + /** + * 邮箱地址 + */ + private String email; + + /** + * 状态(1正常 0停用) + */ + private String status; + + /** + * 备注说明 + */ + private String remark; + + /** + * 配置人ID + */ + private Long configBy; + + /** + * 配置人姓名 + */ + private String configByName; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + private Integer deleted; + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/XljkUserWithDeptInfo.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/XljkUserWithDeptInfo.java new file mode 100644 index 00000000..d76a0768 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/XljkUserWithDeptInfo.java @@ -0,0 +1,39 @@ +package org.dromara.xljk.domain; + +import lombok.Data; + +/** + * 心理咨询用户和部门完整信息 + * 用于批量添加咨询师时获取完整的用户数据 + * + * @author ruoyi + * @date 2025-01-03 + */ +@Data +public class XljkUserWithDeptInfo { + + /** 用户ID */ + private Long userId; + + /** 用户账号 */ + private String userName; + + /** 用户昵称 */ + private String nickName; + + /** 部门ID */ + private Long deptId; + + /** 部门名称 */ + private String deptName; + + /** 手机号码 */ + private String phonenumber; + + /** 用户邮箱 */ + private String email; + + /** 帐号状态(1正常 0停用) */ + private String status; + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/bo/XljkConsultationAppointmentBo.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/bo/XljkConsultationAppointmentBo.java index a2f7590c..438353ad 100644 --- a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/bo/XljkConsultationAppointmentBo.java +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/bo/XljkConsultationAppointmentBo.java @@ -22,12 +22,19 @@ public class XljkConsultationAppointmentBo { private String appointmentTime; private String appointmentDetailTime; // 具体预约时间段,如"08:30-09:00" private String reason; + private String consultTheme; // 咨询主题 private String status; - private Date actualVisitTime; private Integer communicationDuration; private String consultationContent; private String counselorSuggestion; + // 新增心理健康相关字段 + private Integer psychologicalHistory; // 心理病史(1-否,2-是) + private String psychologicalHistoryDetail; // 心理病史详情 + private String basicSituation; // 基本情况 + private String majorLifeEvents; // 重大生活事件 + private Integer needReferral; // 是否需要转介(1-不需要,2-需要) + // 查询参数字段 private String studentName; private String studentNo; diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/bo/XljkConsultationScheduleBatchBo.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/bo/XljkConsultationScheduleBatchBo.java index fb943b9c..0bd081ad 100644 --- a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/bo/XljkConsultationScheduleBatchBo.java +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/bo/XljkConsultationScheduleBatchBo.java @@ -2,6 +2,7 @@ package org.dromara.xljk.domain.bo; import lombok.Data; import java.util.Date; +import java.util.List; /** * 批量创建预约时段的业务对象 @@ -13,6 +14,11 @@ public class XljkConsultationScheduleBatchBo { */ private Long counselorId; + /** + * 办公室 + */ + private String office; + /** * 开始日期 */ @@ -24,35 +30,50 @@ public class XljkConsultationScheduleBatchBo { private String endDate; /** - * 是否启用上午时段 + * 是否启用中午时段(字段名保持morningEnabled以兼容前端) */ private Boolean morningEnabled; /** - * 上午开始时间 (HH:mm) + * 中午开始时间 (HH:mm)(字段名保持morningStartTime以兼容前端) */ private String morningStartTime; /** - * 上午结束时间 (HH:mm) + * 中午结束时间 (HH:mm)(字段名保持morningEndTime以兼容前端) */ private String morningEndTime; + /** + * 是否启用中午时段 + */ + private Boolean noonEnabled; + + /** + * 中午时段列表 + */ + private List noonTimeSlots; + /** * 是否启用下午时段 */ private Boolean afternoonEnabled; /** - * 下午开始时间 (HH:mm) + * 下午开始时间 (HH:mm)(兼容旧版本) */ private String afternoonStartTime; /** - * 下午结束时间 (HH:mm) + * 下午结束时间 (HH:mm)(兼容旧版本) */ private String afternoonEndTime; + /** + * 下午时段列表 + */ + private List afternoonTimeSlots; + /** * 状态 (0正常 1停用) */ @@ -67,4 +88,13 @@ public class XljkConsultationScheduleBatchBo { * 批次ID,用于标识同一批次创建的预约时段 */ private String batchId; + + /** + * 时段内部类 + */ + @Data + public static class TimeSlot { + private String startTime; + private String endTime; + } } \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/bo/XljkConsultationScheduleBo.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/bo/XljkConsultationScheduleBo.java index 63ac131f..22785d29 100644 --- a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/bo/XljkConsultationScheduleBo.java +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/bo/XljkConsultationScheduleBo.java @@ -14,8 +14,9 @@ public class XljkConsultationScheduleBo { private Date updateTime; private Integer deleted; private Long counselorId; + private String office; // 办公室 private Date scheduleDate; - private String timeSlot; // 0上午 1下午 + private String timeSlot; // 0中午 1下午 private String startTime; // 开始时间 HH:mm 格式 private String endTime; // 结束时间 HH:mm 格式 private String status; diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/bo/XljkConsulterBo.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/bo/XljkConsulterBo.java new file mode 100644 index 00000000..a9a23729 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/bo/XljkConsulterBo.java @@ -0,0 +1,118 @@ +package org.dromara.xljk.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.xljk.domain.XljkConsulter; + +import jakarta.validation.constraints.*; +import java.util.Date; +import java.util.List; + +/** + * 心理咨询师配置业务对象 xljk_consulter + * + * @author ruoyi + * @date 2024-01-01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = XljkConsulter.class, reverseConvertGenerate = false) +public class XljkConsulterBo extends BaseEntity { + + /** + * 主键ID + */ + @NotNull(message = "主键ID不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 用户ID + */ + @NotNull(message = "用户ID不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long userId; + + /** + * 用户姓名 + */ + @NotBlank(message = "用户姓名不能为空", groups = { AddGroup.class, EditGroup.class }) + private String userName; + + /** + * 用户昵称 + */ + @NotBlank(message = "用户昵称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String nickName; + + /** + * 部门ID + */ + @NotNull(message = "部门ID不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long deptId; + + /** + * 部门名称 + */ + @NotBlank(message = "部门名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String deptName; + + /** + * 联系电话 + */ + private String phoneNumber; + + /** + * 办公室 + */ + private String office; + + /** + * 邮箱地址 + */ + private String email; + + /** + * 状态(1正常 0停用) + */ + @NotBlank(message = "状态不能为空", groups = { AddGroup.class, EditGroup.class }) + private String status; + + /** + * 备注说明 + */ + private String remark; + + /** + * 配置人ID + */ + private Long configBy; + + /** + * 配置人姓名 + */ + private String configByName; + + /** + * 分页大小 + */ + private Integer pageSize; + + /** + * 页码 + */ + private Integer pageNum; + + /** + * 搜索值 + */ + private String searchValue; + + /** + * 创建时间范围 + */ + private List createTimeRange; + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/vo/ScheduleWithStatusVo.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/vo/ScheduleWithStatusVo.java new file mode 100644 index 00000000..f7f65a44 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/vo/ScheduleWithStatusVo.java @@ -0,0 +1,82 @@ +package org.dromara.xljk.domain.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.util.Date; + +/** + * 带预约状态的排班数据视图对象 + */ +@Data +public class ScheduleWithStatusVo { + + /** + * 排班ID + */ + private Long scheduleId; + + /** + * 咨询师ID + */ + private Long counselorId; + + /** + * 咨询师姓名 + */ + private String counselorName; + + /** + * 排班日期 + */ + @JsonFormat(pattern = "yyyy-MM-dd") + private String date; + + /** + * 时段类型(0中午 1下午 2晚上) + */ + private String timeSlot; + + /** + * 开始时间(HH:mm) + */ + private String startTime; + + /** + * 结束时间(HH:mm) + */ + private String endTime; + + /** + * 时段显示文本(如:中午 08:30-12:00) + */ + private String timeDisplay; + + /** + * 是否已预约满 + */ + private Boolean isFullyBooked; + + /** + * 是否已过期 + */ + private Boolean isExpired; + + /** + * 可预约数量 + */ + private Integer availableSlots; + + /** + * 总预约数量 + */ + private Integer totalSlots; + + // 以下是基础字段,用于避免MyBatis映射错误 + private Long createBy; // 创建者 + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; // 创建时间 + private Long updateBy; // 更新者 + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; // 更新时间 +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/vo/XljkConsultationAppointmentVo.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/vo/XljkConsultationAppointmentVo.java index c1b302f0..79d34a54 100644 --- a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/vo/XljkConsultationAppointmentVo.java +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/vo/XljkConsultationAppointmentVo.java @@ -30,14 +30,12 @@ public class XljkConsultationAppointmentVo { private String appointmentTime; private String appointmentDetailTime; // 具体预约时间段,如"08:30-09:00" private String reason; + private String consultTheme; // 咨询主题 /** * 状态(0待咨询 1已完成 2已取消 3未到访) */ private Integer status; - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private Date actualVisitTime; - private Integer communicationDuration; private String consultationContent; private String counselorSuggestion; @@ -51,4 +49,14 @@ public class XljkConsultationAppointmentVo { // 新增院系和专业字段 private String collegeName; // 院系名称 private String majorName; // 专业名称 + + // 新增心理健康相关字段 + private Integer psychologicalHistory; // 心理病史(1-否,2-是) + private String psychologicalHistoryDetail; // 心理病史详情 + private String basicSituation; // 基本情况 + private String majorLifeEvents; // 重大生活事件 + private Integer needReferral; // 是否需要转介(1-不需要,2-需要) + + // 本学期到访次数(计算字段,不存储在数据库) + private Integer semesterVisitCount; } \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/vo/XljkConsultationScheduleSummaryVo.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/vo/XljkConsultationScheduleSummaryVo.java index dc6d6be0..a5c23720 100644 --- a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/vo/XljkConsultationScheduleSummaryVo.java +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/vo/XljkConsultationScheduleSummaryVo.java @@ -18,11 +18,12 @@ public class XljkConsultationScheduleSummaryVo { private Integer deleted; private Long counselorId; private String counselorName; // 咨询师姓名,用于前端显示 + private String office; // 办公室,用于前端显示 // 汇总信息 private String dateRange; // 日期范围,如"2025-07-01 至 2025-07-10" - private Boolean morningEnabled; // 是否启用上午时段 - private String morningTime; // 上午时间,如"08:30-12:00" + private Boolean morningEnabled; // 是否启用中午时段(字段名保持不变以兼容前端) + private String morningTime; // 中午时间,如"08:30-12:00" private Boolean afternoonEnabled; // 是否启用下午时段 private String afternoonTime; // 下午时间,如"14:30-17:00" private String status; // 状态 diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/vo/XljkConsultationScheduleVo.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/vo/XljkConsultationScheduleVo.java index 3ae8fdda..e6623ff7 100644 --- a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/vo/XljkConsultationScheduleVo.java +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/vo/XljkConsultationScheduleVo.java @@ -15,8 +15,9 @@ public class XljkConsultationScheduleVo { private Integer deleted; private Long counselorId; private String counselorName; // 咨询师姓名,用于前端显示 + private String office; // 办公室 private Date scheduleDate; - private String timeSlot; // 0上午 1下午 + private String timeSlot; // 0中午 1下午 private String startTime; // 开始时间 HH:mm 格式 private String endTime; // 结束时间 HH:mm 格式 private String status; diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/vo/XljkConsulterVo.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/vo/XljkConsulterVo.java new file mode 100644 index 00000000..7a375e57 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/domain/vo/XljkConsulterVo.java @@ -0,0 +1,139 @@ +package org.dromara.xljk.domain.vo; + +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.excel.convert.ExcelDictConvert; +import org.dromara.xljk.domain.XljkConsulter; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 心理咨询师配置视图对象 xljk_consulter + * + * @author ruoyi + * @date 2024-01-01 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = XljkConsulter.class) +public class XljkConsulterVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @ExcelProperty(value = "主键ID") + private Long id; + + /** + * 用户ID + */ + @ExcelProperty(value = "用户ID") + private Long userId; + + /** + * 用户姓名 + */ + @ExcelProperty(value = "用户姓名") + private String userName; + + /** + * 用户昵称 + */ + @ExcelProperty(value = "用户昵称") + private String nickName; + + /** + * 部门ID + */ + @ExcelProperty(value = "部门ID") + private Long deptId; + + /** + * 部门名称 + */ + @ExcelProperty(value = "部门名称") + private String deptName; + + /** + * 联系电话 + */ + @ExcelProperty(value = "联系电话") + private String phoneNumber; + + /** + * 办公室 + */ + @ExcelProperty(value = "办公室") + private String office; + + /** + * 邮箱地址 + */ + @ExcelProperty(value = "邮箱地址") + private String email; + + /** + * 状态(1正常 0停用) + */ + @ExcelProperty(value = "状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_normal_disable") + private String status; + + /** + * 备注说明 + */ + @ExcelProperty(value = "备注说明") + private String remark; + + /** + * 配置人ID + */ + private Long configBy; + + /** + * 配置人姓名 + */ + @ExcelProperty(value = "配置人") + private String configByName; + + /** + * 租户编号 + */ + private String tenantId; + + /** + * 创建部门 + */ + private Long createDept; + + /** + * 创建者ID + */ + private Long createBy; + + /** + * 创建时间 + */ + @ExcelProperty(value = "创建时间") + private Date createTime; + + /** + * 更新者ID + */ + private Long updateBy; + + /** + * 更新时间 + */ + @ExcelProperty(value = "更新时间") + private Date updateTime; + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/mapper/XljkConsulterMapper.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/mapper/XljkConsulterMapper.java new file mode 100644 index 00000000..161f9395 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/mapper/XljkConsulterMapper.java @@ -0,0 +1,66 @@ +package org.dromara.xljk.mapper; + +import org.dromara.xljk.domain.XljkConsulter; +import org.dromara.xljk.domain.vo.XljkConsulterVo; +import org.dromara.xljk.domain.XljkUserWithDeptInfo; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import org.apache.ibatis.annotations.Param; +import java.util.List; + +/** + * 心理咨询师配置Mapper接口 + * + * @author ruoyi + * @date 2024-01-01 + */ +public interface XljkConsulterMapper extends BaseMapperPlus { + + /** + * 查询心理咨询师配置 + * + * @param id 心理咨询师配置主键 + * @return 心理咨询师配置 + */ + XljkConsulterVo selectXljkConsulterVoById(Long id); + + /** + * 查询心理咨询师配置列表 + * + * @param xljkConsulter 心理咨询师配置 + * @return 心理咨询师配置集合 + */ + List selectXljkConsulterVoList(XljkConsulter xljkConsulter); + + /** + * 根据用户ID检查是否已存在咨询师配置 + * + * @param userId 用户ID + * @return 记录数 + */ + int checkUserExists(@Param("userId") Long userId); + + /** + * 批量启用咨询师 + * + * @param ids 咨询师ID列表 + * @return 影响行数 + */ + int enableConsultersByIds(@Param("ids") List ids); + + /** + * 批量停用咨询师 + * + * @param ids 咨询师ID列表 + * @return 影响行数 + */ + int disableConsultersByIds(@Param("ids") List ids); + + /** + * 查询完整的用户信息(包含部门信息)用于批量添加 + * + * @param userIds 用户ID列表 + * @return 用户和部门完整信息列表 + */ + List selectUsersWithDeptInfo(@Param("userIds") List userIds); + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/IXljkConsultationAppointmentService.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/IXljkConsultationAppointmentService.java index 6e103394..7d153080 100644 --- a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/IXljkConsultationAppointmentService.java +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/IXljkConsultationAppointmentService.java @@ -4,6 +4,7 @@ import org.dromara.xljk.domain.XljkConsultationAppointment; import org.dromara.xljk.domain.bo.XljkConsultationAppointmentBo; import org.dromara.xljk.domain.vo.XljkConsultationAppointmentVo; import org.dromara.xljk.domain.vo.TimeSlotDetailVo; +import org.dromara.xljk.domain.vo.ScheduleWithStatusVo; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.mybatis.core.page.PageQuery; import java.util.List; @@ -43,7 +44,7 @@ public interface IXljkConsultationAppointmentService { /** * 获取可用的详细时段列表(30分钟一个时段) * @param date 日期 - * @param timeSlot 时段类型(0上午 1下午) + * @param timeSlot 时段类型(0中午 1下午) * @return 详细时段列表 */ List getAvailableDetailSlots(Date date, String timeSlot); @@ -65,15 +66,21 @@ public interface IXljkConsultationAppointmentService { /** * 记录咨询结果 * @param id 预约ID - * @param actualVisitTime 实际到访时间 * @param communicationDuration 沟通时长 * @param consultationContent 咨询内容 * @param counselorSuggestion 咨询师建议 * @param status 状态(1已完成/3未到访) + * @param psychologicalHistory 心理病史(1-否,2-是) + * @param psychologicalHistoryDetail 心理病史详情 + * @param basicSituation 基本情况 + * @param majorLifeEvents 重大生活事件 + * @param needReferral 是否需要转介(1-不需要,2-需要) * @return 是否成功 */ - boolean recordConsultation(Long id, Date actualVisitTime, Integer communicationDuration, - String consultationContent, String counselorSuggestion, String status); + boolean recordConsultation(Long id, Integer communicationDuration, + String consultationContent, String counselorSuggestion, String status, + Integer psychologicalHistory, String psychologicalHistoryDetail, + String basicSituation, String majorLifeEvents, Integer needReferral); /** * 获取院系选项 @@ -95,4 +102,12 @@ public interface IXljkConsultationAppointmentService { * @return 班级选项列表 */ List> getClassOptions(String collegeName, String majorName); + + /** + * 获取日历视图的排班数据(带预约状态) + * @param startDate 开始日期 + * @param endDate 结束日期 + * @return 排班数据列表 + */ + List getCalendarSchedules(String startDate, String endDate); } \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/IXljkConsulterService.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/IXljkConsulterService.java new file mode 100644 index 00000000..add9af0e --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/IXljkConsulterService.java @@ -0,0 +1,102 @@ +package org.dromara.xljk.service; + +import org.dromara.xljk.domain.vo.XljkConsulterVo; +import org.dromara.xljk.domain.bo.XljkConsulterBo; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; + +import java.util.Collection; +import java.util.List; + +/** + * 心理咨询师配置Service接口 + * + * @author ruoyi + * @date 2024-01-01 + */ +public interface IXljkConsulterService { + + /** + * 查询心理咨询师配置 + * + * @param id 主键 + * @return 心理咨询师配置 + */ + XljkConsulterVo queryById(Long id); + + /** + * 查询心理咨询师配置列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 心理咨询师配置集合 + */ + TableDataInfo queryPageList(XljkConsulterBo bo, PageQuery pageQuery); + + /** + * 查询心理咨询师配置列表 + * + * @param bo 查询条件 + * @return 心理咨询师配置集合 + */ + List queryList(XljkConsulterBo bo); + + /** + * 新增心理咨询师配置 + * + * @param bo 心理咨询师配置 + * @return 结果 + */ + Boolean insertByBo(XljkConsulterBo bo); + + /** + * 修改心理咨询师配置 + * + * @param bo 心理咨询师配置 + * @return 结果 + */ + Boolean updateByBo(XljkConsulterBo bo); + + /** + * 校验并批量删除心理咨询师配置信息 + * + * @param ids 需要删除的主键集合 + * @return 结果 + */ + Boolean deleteWithValidByIds(Collection ids); + + /** + * 批量添加咨询师 + * + * @param userIds 用户ID列表 + * @param remark 备注 + * @return 结果 + */ + Boolean batchAddConsulters(List userIds, String remark); + + /** + * 查询我配置的咨询师列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 咨询师列表 + */ + TableDataInfo queryMyConfigList(XljkConsulterBo bo, PageQuery pageQuery); + + /** + * 批量启用咨询师 + * + * @param ids 咨询师ID列表 + * @return 结果 + */ + Boolean enableConsulters(List ids); + + /** + * 批量停用咨询师 + * + * @param ids 咨询师ID列表 + * @return 结果 + */ + Boolean disableConsulters(List ids); + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/impl/XljkConsultationAppointmentServiceImpl.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/impl/XljkConsultationAppointmentServiceImpl.java index 98bb0546..30debfdf 100644 --- a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/impl/XljkConsultationAppointmentServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/impl/XljkConsultationAppointmentServiceImpl.java @@ -10,6 +10,7 @@ import org.dromara.xljk.domain.XljkConsultationSchedule; import org.dromara.xljk.domain.bo.XljkConsultationAppointmentBo; import org.dromara.xljk.domain.vo.XljkConsultationAppointmentVo; import org.dromara.xljk.domain.vo.TimeSlotDetailVo; +import org.dromara.xljk.domain.vo.ScheduleWithStatusVo; import org.dromara.xljk.mapper.XljkConsultationAppointmentMapper; import org.dromara.xljk.mapper.XljkConsultationScheduleMapper; import org.dromara.xljk.service.IXljkConsultationAppointmentService; @@ -38,6 +39,8 @@ import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.mybatis.core.page.PageQuery; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import java.time.LocalDate; +import java.time.Month; @Slf4j @Service @@ -71,9 +74,8 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation BeanUtils.copyProperties(entity, vo); // 记录关键字段 - log.info("复制后的VO对象关键字段:status={}, actualVisitTime={}, communicationDuration={}, consultationContent长度={}, counselorSuggestion长度={}", + log.info("复制后的VO对象关键字段:status={}, communicationDuration={}, consultationContent长度={}, counselorSuggestion长度={}", vo.getStatus(), - vo.getActualVisitTime(), vo.getCommunicationDuration(), vo.getConsultationContent() != null ? vo.getConsultationContent().length() : 0, vo.getCounselorSuggestion() != null ? vo.getCounselorSuggestion().length() : 0); @@ -128,23 +130,8 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation // 再次检查关键字段 if (vo.getStatus() != null && vo.getStatus() == 1) { - log.info("已到访状态,检查咨询结果相关字段:actualVisitTime={}, communicationDuration={}", - vo.getActualVisitTime(), vo.getCommunicationDuration()); - - // 如果是已到访状态但实际到访时间为空,尝试从数据库直接查询 - if (vo.getActualVisitTime() == null) { - log.warn("已到访状态但实际到访时间为空,尝试从数据库直接查询"); - try { - String timeSql = "SELECT actual_visit_time FROM xljk_consultation_appointment WHERE id = " + id; - Date actualVisitTime = jdbcTemplate.queryForObject(timeSql, Date.class); - if (actualVisitTime != null) { - log.info("从数据库查询到实际到访时间: {}", actualVisitTime); - vo.setActualVisitTime(actualVisitTime); - } - } catch (Exception e) { - log.error("查询实际到访时间失败", e); - } - } + log.info("已到访状态,检查咨询结果相关字段:communicationDuration={}", + vo.getCommunicationDuration()); } log.info("返回预约记录详情: {}", vo); @@ -238,6 +225,9 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation return resultList.stream().map(row -> { XljkConsultationAppointmentVo vo = new XljkConsultationAppointmentVo(); + // 获取学生ID用于计算本学期到访次数 + Long studentId = row.get("student_id") != null ? ((Number) row.get("student_id")).longValue() : null; + // 设置基本字段 vo.setId(((Number) row.get("id")).longValue()); vo.setTenantId((String) row.get("tenant_id")); @@ -291,11 +281,44 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation vo.setStatus(0); // 默认为待咨询 } - vo.setActualVisitTime(convertToDate(row.get("actual_visit_time"))); vo.setCommunicationDuration(row.get("communication_duration") != null ? ((Number) row.get("communication_duration")).intValue() : null); vo.setConsultationContent((String) row.get("consultation_content")); vo.setCounselorSuggestion((String) row.get("counselor_suggestion")); + // 设置咨询主题字段 + vo.setConsultTheme((String) row.get("consult_theme")); + + // 设置心理健康相关字段 + Object psychologicalHistoryObj = row.get("psychological_history"); + if (psychologicalHistoryObj != null) { + if (psychologicalHistoryObj instanceof Number) { + vo.setPsychologicalHistory(((Number) psychologicalHistoryObj).intValue()); + } else if (psychologicalHistoryObj instanceof String) { + try { + vo.setPsychologicalHistory(Integer.parseInt((String) psychologicalHistoryObj)); + } catch (NumberFormatException e) { + vo.setPsychologicalHistory(null); + } + } + } + + vo.setPsychologicalHistoryDetail((String) row.get("psychological_history_detail")); + vo.setBasicSituation((String) row.get("basic_situation")); + vo.setMajorLifeEvents((String) row.get("major_life_events")); + + Object needReferralObj = row.get("need_referral"); + if (needReferralObj != null) { + if (needReferralObj instanceof Number) { + vo.setNeedReferral(((Number) needReferralObj).intValue()); + } else if (needReferralObj instanceof String) { + try { + vo.setNeedReferral(Integer.parseInt((String) needReferralObj)); + } catch (NumberFormatException e) { + vo.setNeedReferral(null); + } + } + } + // 设置关联字段 vo.setStudentName((String) row.get("student_name")); vo.setStudentNo((String) row.get("student_no")); @@ -304,9 +327,68 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation vo.setCollegeName((String) row.get("college_name")); vo.setMajorName((String) row.get("major_name")); + // 计算本学期到访次数 + if (studentId != null) { + vo.setSemesterVisitCount(calculateSemesterVisitCount(studentId)); + } else { + vo.setSemesterVisitCount(0); + } + return vo; }).collect(Collectors.toList()); } + + /** + * 计算学生本学期到访次数 + * 上学期:3月1日-8月31日 + * 下学期:9月1日-2月29日 + * @param studentId 学生ID + * @return 本学期到访次数 + */ + private int calculateSemesterVisitCount(Long studentId) { + try { + // 获取当前日期 + LocalDate now = LocalDate.now(); + String startDate, endDate; + + // 判断当前处于哪个学期 + if (now.getMonthValue() >= 3 && now.getMonthValue() <= 8) { + // 上学期:3月1日-8月31日 + startDate = now.getYear() + "-03-01"; + endDate = now.getYear() + "-08-31"; + } else if (now.getMonthValue() >= 9) { + // 下学期:9月1日-次年2月29日(当年9月到次年2月) + startDate = now.getYear() + "-09-01"; + // 计算次年2月的最后一天 + int nextYear = now.getYear() + 1; + LocalDate nextFeb = LocalDate.of(nextYear, Month.FEBRUARY, 1); + LocalDate lastDayOfFeb = nextFeb.withDayOfMonth(nextFeb.lengthOfMonth()); + endDate = lastDayOfFeb.toString(); + } else { + // 1月、2月属于上一学年的下学期 + int prevYear = now.getYear() - 1; + startDate = prevYear + "-09-01"; + // 当年2月的最后一天 + LocalDate currentFeb = LocalDate.of(now.getYear(), Month.FEBRUARY, 1); + LocalDate lastDayOfFeb = currentFeb.withDayOfMonth(currentFeb.lengthOfMonth()); + endDate = lastDayOfFeb.toString(); + } + + // 查询该学生在当前学期内状态为已到访(status=1)的记录数 + String sql = "SELECT COUNT(*) FROM xljk_consultation_appointment " + + "WHERE student_id = ? AND status = 1 AND deleted = 0 " + + "AND appointment_date >= ? AND appointment_date <= ?"; + + Integer count = jdbcTemplate.queryForObject(sql, Integer.class, studentId, startDate, endDate); + + log.debug("学生ID: {}, 学期时间段: {} - {}, 到访次数: {}", studentId, startDate, endDate, count); + + return count != null ? count : 0; + } catch (Exception e) { + log.error("计算学生本学期到访次数失败,学生ID: {}", studentId, e); + return 0; + } + } @Override public boolean insert(XljkConsultationAppointmentBo bo) { @@ -341,7 +423,7 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation LambdaQueryWrapper appointmentQueryWrapper = new LambdaQueryWrapper<>(); appointmentQueryWrapper.ge(XljkConsultationAppointment::getAppointmentDate, startDate) .le(XljkConsultationAppointment::getAppointmentDate, endDate) - .in(XljkConsultationAppointment::getStatus, Arrays.asList("0", "1")); // 待咨询或已完成的 + .in(XljkConsultationAppointment::getStatus, Arrays.asList("0", "1", "4")); // 待咨询、已完成或待审核的 List appointments = mapper.selectList(appointmentQueryWrapper); @@ -365,9 +447,10 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation String dateStr = new SimpleDateFormat("yyyy-MM-dd").format(schedule.getScheduleDate()); String key = dateStr + "_" + schedule.getId(); - // 检查该时段的可用详细时段 + // 检查该时段的可用详细时段(用于时间判断,但总容量固定为1) List detailSlots = TimeSlotUtils.generateTimeSlots(schedule.getStartTime(), schedule.getEndTime()); - int totalDetailSlots = detailSlots.size(); + int totalDetailSlots = detailSlots.size(); // 保留用于时间过期判断 + int totalSlots = 1; // 每个时段只能预约1个人 int bookedCount = 0; boolean isExpired = false; @@ -408,15 +491,11 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation isExpired = false; } - // 检查已预约的详细时段 + // 检查已预约数量(按整个时段计算,不再按详细时段计算) if (bookedDetailSlots.containsKey(key)) { List bookedTimes = bookedDetailSlots.get(key); - for (TimeSlotDetailVo slot : detailSlots) { - String detailTime = slot.getStartTime() + "-" + slot.getEndTime(); - if (bookedTimes.contains(detailTime)) { - bookedCount++; - } - } + // 只要有任何预约,就算已预约1个(因为每个时段只能预约1个人) + bookedCount = bookedTimes.isEmpty() ? 0 : 1; } // 创建时段信息 @@ -427,8 +506,8 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation slot.put("timeSlot", schedule.getTimeSlot()); slot.put("startTime", schedule.getStartTime()); slot.put("endTime", schedule.getEndTime()); - slot.put("allBooked", bookedCount >= totalDetailSlots); // 是否全部约满 - slot.put("availableCount", totalDetailSlots - bookedCount); // 可用数量 + slot.put("allBooked", bookedCount >= totalSlots); // 是否全部约满 + slot.put("availableCount", totalSlots - bookedCount); // 可用数量 slot.put("isExpired", isExpired); // 是否已过期 // 查询咨询师姓名 @@ -463,21 +542,34 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation .eq(XljkConsultationSchedule::getTimeSlot, timeSlot) .eq(XljkConsultationSchedule::getStatus, "0"); // 0表示正常可用 - XljkConsultationSchedule schedule = scheduleMapper.selectOne(scheduleQueryWrapper); + // 修改:使用selectList()而不是selectOne(),因为可能有多条记录 + List schedules = scheduleMapper.selectList(scheduleQueryWrapper); - if (schedule == null) { + if (schedules == null || schedules.isEmpty()) { // 该日期没有排班 return new ArrayList<>(); } - // 2. 生成30分钟时段列表 - List timeSlots = TimeSlotUtils.generateTimeSlots(schedule.getStartTime(), schedule.getEndTime()); + // 2. 合并所有排班的时段,生成完整的30分钟时段列表 + List allTimeSlots = new ArrayList<>(); + Set uniqueTimeSlots = new LinkedHashSet<>(); // 用于去重,保持顺序 - // 3. 查询已预约的时段 + for (XljkConsultationSchedule schedule : schedules) { + List timeSlots = TimeSlotUtils.generateTimeSlots(schedule.getStartTime(), schedule.getEndTime()); + for (TimeSlotDetailVo slot : timeSlots) { + String slotKey = slot.getStartTime() + "-" + slot.getEndTime(); + if (uniqueTimeSlots.add(slotKey)) { + allTimeSlots.add(slot); + } + } + } + + // 3. 查询已预约的时段(查询所有相关排班的预约) + List scheduleIds = schedules.stream().map(XljkConsultationSchedule::getId).collect(Collectors.toList()); LambdaQueryWrapper appointmentQueryWrapper = new LambdaQueryWrapper<>(); appointmentQueryWrapper.eq(XljkConsultationAppointment::getAppointmentDate, date) - .eq(XljkConsultationAppointment::getScheduleId, schedule.getId()) // 按排班ID过滤,确保只查询当前时段的预约 - .in(XljkConsultationAppointment::getStatus, Arrays.asList("0", "1")); // 待咨询或已完成的 + .in(XljkConsultationAppointment::getScheduleId, scheduleIds) // 按排班ID列表过滤 + .in(XljkConsultationAppointment::getStatus, Arrays.asList("0", "1", "4")); // 待咨询、已完成或待审核的 List appointments = mapper.selectList(appointmentQueryWrapper); @@ -491,7 +583,7 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation Calendar calendar = Calendar.getInstance(); calendar.setTime(date); - for (TimeSlotDetailVo slot : timeSlots) { + for (TimeSlotDetailVo slot : allTimeSlots) { String detailTime = slot.getStartTime() + "-" + slot.getEndTime(); // 检查是否已预约 @@ -532,7 +624,7 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation } } - return timeSlots; + return allTimeSlots; } @Override @@ -582,7 +674,7 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation XljkConsultationAppointment entity = new XljkConsultationAppointment(); BeanUtils.copyProperties(bo, entity); entity.setStudentId(studentId); // 使用验证后的学生ID - entity.setStatus("0"); // 0表示待咨询 + entity.setStatus("4"); // 4表示待审核 entity.setCreateTime(new Date()); // 设置创建时间 return mapper.insert(entity) > 0; } @@ -645,10 +737,6 @@ if (statusObj != null) { vo.setScheduleId(((Number) row.get("schedule_id")).longValue()); } - if (row.get("actual_visit_time") != null) { - vo.setActualVisitTime(convertToDate(row.get("actual_visit_time"))); - } - if (row.get("communication_duration") != null) { vo.setCommunicationDuration(((Number) row.get("communication_duration")).intValue()); } @@ -691,9 +779,11 @@ if (statusObj != null) { @Override @Transactional - public boolean recordConsultation(Long id, Date actualVisitTime, Integer communicationDuration, - String consultationContent, String counselorSuggestion, String status) { - log.info("记录咨询结果,ID: {}, 状态: {}, 实际到访时间: {}", id, status, actualVisitTime); + public boolean recordConsultation(Long id, Integer communicationDuration, + String consultationContent, String counselorSuggestion, String status, + Integer psychologicalHistory, String psychologicalHistoryDetail, + String basicSituation, String majorLifeEvents, Integer needReferral) { + log.info("记录咨询结果,ID: {}, 状态: {}, 心理病史: {}, 转介: {}", id, status, psychologicalHistory, needReferral); XljkConsultationAppointment entity = mapper.selectById(id); if (entity == null) { @@ -717,29 +807,42 @@ if (statusObj != null) { // 只有在已到访状态(1)时,才设置咨询结果相关字段 if (statusInt == 1) { - if (actualVisitTime == null) { - log.warn("实际到访时间为空,使用当前时间"); - actualVisitTime = new Date(); - } - - entity.setActualVisitTime(actualVisitTime); entity.setCommunicationDuration(communicationDuration != null ? communicationDuration : 30); entity.setConsultationContent(consultationContent); entity.setCounselorSuggestion(counselorSuggestion); - log.info("设置咨询结果相关字段:实际到访时间={}, 沟通时长={}, 咨询内容长度={}, 咨询师建议长度={}", - actualVisitTime, + // 设置心理病史相关字段 + entity.setPsychologicalHistory(psychologicalHistory); + entity.setPsychologicalHistoryDetail(psychologicalHistoryDetail); + entity.setBasicSituation(basicSituation); + entity.setMajorLifeEvents(majorLifeEvents); + entity.setNeedReferral(needReferral); + + log.info("设置咨询结果相关字段:沟通时长={}, 咨询内容长度={}, 咨询师建议长度={}", communicationDuration, consultationContent != null ? consultationContent.length() : 0, counselorSuggestion != null ? counselorSuggestion.length() : 0); + + log.info("设置心理病史相关字段:心理病史={}, 心理病史详情长度={}, 基本情况长度={}, 重大生活事件长度={}, 转介={}", + psychologicalHistory, + psychologicalHistoryDetail != null ? psychologicalHistoryDetail.length() : 0, + basicSituation != null ? basicSituation.length() : 0, + majorLifeEvents != null ? majorLifeEvents.length() : 0, + needReferral); } else { // 未到访状态,清空咨询结果相关字段 log.info("非已到访状态,清空咨询结果相关字段"); - entity.setActualVisitTime(null); entity.setCommunicationDuration(0); entity.setConsultationContent(""); entity.setCounselorSuggestion(""); + // 清空心理病史相关字段 + entity.setPsychologicalHistory(null); + entity.setPsychologicalHistoryDetail(""); + entity.setBasicSituation(""); + entity.setMajorLifeEvents(""); + entity.setNeedReferral(null); + // 如果是未到访状态(3),创建未到访记录 if (statusInt == 3) { log.info("记录未到访,创建未到访记录"); @@ -1323,10 +1426,19 @@ if (statusObj != null) { public TableDataInfo selectPageList(XljkConsultationAppointmentBo bo, PageQuery pageQuery) { log.info("分页查询预约记录列表,页码: {}, 每页数量: {}", pageQuery.getPageNum(), pageQuery.getPageSize()); + // 将变量定义移到try块外,确保catch块可以访问 + StringBuilder sqlBuilder = new StringBuilder(); + List params = new ArrayList<>(); + try { // 构建查询条件 - StringBuilder sqlBuilder = new StringBuilder(); - sqlBuilder.append("SELECT a.*, s.name AS student_name, s.student_no, "); + sqlBuilder.append("SELECT a.id, a.tenant_id, a.student_id, a.counselor_id, a.schedule_id, "); + sqlBuilder.append("a.appointment_date, a.appointment_time, a.appointment_detail_time, "); + sqlBuilder.append("a.reason, a.status, a.communication_duration, a.consultation_content, "); + sqlBuilder.append("a.counselor_suggestion, a.psychological_history, a.psychological_history_detail, "); + sqlBuilder.append("a.basic_situation, a.major_life_events, a.need_referral, "); + sqlBuilder.append("a.create_by, a.create_time, a.update_by, a.update_time, "); + sqlBuilder.append("s.name AS student_name, s.student_no, "); sqlBuilder.append("s.college_name, s.major_name, s.class_name, "); sqlBuilder.append("u.nick_name AS counselor_name "); sqlBuilder.append("FROM xljk_consultation_appointment a "); @@ -1334,8 +1446,6 @@ if (statusObj != null) { sqlBuilder.append("LEFT JOIN sys_user u ON a.counselor_id = u.user_id "); sqlBuilder.append("WHERE a.deleted = 0 "); - List params = new ArrayList<>(); - // 添加查询条件 if (bo.getStudentName() != null && !bo.getStudentName().isEmpty()) { sqlBuilder.append("AND s.name LIKE ? "); @@ -1457,10 +1567,6 @@ if (statusObj != null) { vo.setScheduleId(((Number) row.get("schedule_id")).longValue()); } - if (row.get("actual_visit_time") != null) { - vo.setActualVisitTime(convertToDate(row.get("actual_visit_time"))); - } - if (row.get("communication_duration") != null) { vo.setCommunicationDuration(((Number) row.get("communication_duration")).intValue()); } @@ -1468,6 +1574,37 @@ if (statusObj != null) { vo.setConsultationContent((String) row.get("consultation_content")); vo.setCounselorSuggestion((String) row.get("counselor_suggestion")); + // 新增字段映射 + Object psychologicalHistoryObj = row.get("psychological_history"); + if (psychologicalHistoryObj != null) { + if (psychologicalHistoryObj instanceof Number) { + vo.setPsychologicalHistory(((Number) psychologicalHistoryObj).intValue()); + } else if (psychologicalHistoryObj instanceof String) { + try { + vo.setPsychologicalHistory(Integer.parseInt((String) psychologicalHistoryObj)); + } catch (NumberFormatException e) { + vo.setPsychologicalHistory(null); + } + } + } + + vo.setPsychologicalHistoryDetail((String) row.get("psychological_history_detail")); + vo.setBasicSituation((String) row.get("basic_situation")); + vo.setMajorLifeEvents((String) row.get("major_life_events")); + + Object needReferralObj = row.get("need_referral"); + if (needReferralObj != null) { + if (needReferralObj instanceof Number) { + vo.setNeedReferral(((Number) needReferralObj).intValue()); + } else if (needReferralObj instanceof String) { + try { + vo.setNeedReferral(Integer.parseInt((String) needReferralObj)); + } catch (NumberFormatException e) { + vo.setNeedReferral(null); + } + } + } + // 其他通用字段 if (row.get("create_by") != null) { vo.setCreateBy(((Number) row.get("create_by")).longValue()); @@ -1485,6 +1622,13 @@ if (statusObj != null) { vo.setUpdateTime(convertToDate(row.get("update_time"))); } + // 计算本学期到访次数 + if (vo.getStudentId() != null) { + vo.setSemesterVisitCount(calculateSemesterVisitCount(vo.getStudentId())); + } else { + vo.setSemesterVisitCount(0); + } + result.add(vo); } @@ -1496,12 +1640,194 @@ if (statusObj != null) { return tableDataInfo; } catch (Exception e) { log.error("分页查询预约记录列表异常", e); + log.error("SQL: {}", sqlBuilder.toString()); + log.error("参数: {}", params); TableDataInfo emptyResult = new TableDataInfo<>(); emptyResult.setRows(new ArrayList<>()); emptyResult.setTotal(0); emptyResult.setCode(200); - emptyResult.setMsg("查询失败"); + emptyResult.setMsg("查询失败: " + e.getMessage()); return emptyResult; } } + + @Override + public List getCalendarSchedules(String startDate, String endDate) { + try { + // 1. 查询指定日期范围内的所有排班数据 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.ge(XljkConsultationSchedule::getScheduleDate, startDate) + .le(XljkConsultationSchedule::getScheduleDate, endDate) + .eq(XljkConsultationSchedule::getStatus, "0") // 0表示正常可用 + .orderByAsc(XljkConsultationSchedule::getScheduleDate) + .orderByAsc(XljkConsultationSchedule::getTimeSlot) + .orderByAsc(XljkConsultationSchedule::getStartTime); + + List schedules = scheduleMapper.selectList(queryWrapper); + + if (schedules.isEmpty()) { + return new ArrayList<>(); + } + + // 2. 查询该日期范围内的所有预约记录 + List scheduleIds = schedules.stream().map(XljkConsultationSchedule::getId).collect(Collectors.toList()); + LambdaQueryWrapper appointmentQueryWrapper = new LambdaQueryWrapper<>(); + appointmentQueryWrapper.in(XljkConsultationAppointment::getScheduleId, scheduleIds) + .in(XljkConsultationAppointment::getStatus, Arrays.asList("0", "1", "4")); // 待咨询、已完成或待审核的 + + List appointments = mapper.selectList(appointmentQueryWrapper); + + // 3. 构建排班ID到预约记录的映射 + Map> appointmentMap = appointments.stream() + .collect(Collectors.groupingBy(XljkConsultationAppointment::getScheduleId)); + + // 4. 获取咨询师信息映射 + Map counselorMap = getCounselorNameMap(); + + // 5. 获取当前时间 + Date now = new Date(); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm"); + + // 6. 构建结果列表 + List result = new ArrayList<>(); + + for (XljkConsultationSchedule schedule : schedules) { + ScheduleWithStatusVo vo = new ScheduleWithStatusVo(); + + // 基本信息 + vo.setScheduleId(schedule.getId()); + vo.setCounselorId(schedule.getCounselorId()); + vo.setCounselorName(counselorMap.get(schedule.getCounselorId())); + vo.setDate(dateFormat.format(schedule.getScheduleDate())); + vo.setTimeSlot(schedule.getTimeSlot()); + vo.setStartTime(schedule.getStartTime()); + vo.setEndTime(schedule.getEndTime()); + + // 构建时段显示文本 + String timeSlotText = getTimeSlotText(schedule.getTimeSlot()); + vo.setTimeDisplay(timeSlotText + " " + schedule.getStartTime() + "-" + schedule.getEndTime()); + + // 计算预约状态 + List scheduleAppointments = appointmentMap.get(schedule.getId()); + int bookedCount = scheduleAppointments != null ? scheduleAppointments.size() : 0; + + // 每个时段只能预约1个人(无论时段长度) + // 生成30分钟时段用于详细时段显示,但总预约数量固定为1 + List timeSlots = TimeSlotUtils.generateTimeSlots(schedule.getStartTime(), schedule.getEndTime()); + int totalSlots = 1; // 每个时段只能预约1个人 + + vo.setTotalSlots(totalSlots); + vo.setAvailableSlots(Math.max(0, totalSlots - bookedCount)); + vo.setIsFullyBooked(bookedCount >= totalSlots); + + // 检查是否已过期 + boolean isExpired = false; + try { + Date scheduleDate = schedule.getScheduleDate(); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(scheduleDate); + + // 设置具体的结束时间 + String[] endTimeParts = schedule.getEndTime().split(":"); + calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(endTimeParts[0])); + calendar.set(Calendar.MINUTE, Integer.parseInt(endTimeParts[1])); + calendar.set(Calendar.SECOND, 0); + + Date scheduleEndTime = calendar.getTime(); + isExpired = scheduleEndTime.before(now); + } catch (Exception e) { + log.warn("检查时段过期状态失败: " + schedule.getId(), e); + } + + vo.setIsExpired(isExpired); + + // 设置基础字段 + vo.setCreateBy(schedule.getCreateBy()); + vo.setCreateTime(schedule.getCreateTime()); + vo.setUpdateBy(schedule.getUpdateBy()); + vo.setUpdateTime(schedule.getUpdateTime()); + + result.add(vo); + } + + return result; + + } catch (Exception e) { + log.error("获取日历排班数据失败", e); + return new ArrayList<>(); + } + } + + /** + * 获取时段类型文本 + */ + private String getTimeSlotText(String timeSlot) { + switch (timeSlot) { + case "0": return "中午"; + case "1": return "下午"; + case "2": return "晚上"; + default: return "未知"; + } + } + + /** + * 获取咨询师名称映射 + */ + private Map getCounselorNameMap() { + try { + Map counselorMap = new HashMap<>(); + + // 1. 查询所有已创建排班的咨询师ID + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.select(XljkConsultationSchedule::getCounselorId) + .eq(XljkConsultationSchedule::getStatus, "0") // 只查询正常状态的 + .groupBy(XljkConsultationSchedule::getCounselorId); // 去重 + + List schedules = scheduleMapper.selectList(queryWrapper); + if (schedules.isEmpty()) { + return counselorMap; + } + + // 2. 提取咨询师ID列表 + List counselorIds = schedules.stream() + .map(XljkConsultationSchedule::getCounselorId) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + + if (counselorIds.isEmpty()) { + return counselorMap; + } + + // 3. 批量查询咨询师信息 + List counselors = userService.selectUserByIds(counselorIds, null); + + // 4. 构建ID到姓名的映射 + counselorMap = counselors.stream() + .filter(user -> user.getUserId() != null) + .collect(Collectors.toMap( + SysUserVo::getUserId, + user -> { + // 优先使用昵称,如果没有昵称则使用用户名 + String name = user.getNickName(); + if (StringUtils.isBlank(name)) { + name = user.getUserName(); + } + if (StringUtils.isBlank(name)) { + name = "未知咨询师"; + } + return name; + }, + (existing, replacement) -> existing // 如果ID重复,保留第一个 + )); + + log.info("成功加载咨询师信息映射,共{}个咨询师", counselorMap.size()); + return counselorMap; + + } catch (Exception e) { + log.error("获取咨询师信息失败", e); + return new HashMap<>(); + } + } } \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/impl/XljkConsultationScheduleServiceImpl.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/impl/XljkConsultationScheduleServiceImpl.java index 73b8687a..b186bdc3 100644 --- a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/impl/XljkConsultationScheduleServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/impl/XljkConsultationScheduleServiceImpl.java @@ -13,6 +13,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import lombok.extern.slf4j.Slf4j; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -26,6 +27,7 @@ import java.util.UUID; import java.util.stream.Collectors; import java.util.LinkedHashMap; +@Slf4j @Service public class XljkConsultationScheduleServiceImpl implements IXljkConsultationScheduleService { @Autowired @@ -43,11 +45,16 @@ public class XljkConsultationScheduleServiceImpl implements IXljkConsultationSch // 查询咨询师姓名 if (entity.getCounselorId() != null) { String sql = "SELECT nick_name FROM sys_user WHERE user_id = ?"; - List> result = jdbcTemplate.queryForList(sql, entity.getCounselorId()); - if (!result.isEmpty()) { - vo.setCounselorName((String) result.get(0).get("nick_name")); + try { + String counselorName = jdbcTemplate.queryForObject(sql, String.class, entity.getCounselorId()); + vo.setCounselorName(counselorName); + } catch (Exception e) { + log.error("查询咨询师姓名失败,counselorId: {}", entity.getCounselorId(), e); } } + + // office字段直接从排班记录中获取 + vo.setOffice(entity.getOffice()); return vo; } @@ -65,11 +72,15 @@ public class XljkConsultationScheduleServiceImpl implements IXljkConsultationSch // 查询咨询师姓名 if (entity.getCounselorId() != null) { String sql = "SELECT nick_name FROM sys_user WHERE user_id = ?"; - List> result = jdbcTemplate.queryForList(sql, entity.getCounselorId()); - if (!result.isEmpty()) { - vo.setCounselorName((String) result.get(0).get("nick_name")); + try { + String counselorName = jdbcTemplate.queryForObject(sql, String.class, entity.getCounselorId()); + vo.setCounselorName(counselorName); + } catch (Exception e) { + log.error("查询咨询师姓名失败,counselorId: {}", entity.getCounselorId(), e); } } + // office字段直接从排班记录中获取 + vo.setOffice(entity.getOffice()); return vo; }).collect(Collectors.toList()); } @@ -87,6 +98,11 @@ public class XljkConsultationScheduleServiceImpl implements IXljkConsultationSch queryWrapper.eq(XljkConsultationSchedule::getCounselorId, bo.getCounselorId()); } + // 添加办公室筛选条件 + if (bo.getOffice() != null && !bo.getOffice().isEmpty()) { + queryWrapper.like(XljkConsultationSchedule::getOffice, bo.getOffice()); + } + // 按创建时间倒序查询所有记录 queryWrapper.orderByDesc(XljkConsultationSchedule::getCreateTime); @@ -144,12 +160,17 @@ public class XljkConsultationScheduleServiceImpl implements IXljkConsultationSch // 查询咨询师姓名 if (batchGroup.getCounselorId() != null) { String sql = "SELECT nick_name FROM sys_user WHERE user_id = ?"; - List> result = jdbcTemplate.queryForList(sql, batchGroup.getCounselorId()); - if (!result.isEmpty()) { - summary.setCounselorName((String) result.get(0).get("nick_name")); + try { + String counselorName = jdbcTemplate.queryForObject(sql, String.class, batchGroup.getCounselorId()); + summary.setCounselorName(counselorName); + } catch (Exception e) { + log.error("查询咨询师姓名失败,counselorId: {}", batchGroup.getCounselorId(), e); } } + // office字段直接从排班记录中获取 + summary.setOffice(batchGroup.getOffice()); + // 确保创建时间不为空 if (summary.getCreateTime() == null) { summary.setCreateTime(new Date()); @@ -226,12 +247,17 @@ public class XljkConsultationScheduleServiceImpl implements IXljkConsultationSch // 查询咨询师姓名 if (firstSchedule.getCounselorId() != null) { String sql = "SELECT nick_name FROM sys_user WHERE user_id = ?"; - List> result = jdbcTemplate.queryForList(sql, firstSchedule.getCounselorId()); - if (!result.isEmpty()) { - summary.setCounselorName((String) result.get(0).get("nick_name")); + try { + String counselorName = jdbcTemplate.queryForObject(sql, String.class, firstSchedule.getCounselorId()); + summary.setCounselorName(counselorName); + } catch (Exception e) { + log.error("查询咨询师姓名失败,counselorId: {}", firstSchedule.getCounselorId(), e); } } + // office字段直接从排班记录中获取 + summary.setOffice(firstSchedule.getOffice()); + summaryList.add(summary); } } @@ -283,9 +309,12 @@ public class XljkConsultationScheduleServiceImpl implements IXljkConsultationSch return 0; } - // 检查是否至少启用了一个时段 - if ((batchBo.getMorningEnabled() == null || !batchBo.getMorningEnabled()) && - (batchBo.getAfternoonEnabled() == null || !batchBo.getAfternoonEnabled())) { + // 检查是否至少启用了一个时段(兼容新旧版本) + boolean hasMorning = (batchBo.getMorningEnabled() != null && batchBo.getMorningEnabled()); + boolean hasNoon = (batchBo.getNoonEnabled() != null && batchBo.getNoonEnabled()); + boolean hasAfternoon = (batchBo.getAfternoonEnabled() != null && batchBo.getAfternoonEnabled()); + + if (!hasMorning && !hasNoon && !hasAfternoon) { return 0; } @@ -321,12 +350,13 @@ public class XljkConsultationScheduleServiceImpl implements IXljkConsultationSch // 如果是周末且设置了跳过周末,则跳过 if (!(isWeekend && Boolean.TRUE.equals(batchBo.getSkipWeekends()))) { - // 创建上午时段 + // 创建中午时段(字段名保持morning以兼容前端) if (Boolean.TRUE.equals(batchBo.getMorningEnabled())) { XljkConsultationSchedule morningSchedule = new XljkConsultationSchedule(); morningSchedule.setCounselorId(batchBo.getCounselorId()); + morningSchedule.setOffice(batchBo.getOffice()); morningSchedule.setScheduleDate(currentDate); - morningSchedule.setTimeSlot("0"); // 上午 + morningSchedule.setTimeSlot("0"); // 中午 morningSchedule.setStartTime(batchBo.getMorningStartTime()); morningSchedule.setEndTime(batchBo.getMorningEndTime()); morningSchedule.setStatus(batchBo.getStatus()); @@ -335,18 +365,54 @@ public class XljkConsultationScheduleServiceImpl implements IXljkConsultationSch schedules.add(morningSchedule); } - // 创建下午时段 + // 创建中午时段(新版本) + if (Boolean.TRUE.equals(batchBo.getNoonEnabled()) && batchBo.getNoonTimeSlots() != null) { + for (XljkConsultationScheduleBatchBo.TimeSlot timeSlot : batchBo.getNoonTimeSlots()) { + XljkConsultationSchedule noonSchedule = new XljkConsultationSchedule(); + noonSchedule.setCounselorId(batchBo.getCounselorId()); + noonSchedule.setOffice(batchBo.getOffice()); + noonSchedule.setScheduleDate(currentDate); + noonSchedule.setTimeSlot("0"); // 中午时段 + noonSchedule.setStartTime(timeSlot.getStartTime()); + noonSchedule.setEndTime(timeSlot.getEndTime()); + noonSchedule.setStatus(batchBo.getStatus()); + noonSchedule.setBatchId(batchId); // 设置批次ID + noonSchedule.setCreateTime(now); // 设置创建时间 + schedules.add(noonSchedule); + } + } + + // 创建下午时段(兼容新旧版本) if (Boolean.TRUE.equals(batchBo.getAfternoonEnabled())) { - XljkConsultationSchedule afternoonSchedule = new XljkConsultationSchedule(); - afternoonSchedule.setCounselorId(batchBo.getCounselorId()); - afternoonSchedule.setScheduleDate(currentDate); - afternoonSchedule.setTimeSlot("1"); // 下午 - afternoonSchedule.setStartTime(batchBo.getAfternoonStartTime()); - afternoonSchedule.setEndTime(batchBo.getAfternoonEndTime()); - afternoonSchedule.setStatus(batchBo.getStatus()); - afternoonSchedule.setBatchId(batchId); // 设置批次ID - afternoonSchedule.setCreateTime(now); // 设置创建时间 - schedules.add(afternoonSchedule); + if (batchBo.getAfternoonTimeSlots() != null && !batchBo.getAfternoonTimeSlots().isEmpty()) { + // 新版本:支持多时段 + for (XljkConsultationScheduleBatchBo.TimeSlot timeSlot : batchBo.getAfternoonTimeSlots()) { + XljkConsultationSchedule afternoonSchedule = new XljkConsultationSchedule(); + afternoonSchedule.setCounselorId(batchBo.getCounselorId()); + afternoonSchedule.setOffice(batchBo.getOffice()); + afternoonSchedule.setScheduleDate(currentDate); + afternoonSchedule.setTimeSlot("1"); // 下午 + afternoonSchedule.setStartTime(timeSlot.getStartTime()); + afternoonSchedule.setEndTime(timeSlot.getEndTime()); + afternoonSchedule.setStatus(batchBo.getStatus()); + afternoonSchedule.setBatchId(batchId); // 设置批次ID + afternoonSchedule.setCreateTime(now); // 设置创建时间 + schedules.add(afternoonSchedule); + } + } else if (batchBo.getAfternoonStartTime() != null && batchBo.getAfternoonEndTime() != null) { + // 旧版本:单时段 + XljkConsultationSchedule afternoonSchedule = new XljkConsultationSchedule(); + afternoonSchedule.setCounselorId(batchBo.getCounselorId()); + afternoonSchedule.setOffice(batchBo.getOffice()); + afternoonSchedule.setScheduleDate(currentDate); + afternoonSchedule.setTimeSlot("1"); // 下午 + afternoonSchedule.setStartTime(batchBo.getAfternoonStartTime()); + afternoonSchedule.setEndTime(batchBo.getAfternoonEndTime()); + afternoonSchedule.setStatus(batchBo.getStatus()); + afternoonSchedule.setBatchId(batchId); // 设置批次ID + afternoonSchedule.setCreateTime(now); // 设置创建时间 + schedules.add(afternoonSchedule); + } } } diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/impl/XljkConsulterServiceImpl.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/impl/XljkConsulterServiceImpl.java new file mode 100644 index 00000000..e4d48e74 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/impl/XljkConsulterServiceImpl.java @@ -0,0 +1,329 @@ +package org.dromara.xljk.service.impl; + +import org.dromara.common.core.utils.MapstructUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.dromara.xljk.domain.bo.XljkConsulterBo; +import org.dromara.xljk.domain.vo.XljkConsulterVo; +import org.dromara.xljk.domain.XljkConsulter; +import org.dromara.xljk.domain.XljkUserWithDeptInfo; +import org.dromara.xljk.mapper.XljkConsulterMapper; +import org.dromara.xljk.service.IXljkConsulterService; +import org.dromara.system.service.ISysUserService; +import org.dromara.system.service.ISysRoleService; +import org.dromara.system.domain.vo.SysUserVo; +import org.dromara.system.domain.vo.SysRoleVo; +import org.dromara.system.domain.SysRole; +import org.dromara.system.domain.SysUserRole; +import org.dromara.system.domain.bo.SysRoleBo; +import org.dromara.system.mapper.SysUserRoleMapper; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.common.core.exception.ServiceException; + +import java.util.List; +import java.util.Map; +import java.util.Collection; +import java.util.ArrayList; +import java.util.stream.Collectors; + +/** + * 心理咨询师配置Service业务层处理 + * + * @author ruoyi + * @date 2024-01-01 + */ +@RequiredArgsConstructor +@Service +public class XljkConsulterServiceImpl implements IXljkConsulterService { + + private final XljkConsulterMapper baseMapper; + private final ISysUserService sysUserService; + private final ISysRoleService sysRoleService; + private final SysUserRoleMapper sysUserRoleMapper; + + /** + * 查询心理咨询师配置 + */ + @Override + public XljkConsulterVo queryById(Long id) { + XljkConsulter entity = baseMapper.selectById(id); + if (entity == null) { + return null; + } + return MapstructUtils.convert(entity, XljkConsulterVo.class); + } + + /** + * 查询心理咨询师配置列表 + */ + @Override + public TableDataInfo queryPageList(XljkConsulterBo bo, PageQuery pageQuery) { + // 直接使用查询条件构建器 + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page entityPage = baseMapper.selectPage(pageQuery.build(), lqw); + + // 转换为 VO 对象 + List voList = new ArrayList<>(); + for (XljkConsulter entity : entityPage.getRecords()) { + XljkConsulterVo vo = MapstructUtils.convert(entity, XljkConsulterVo.class); + voList.add(vo); + } + + // 构建分页结果 + Page voPage = new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal()); + voPage.setRecords(voList); + + return TableDataInfo.build(voPage); + } + + /** + * 查询心理咨询师配置列表 + */ + @Override + public List queryList(XljkConsulterBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + List entityList = baseMapper.selectList(lqw); + + // 转换为 VO 对象 + List voList = new ArrayList<>(); + for (XljkConsulter entity : entityList) { + XljkConsulterVo vo = MapstructUtils.convert(entity, XljkConsulterVo.class); + voList.add(vo); + } + + return voList; + } + + private LambdaQueryWrapper buildQueryWrapper(XljkConsulterBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(bo.getUserId() != null, XljkConsulter::getUserId, bo.getUserId()); + lqw.like(StringUtils.isNotBlank(bo.getUserName()), XljkConsulter::getUserName, bo.getUserName()); + lqw.like(StringUtils.isNotBlank(bo.getNickName()), XljkConsulter::getNickName, bo.getNickName()); + lqw.eq(bo.getDeptId() != null, XljkConsulter::getDeptId, bo.getDeptId()); + lqw.eq(StringUtils.isNotBlank(bo.getStatus()), XljkConsulter::getStatus, bo.getStatus()); + lqw.eq(bo.getConfigBy() != null, XljkConsulter::getConfigBy, bo.getConfigBy()); + lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null, + XljkConsulter::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime")); + lqw.orderByDesc(XljkConsulter::getCreateTime); + return lqw; + } + + /** + * 新增心理咨询师配置 + */ + @Override + public Boolean insertByBo(XljkConsulterBo bo) { + XljkConsulter add = MapstructUtils.convert(bo, XljkConsulter.class); + validEntityBeforeSave(add); + + // 设置配置人信息 + Long currentUserId = LoginHelper.getUserId(); + add.setConfigBy(currentUserId); + + // 获取当前用户信息设置配置人姓名 + try { + SysUserVo currentUser = sysUserService.selectUserById(currentUserId); + if (currentUser != null) { + add.setConfigByName(currentUser.getUserName()); + } + } catch (Exception e) { + // 如果获取失败,使用默认值 + add.setConfigByName("系统管理员"); + } + + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改心理咨询师配置 + */ + @Override + public Boolean updateByBo(XljkConsulterBo bo) { + XljkConsulter update = MapstructUtils.convert(bo, XljkConsulter.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(XljkConsulter entity) { + if (entity.getUserId() != null) { + // 检查用户是否已存在(新增时或修改用户ID时) + if (entity.getId() == null) { + // 新增时检查 + int count = baseMapper.checkUserExists(entity.getUserId()); + if (count > 0) { + throw new ServiceException("该用户已配置为咨询师,不能重复添加"); + } + } + } + } + + /** + * 批量删除心理咨询师配置 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids) { + return baseMapper.deleteBatchIds(ids) > 0; + } + + /** + * 批量添加咨询师 + */ + @Override + public Boolean batchAddConsulters(List userIds, String remark) { + if (userIds == null || userIds.isEmpty()) { + throw new ServiceException("用户ID列表不能为空"); + } + + Long currentUserId = LoginHelper.getUserId(); + String currentUserName = "系统管理员"; + + // 获取当前用户信息 + try { + SysUserVo currentUser = sysUserService.selectUserById(currentUserId); + if (currentUser != null) { + currentUserName = currentUser.getUserName(); + } + } catch (Exception e) { + // 使用默认值 + } + + // 1. 获取完整的用户信息(包含部门信息) + List users = baseMapper.selectUsersWithDeptInfo(userIds); + if (users.isEmpty()) { + throw new ServiceException("没有找到有效的用户信息"); + } + + // 2. 过滤已存在的咨询师 + List existingUserIds = baseMapper.selectList(Wrappers.lambdaQuery() + .in(XljkConsulter::getUserId, userIds)) + .stream().map(XljkConsulter::getUserId).collect(Collectors.toList()); + + // 3. 构建新增咨询师列表 + List consulterList = new ArrayList<>(); + for (XljkUserWithDeptInfo user : users) { + if (!existingUserIds.contains(user.getUserId())) { + XljkConsulter consulter = new XljkConsulter(); + consulter.setUserId(user.getUserId()); + consulter.setUserName(user.getUserName()); + consulter.setNickName(user.getNickName()); + consulter.setDeptId(user.getDeptId()); + consulter.setDeptName(user.getDeptName() != null ? user.getDeptName() : ""); + consulter.setPhoneNumber(user.getPhonenumber()); + consulter.setEmail(user.getEmail()); + consulter.setStatus("1"); // 默认正常状态 + consulter.setRemark(remark); + consulter.setConfigBy(currentUserId); + consulter.setConfigByName(currentUserName); + consulterList.add(consulter); + } + } + + // 4. 批量保存 + if (consulterList.isEmpty()) { + throw new ServiceException("所有用户都已存在,无需重复添加"); + } + + boolean result = baseMapper.insertBatch(consulterList); + + // 5. 为新添加的咨询师自动分配 xljk_consultant 角色 + if (result) { + assignConsultantRole(consulterList); + } + + return result; + } + + /** + * 为咨询师分配 xljk_consultant 角色 + */ + private void assignConsultantRole(List consulterList) { + try { + // 1. 根据角色键查询角色信息 + SysRoleBo roleBo = new SysRoleBo(); + roleBo.setRoleKey("xljk_consultant"); + List roleList = sysRoleService.selectRoleList(roleBo); + + if (roleList == null || roleList.isEmpty()) { + throw new ServiceException("未找到角色键为 'xljk_consultant' 的角色,请联系管理员创建该角色"); + } + + Long roleId = roleList.get(0).getRoleId(); + + // 2. 为每个用户分配角色 + List userRoleList = new ArrayList<>(); + for (XljkConsulter consulter : consulterList) { + // 检查用户是否已有该角色 + Long count = sysUserRoleMapper.selectCount( + Wrappers.lambdaQuery() + .eq(SysUserRole::getUserId, consulter.getUserId()) + .eq(SysUserRole::getRoleId, roleId) + ); + + // 如果用户还没有该角色,则添加 + if (count == 0) { + SysUserRole userRole = new SysUserRole(); + userRole.setUserId(consulter.getUserId()); + userRole.setRoleId(roleId); + userRoleList.add(userRole); + } + } + + // 3. 批量插入用户角色关联 + if (!userRoleList.isEmpty()) { + sysUserRoleMapper.insertBatch(userRoleList); + System.out.println("成功为 " + userRoleList.size() + " 个用户分配了 'xljk_consultant' 角色"); + } + + } catch (Exception e) { + // 记录错误但不影响主流程 + System.err.println("为咨询师分配角色失败: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * 查询我配置的咨询师列表 + */ + @Override + public TableDataInfo queryMyConfigList(XljkConsulterBo bo, PageQuery pageQuery) { + bo.setConfigBy(LoginHelper.getUserId()); + return queryPageList(bo, pageQuery); + } + + /** + * 批量启用咨询师 + */ + @Override + public Boolean enableConsulters(List ids) { + if (ids == null || ids.isEmpty()) { + return false; + } + return baseMapper.enableConsultersByIds(ids) > 0; + } + + /** + * 批量停用咨询师 + */ + @Override + public Boolean disableConsulters(List ids) { + if (ids == null || ids.isEmpty()) { + return false; + } + return baseMapper.disableConsultersByIds(ids) > 0; + } + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/resources/mapper/XljkConsulterMapper.xml b/backend/ruoyi-modules/ruoyi-xljk/src/main/resources/mapper/XljkConsulterMapper.xml new file mode 100644 index 00000000..529b791d --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/resources/mapper/XljkConsulterMapper.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + select id, user_id, user_name, nick_name, dept_id, dept_name, phone_number, office, email, status, remark, config_by, config_by_name, tenant_id, create_dept, create_by, create_time, update_by, update_time from xljk_consulter + + + + + + + + + + + + UPDATE xljk_consulter SET status = '1' WHERE id IN + + #{id} + + AND deleted = 0 + + + + + UPDATE xljk_consulter SET status = '0' WHERE id IN + + #{id} + + AND deleted = 0 + + + + + + \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/controller/XskhDisciplineRecordController.java b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/controller/XskhDisciplineRecordController.java index 58daab72..be7e95fc 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/controller/XskhDisciplineRecordController.java +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/controller/XskhDisciplineRecordController.java @@ -151,6 +151,19 @@ public class XskhDisciplineRecordController extends BaseController { @RequestParam String disciplineTypeIds, @RequestParam String startTime, @RequestParam String endTime) { - return R.ok(xskhDisciplineRecordService.getStudentDisciplineRecords(studentId, disciplineTypeIds, startTime, endTime)); + return R.ok(xskhDisciplineRecordService.getStudentDisciplineRecords(studentId, disciplineTypeIds, startTime, + endTime)); + } + + /** + * 获取指定违纪类型在指定时间范围内所有学生的最高总扣分 + */ + @SaCheckPermission("disciplineRecord:disciplineRecord:query") + @GetMapping("/getMaxScoreByTypeAndPeriod/{typeId}") + public R getMaxScoreByTypeAndPeriod( + @NotNull(message = "违纪类型ID不能为空") @PathVariable Long typeId, + @RequestParam String startTime, + @RequestParam String endTime) { + return R.ok(xskhDisciplineRecordService.getMaxScoreByTypeAndPeriod(typeId, startTime, endTime)); } } diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/controller/XskhRewardRecordController.java b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/controller/XskhRewardRecordController.java index c6375b5c..a8e2a19b 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/controller/XskhRewardRecordController.java +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/controller/XskhRewardRecordController.java @@ -143,4 +143,16 @@ public class XskhRewardRecordController extends BaseController { @RequestParam String endTime) { return R.ok(xskhRewardRecordService.getStudentRewardRecords(studentId, rewardTypeIds, startTime, endTime)); } + + /** + * 获取指定奖励类型在指定时间范围内所有学生的最高总加分 + */ + @SaCheckPermission("rewardRecord:rewardRecord:query") + @GetMapping("/getMaxScoreByTypeAndPeriod/{typeId}") + public R getMaxScoreByTypeAndPeriod( + @NotNull(message = "奖励类型ID不能为空") @PathVariable Long typeId, + @RequestParam String startTime, + @RequestParam String endTime) { + return R.ok(xskhRewardRecordService.getMaxScoreByTypeAndPeriod(typeId, startTime, endTime)); + } } diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/mapper/XskhDisciplineRecordMapper.java b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/mapper/XskhDisciplineRecordMapper.java index 3557020d..75db4990 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/mapper/XskhDisciplineRecordMapper.java +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/mapper/XskhDisciplineRecordMapper.java @@ -69,4 +69,16 @@ public interface XskhDisciplineRecordMapper extends BaseMapperPlus rewardTypeIds, @Param("startTime") String startTime, @Param("endTime") String endTime); + + /** + * 获取指定奖励类型在指定时间范围内所有学生的最高总加分 + * + * @param typeId 奖励类型ID + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 最高总加分 + */ + java.math.BigDecimal selectMaxScoreByTypeAndPeriod(@Param("typeId") Long typeId, + @Param("startTime") String startTime, + @Param("endTime") String endTime); } diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/IXskhDisciplineRecordService.java b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/IXskhDisciplineRecordService.java index f6a5ad67..4f061dce 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/IXskhDisciplineRecordService.java +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/IXskhDisciplineRecordService.java @@ -91,4 +91,14 @@ public interface IXskhDisciplineRecordService { */ List getStudentDisciplineRecords(Long studentId, String disciplineTypeIds, String startTime, String endTime); + + /** + * 获取指定违纪类型在指定时间范围内所有学生的最高总扣分 + * + * @param typeId 违纪类型ID + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 最高总扣分 + */ + java.math.BigDecimal getMaxScoreByTypeAndPeriod(Long typeId, String startTime, String endTime); } diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/IXskhRewardRecordService.java b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/IXskhRewardRecordService.java index 4112809f..dd68d0c3 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/IXskhRewardRecordService.java +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/IXskhRewardRecordService.java @@ -104,4 +104,14 @@ public interface IXskhRewardRecordService { */ List getStudentRewardRecords(Long studentId, String rewardTypeIds, String startTime, String endTime); + + /** + * 获取指定奖励类型在指定时间范围内所有学生的最高总加分 + * + * @param typeId 奖励类型ID + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 最高总加分 + */ + java.math.BigDecimal getMaxScoreByTypeAndPeriod(Long typeId, String startTime, String endTime); } diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhDisciplineRecordServiceImpl.java b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhDisciplineRecordServiceImpl.java index 9f12aaf5..97a73cbf 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhDisciplineRecordServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhDisciplineRecordServiceImpl.java @@ -267,4 +267,23 @@ public class XskhDisciplineRecordServiceImpl implements IXskhDisciplineRecordSer return baseMapper.selectStudentDisciplineRecords(studentId, typeIds, startTime, endTime); } + + /** + * 获取指定违纪类型在指定时间范围内所有学生的最高总扣分 + */ + @Override + public java.math.BigDecimal getMaxScoreByTypeAndPeriod(Long typeId, String startTime, String endTime) { + log.info("获取违纪类型 {} 在时间范围 {} - {} 内的最高总扣分", typeId, startTime, endTime); + + java.math.BigDecimal maxScore = baseMapper.selectMaxScoreByTypeAndPeriod(typeId, startTime, endTime); + + // 如果查询结果为null,返回0 + if (maxScore == null) { + log.info("违纪类型 {} 在指定时间范围内无数据,返回0", typeId); + return java.math.BigDecimal.ZERO; + } + + log.info("违纪类型 {} 的最高总扣分为: {}", typeId, maxScore); + return maxScore; + } } diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhRewardRecordServiceImpl.java b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhRewardRecordServiceImpl.java index ecbd0688..e637eb82 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhRewardRecordServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhRewardRecordServiceImpl.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Arrays; import java.util.stream.Collectors; import java.util.Map; +import lombok.extern.slf4j.Slf4j; /** * 学生奖励记录Service业务层处理 @@ -31,6 +32,7 @@ import java.util.Map; * @author ruoyi * @date 2025-01-03 */ +@Slf4j @RequiredArgsConstructor @Service public class XskhRewardRecordServiceImpl implements IXskhRewardRecordService { @@ -249,4 +251,23 @@ public class XskhRewardRecordServiceImpl implements IXskhRewardRecordService { return baseMapper.selectStudentRewardRecords(studentId, typeIds, startTime, endTime); } + + /** + * 获取指定奖励类型在指定时间范围内所有学生的最高总加分 + */ + @Override + public java.math.BigDecimal getMaxScoreByTypeAndPeriod(Long typeId, String startTime, String endTime) { + log.info("获取奖励类型 {} 在时间范围 {} - {} 内的最高总加分", typeId, startTime, endTime); + + java.math.BigDecimal maxScore = baseMapper.selectMaxScoreByTypeAndPeriod(typeId, startTime, endTime); + + // 如果查询结果为null,返回0 + if (maxScore == null) { + log.info("奖励类型 {} 在指定时间范围内无数据,返回0", typeId); + return java.math.BigDecimal.ZERO; + } + + log.info("奖励类型 {} 的最高总加分为: {}", typeId, maxScore); + return maxScore; + } } diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/XskhDisciplineRecordMapper.xml b/backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/XskhDisciplineRecordMapper.xml index 9713a923..ac51253f 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/XskhDisciplineRecordMapper.xml +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/XskhDisciplineRecordMapper.xml @@ -134,4 +134,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" and deleted = 0 + + + diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/xskh/XskhRewardRecordMapper.xml b/backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/xskh/XskhRewardRecordMapper.xml index 2b4783b5..1d78d650 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/xskh/XskhRewardRecordMapper.xml +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/xskh/XskhRewardRecordMapper.xml @@ -151,4 +151,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" order by create_time desc + + + diff --git a/docs/业务模块/学工管理-宿舍管理/数据库设计/宿舍管理数据库设计.md b/docs/业务模块/学工管理-宿舍管理/数据库设计/宿舍管理数据库设计.md deleted file mode 100644 index fae3231f..00000000 --- a/docs/业务模块/学工管理-宿舍管理/数据库设计/宿舍管理数据库设计.md +++ /dev/null @@ -1,416 +0,0 @@ -# 宿舍管理数据库设计文档 - -## 📋 文档信息 - -| 项目 | 信息 | -|------|------| -| **项目名称** | 校园管理系统 - 宿舍管理模块 | -| **数据库版本** | MySQL 8.0+ | -| **文档版本** | V1.0.0 | -| **创建时间** | 2025-01-14 | -| **创建人员** | 数据库设计团队 | -| **审核状态** | 待审核 | - -## 🎯 设计目标 - -### 核心目标 -- 支持多维度宿舍管理人员分配(全校、校区、宿舍区、宿舍楼、二级学院) -- 实现多级向下授权功能(5级权限体系) -- 支持权限的动态配置和调整 -- 提供完整的管理效果评估功能 -- 确保数据安全和审计追踪 - -### 设计原则 -- **分层设计**:按权限层级进行数据分层 -- **维度扩展**:支持管理维度的灵活扩展 -- **权限控制**:严格的数据权限控制机制 -- **审计追踪**:完整的操作日志和变更记录 -- **性能优化**:针对多维度查询的性能优化 - -## 🗄️ 数据库表设计 - -### 1. 宿舍管理维度表 - -#### 1.1 宿舍管理维度定义表 (dorm_management_dimension) -**表说明**:定义宿舍管理的各个维度,支持维度的灵活扩展 - -```sql - -``` - -#### 1.2 校区信息表 (dorm_campus) -**表说明**:存储校区基本信息 - -```sql -DROP TABLE IF EXISTS `xg_dorm_campus`; -CREATE TABLE `xg_dorm_campus` ( - `id` bigint(20) NOT NULL COMMENT '主键ID', - `campus_code` varchar(50) NOT NULL COMMENT '校区编码', - `campus_name` varchar(100) NOT NULL COMMENT '校区名称', - `campus_description` varchar(500) DEFAULT NULL COMMENT '校区描述', - `sort_order` int(11) DEFAULT '0' COMMENT '排序号', - `deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', - `tenant_id` bigint(20) DEFAULT '0' COMMENT '租户编号', - `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) DEFAULT NULL COMMENT '更新者', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_campus_code` (`campus_code`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='校区信息表'; -``` - -#### 1.3 宿舍区信息表 (dorm_area) -**表说明**:存储宿舍区基本信息,关联校区 - -```sql -DROP TABLE IF EXISTS `xg_dorm_building`; -CREATE TABLE `xg_dorm_building` ( - `id` bigint(20) NOT NULL COMMENT '主键ID', - `building_code` varchar(50) NOT NULL COMMENT '宿舍楼编码', - `building_name` varchar(100) NOT NULL COMMENT '宿舍楼名称', - `area_id` bigint(20) NOT NULL COMMENT '所属宿舍区ID', - `campus_id` bigint(20) NOT NULL COMMENT '所属校区ID', - `building_type` char(1) DEFAULT '1' COMMENT '宿舍楼类型(1男生楼 2女生楼 3混合楼)', - `building_description` varchar(500) DEFAULT NULL COMMENT '宿舍楼描述', - `sort_order` int(11) DEFAULT '0' COMMENT '排序号', - `deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', - `tenant_id` bigint(20) DEFAULT '0' COMMENT '租户编号', - `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) DEFAULT NULL COMMENT '更新者', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_building_code` (`building_code`), - KEY `idx_area_id` (`area_id`), - KEY `idx_campus_id` (`campus_id`), -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宿舍楼信息表'; -``` - -#### 1.4 宿舍楼信息表 (dorm_building) -**表说明**:存储宿舍楼基本信息,关联宿舍区 - -```sql -DROP TABLE IF EXISTS `xg_dorm_area`; -CREATE TABLE `xg_dorm_area` ( - `id` bigint(20) NOT NULL COMMENT '主键ID', - `area_code` varchar(50) NOT NULL COMMENT '宿舍区编码', - `area_name` varchar(100) NOT NULL COMMENT '宿舍区名称', - `campus_id` bigint(20) NOT NULL COMMENT '所属校区ID', - `area_description` varchar(500) DEFAULT NULL COMMENT '宿舍区描述', - `sort_order` int(11) DEFAULT '0' COMMENT '排序号', - `deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', - `tenant_id` bigint(20) DEFAULT '0' COMMENT '租户编号', - `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) DEFAULT NULL COMMENT '更新者', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_area_code` (`area_code`), - KEY `idx_campus_id` (`campus_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宿舍区信息表'; -``` - -#### 1.5 宿舍房间表 (dorm_room) -**表说明**:存储宿舍房间基本信息,关联宿舍楼,包含楼层属性 - -```sql -DROP TABLE IF EXISTS `xg_dorm_room`; -CREATE TABLE `xg_dorm_room` ( - `id` bigint(20) NOT NULL COMMENT '主键ID', - `room_code` varchar(50) NOT NULL COMMENT '房间编号', - `room_name` varchar(100) NOT NULL COMMENT '房间名称', - `building_id` bigint(20) NOT NULL COMMENT '所属宿舍楼ID', - `floor_num` int(11) NOT NULL COMMENT '楼层', - `room_type` char(1) DEFAULT '1' COMMENT '房间类型(1学生宿舍 3教师宿舍)', - `actual_beds` int(11) DEFAULT 4 COMMENT '实际床位数', - `room_area` decimal(10,2) DEFAULT NULL COMMENT '房间面积(平方米)', - `room_status` char(1) DEFAULT '0' COMMENT '房间状态(0可用 1维修中 2已报废)', - `deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', - `tenant_id` bigint(20) DEFAULT '0' COMMENT '租户编号', - `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) DEFAULT NULL COMMENT '更新者', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_room_code` (`room_code`), - KEY `idx_building_id` (`building_id`), - KEY `idx_floor_num` (`floor_num`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宿舍房间信息表'; -``` - -#### 1.6 宿舍床位表 (dorm_bed) -**表说明**:存储宿舍床位基本信息,关联宿舍房间 - -```sql -DROP TABLE IF EXISTS `xg_dorm_bed`; -CREATE TABLE `xg_dorm_bed` ( - `id` bigint(20) NOT NULL COMMENT '主键ID', - `bed_code` varchar(50) NOT NULL COMMENT '床位编号', - `bed_name` varchar(100) NOT NULL COMMENT '床位名称', - `room_id` bigint(20) NOT NULL COMMENT '所属房间ID', - `bed_type` char(1) DEFAULT '1' COMMENT '床位类型(1下铺 2上铺 3中铺 4独立床)', - `bed_status` char(1) DEFAULT '0' COMMENT '床位状态(0空闲 1已分配 2维修中)', - `deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', - `tenant_id` bigint(20) DEFAULT '0' COMMENT '租户编号', - `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) DEFAULT NULL COMMENT '更新者', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_bed_code` (`bed_code`), - KEY `idx_room_id` (`room_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宿舍床位信息表'; - -DROP TABLE IF EXISTS `xg_dorm_bed_student`; -CREATE TABLE `xg_dorm_bed_student` ( - `id` bigint(20) NOT NULL COMMENT '主键ID', - `college_id` bigint(20) NOT NULL COMMENT '分配学院ID', - `student_id` bigint(20) NOT NULL COMMENT '分配学生ID', - `bed_id` bigint(20) NOT NULL COMMENT '床位ID', - `room_id` bigint(20) NOT NULL COMMENT '房间ID', - `building_id` bigint(20) NOT NULL COMMENT '宿舍楼ID', - `area_id` bigint(20) NOT NULL COMMENT '宿舍区ID', - `campus_id` bigint(20) NOT NULL COMMENT '校区ID', - `status` char(1) DEFAULT '0' COMMENT '状态(0正常 1已退宿)', - `assign_time` datetime NOT NULL COMMENT '分配时间', - `checkout_time` datetime DEFAULT NULL COMMENT '退宿时间', - `deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', - `tenant_id` bigint(20) DEFAULT '0' COMMENT '租户编号', - `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) DEFAULT NULL COMMENT '更新者', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_student_bed` (`student_id`, `bed_id`), - KEY `idx_college_id` (`college_id`), - KEY `idx_student_id` (`student_id`), - KEY `idx_bed_id` (`bed_id`), - KEY `idx_room_id` (`room_id`), - KEY `idx_building_id` (`building_id`), - KEY `idx_area_id` (`area_id`), - KEY `idx_campus_id` (`campus_id`), - KEY `idx_assign_time` (`assign_time`), -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='床位学生分配关系表'; -``` - -### 2. 宿舍管理人员相关表 - -#### 2.1 宿舍管理员表 (dorm_manager) -**表说明**:存储全校、校区、宿舍区、宿舍楼管理员的基本信息 - -```sql -DROP TABLE IF EXISTS `xg_dorm_manager`; -CREATE TABLE `xg_dorm_manager` ( - `id` bigint(20) NOT NULL COMMENT '主键ID', - `manager_name` varchar(100) NOT NULL COMMENT '管理员姓名', - `manager_type` char(1) NOT NULL COMMENT '管理员类型(1全校管理员 2校区管理员 3宿舍区管理员 4宿舍楼管理员)', - `manager_level` int(11) DEFAULT 1 COMMENT '管理级别(1-5,1为最高)', - `user_id` bigint(20) DEFAULT NULL COMMENT '关联用户ID', - `phone` varchar(20) DEFAULT NULL COMMENT '联系电话', - `gender` char(1) DEFAULT '0' COMMENT '性别(0男 1女 2未知)', - `status` char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)', - `deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', - `tenant_id` bigint(20) DEFAULT '0' COMMENT '租户编号', - `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) DEFAULT NULL COMMENT '更新者', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`id`), - KEY `idx_user_id` (`user_id`), - KEY `idx_manager_type` (`manager_type`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宿舍管理员信息表'; - -DROP TABLE IF EXISTS `xg_dorm_manager_source`; -CREATE TABLE `xg_dorm_manager_source` ( - `id` bigint(20) NOT NULL COMMENT '主键ID', - `manager_id` bigint(20) NOT NULL COMMENT '管理员ID', - `permission_type` char(1) DEFAULT '1' COMMENT '权限类型(1查看 2编辑 3完全管理)', - `auth_type` char(1) NOT NULL COMMENT '授权类型(1全校级 2校区级 3宿舍区级 4楼栋级)', - `auth_source` bigint(20) DEFAULT NULL COMMENT '授权来源ID(对应校区ID或宿舍区ID或楼栋ID)', - `status` char(1) DEFAULT '0' COMMENT '状态(0有效 1已撤销)', - `deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', - `tenant_id` bigint(20) DEFAULT '0' COMMENT '租户编号', - `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) DEFAULT NULL COMMENT '更新者', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`id`), - KEY `idx_manager_id` (`manager_id`), - KEY `idx_auth_source` (`auth_source`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员授权关系表'; - -``` - -#### 2.2 管理员宿舍楼权限关系表 (dorm_manager_building) -**表说明**:存储管理员与宿舍楼的权限绑定关系 - -```sql - - -DROP TABLE IF EXISTS `xg_dorm_manager_building`; -CREATE TABLE `xg_dorm_manager_building` ( - `id` bigint(20) NOT NULL COMMENT '主键ID', - `manager_id` bigint(20) NOT NULL COMMENT '管理员ID', - `building_id` bigint(20) NOT NULL COMMENT '宿舍楼ID', - `status` char(1) DEFAULT '0' COMMENT '状态(0有效 1已撤销)', - `deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', - `tenant_id` bigint(20) DEFAULT '0' COMMENT '租户编号', - `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) DEFAULT NULL COMMENT '更新者', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_manager_building` (`manager_id`, `building_id`), - KEY `idx_manager_id` (`manager_id`), - KEY `idx_building_id` (`building_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员宿舍楼权限关系表'; -``` - -#### 2.3 二级学院宿舍管理员表 (dorm_college_manager) -**表说明**:存储二级学院宿舍管理员的基本信息 - -```sql -DROP TABLE IF EXISTS `xg_dorm_college_manager`; -CREATE TABLE `xg_dorm_college_manager` ( - `id` bigint(20) NOT NULL COMMENT '主键ID', - `manager_name` varchar(100) NOT NULL COMMENT '管理员姓名', - `college_id` bigint(20) NOT NULL COMMENT '所属学院ID', - `user_id` bigint(20) DEFAULT NULL COMMENT '关联用户ID', - `phone` varchar(20) DEFAULT NULL COMMENT '联系电话', - `gender` char(1) DEFAULT '0' COMMENT '性别(0男 1女 2未知)', - `status` char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)', - `deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', - `tenant_id` bigint(20) DEFAULT '0' COMMENT '租户编号', - `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) DEFAULT NULL COMMENT '更新者', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`id`), - KEY `idx_college_id` (`college_id`), - KEY `idx_user_id` (`user_id`), -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='二级学院宿舍管理员信息表'; -``` - -#### 2.4 二级学院床位绑定关系表 (dorm_college_bed) -**表说明**:存储二级学院与床位的绑定关系 - -```sql -DROP TABLE IF EXISTS `xg_dorm_college_bed`; -CREATE TABLE `xg_dorm_college_bed` ( - `id` bigint(20) NOT NULL COMMENT '主键ID', - `college_id` bigint(20) NOT NULL COMMENT '学院ID', - `bed_id` bigint(20) NOT NULL COMMENT '床位ID', - `building_id` bigint(20) NOT NULL COMMENT '宿舍楼ID', - `room_id` bigint(20) NOT NULL COMMENT '房间ID', - `status` char(1) DEFAULT '0' COMMENT '状态(0正常 1已解除)', - `assign_time` datetime DEFAULT NULL COMMENT '分配时间', - `release_time` datetime DEFAULT NULL COMMENT '到期解除时间', - `deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', - `tenant_id` bigint(20) DEFAULT '0' COMMENT '租户编号', - `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) DEFAULT NULL COMMENT '更新者', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_college_bed` (`college_id`, `bed_id`), - KEY `idx_college_id` (`college_id`), - KEY `idx_bed_id` (`bed_id`), - KEY `idx_building_id` (`building_id`), - KEY `idx_room_id` (`room_id`), -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='二级学院床位绑定关系表'; -``` - - - -## 🔗 表关系设计 - -### 1. 维度关系图 -``` -dorm_campus (校区) -├── dorm_area (宿舍区) -│ └── dorm_building (宿舍楼) -└── dorm_college (二级学院) -``` - -### 2. 权限层级关系图 -``` -dorm_permission_level (权限层级) -├── Level 1: 学生处宿舍管理负责人 -├── Level 2: 校区宿舍管理负责人 -├── Level 3: 宿舍区主管 -├── Level 4: 宿舍楼管理员 -└── Level 5: 二级学院宿舍管理负责人 -``` - - -## 📊 索引设计 - -### 1. 主键索引 -- 所有表都使用 `bigint(20)` 类型的自增主键 -- 主键命名统一为 `id` - -### 2. 唯一索引 -- 编码字段:`manager_code`, `campus_code`, `area_code`, `building_code`, `college_code` -- 身份证号:`id_card` -- 权限层级值:`level_value` - -### 3. 复合索引 -- 管理人员分配:`(manager_id, dimension_type, dimension_id)` -- 权限配置:`(manager_id, permission_level_id, dimension_type)` -- 统计数据:`(manager_id, dimension_type, dimension_id, stats_date)` - -### 4. 普通索引 -- 状态字段:`status`, `approval_status`, `permission_status` -- 时间字段:`create_time`, `update_time`, `operation_time` -- 关联字段:`campus_id`, `area_id`, `position_id` - -## 🔒 数据安全设计 - -### 1. 敏感信息加密 -- 身份证号:使用AES加密存储 -- 手机号码:部分脱敏显示 -- 邮箱地址:部分脱敏显示 - -### 2. 权限控制 -- 数据权限:基于维度的数据访问控制 -- 功能权限:基于角色的功能访问控制 -- 操作权限:基于权限层级的操作控制 - -### 3. 审计日志 -- 权限变更:完整的权限变更记录 -- 操作日志:详细的操作行为记录 -- 数据变更:重要数据的变更追踪 - -## 📈 性能优化设计 - -### 1. 分区策略 -- 按时间分区:`dorm_permission_usage` 表按月份分区 -- 按维度分区:`dorm_management_stats` 表按维度分区 - -### 2. 缓存策略 -- 权限缓存:管理人员权限信息缓存 -- 维度缓存:维度信息缓存 -- 统计缓存:统计数据缓存 - -### 3. 查询优化 -- 索引优化:针对多维度查询的复合索引 -- SQL优化:避免全表扫描的查询语句 -- 分页优化:大数据量的分页查询优化 - -## 📝 变更记录 - -| 版本 | 日期 | 变更内容 | 变更人 | -|------|------|----------|--------| -| V1.0.0 | 2025-01-14 | 初始版本创建 | 数据库设计团队 | \ No newline at end of file diff --git a/docs/开发文档/API/API文档总览.md b/docs/开发文档/API/API文档总览.md index 5d93d987..06e458c5 100644 --- a/docs/开发文档/API/API文档总览.md +++ b/docs/开发文档/API/API文档总览.md @@ -20,6 +20,7 @@ - [学生管理员授权关系API](./学生管理员授权关系API.md) - [学生销假管理API](./学生销假管理API.md) - [假期留宿管理API](./假期留宿管理API.md) - **重要修复:单人留宿计算逻辑**(2025-01-03) +- [均化处理最高分值API](./均化处理最高分值API.md) - **新增**:支持考核申请均化处理功能(2025-01-03) ### 1. 用户认证登录 diff --git a/docs/设计文档/database/hzdyjs_xg0820.sql b/docs/设计文档/database/hzdyjs_xg0820.sql index 05034694..0897434b 100644 --- a/docs/设计文档/database/hzdyjs_xg0820.sql +++ b/docs/设计文档/database/hzdyjs_xg0820.sql @@ -4473,29 +4473,6 @@ CREATE TABLE `xg_dorm_manager` ( KEY `idx_manager_type` (`manager_type`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宿舍管理员信息表'; --- ---------------------------- --- Table structure for xg_dorm_manager_building --- ---------------------------- -DROP TABLE IF EXISTS `xg_dorm_manager_building`; -CREATE TABLE `xg_dorm_manager_building` ( - `id` bigint(20) NOT NULL COMMENT '主键ID', - `manager_id` bigint(20) NOT NULL COMMENT '管理员ID', - `building_id` bigint(20) NOT NULL COMMENT '宿舍楼ID', - `status` char(1) DEFAULT '0' COMMENT '状态(0有效 1已撤销)', - `deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', - `tenant_id` bigint(20) DEFAULT '0' COMMENT '租户编号', - `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', - `create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) DEFAULT NULL COMMENT '更新者', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_manager_building` (`manager_id`,`building_id`), - KEY `idx_manager_id` (`manager_id`), - KEY `idx_building_id` (`building_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员宿舍楼权限关系表'; - -- ---------------------------- -- Table structure for xg_dorm_manager_source -- ---------------------------- diff --git a/docs/设计文档/database/学生调宿退宿/学生宿舍申请单表结构.sql b/docs/设计文档/database/学生调宿退宿/学生宿舍申请单表结构.sql new file mode 100644 index 00000000..8b89aed4 --- /dev/null +++ b/docs/设计文档/database/学生调宿退宿/学生宿舍申请单表结构.sql @@ -0,0 +1,121 @@ +-- ========================================== +-- 学生宿舍申请单数据库脚本(调宿+退宿) +-- 创建时间:2025-01-15 +-- 设计目标:统一的调宿和退宿申请单表 +-- ========================================== + +-- ---------------------------- +-- 1. 主表:学生宿舍申请单表 +-- ---------------------------- + +DROP TABLE IF EXISTS `xg_dorm_application`; +CREATE TABLE `xg_dorm_application` ( + `id` bigint(20) NOT NULL COMMENT '主键ID(雪花ID)', + + -- 申请基本信息 + `application_name` varchar(50) NOT NULL COMMENT '申请名称', + `application_type` char(1) NOT NULL COMMENT '申请类型(1:调宿申请 2:退宿申请)', + `application_status` char(20) DEFAULT 'waiting' COMMENT '审批状态:cancel-已撤销 , draft-草稿, waiting-待审核, finish-已完成, invalid-已作废, back-已退回, termination-已终止"', + + -- 学生信息 + `student_id` bigint(20) NOT NULL COMMENT '申请学生ID', + `student_no` varchar(20) NOT NULL COMMENT '学号', + `student_name` varchar(50) NOT NULL COMMENT '学生姓名', + `student_phone` varchar(20) DEFAULT NULL COMMENT '学生联系电话', + `college_id` bigint(20) NOT NULL COMMENT '学院ID', + `college_name` varchar(100) DEFAULT NULL COMMENT '学院名称', + `major_id` bigint(20) DEFAULT NULL COMMENT '专业ID', + `major_name` varchar(100) DEFAULT NULL COMMENT '专业名称', + `class_id` bigint(20) DEFAULT NULL COMMENT '班级ID', + `class_name` varchar(100) DEFAULT NULL COMMENT '班级名称', + + -- 当前宿舍信息(原宿舍/退宿的宿舍) + `current_campus_id` bigint(20) DEFAULT NULL COMMENT '当前校区ID', + `current_campus_name` varchar(100) DEFAULT NULL COMMENT '当前校区名称', + `current_building_id` bigint(20) DEFAULT NULL COMMENT '当前宿舍楼ID', + `current_building_name` varchar(100) DEFAULT NULL COMMENT '当前宿舍楼名称', + `current_room_id` bigint(20) DEFAULT NULL COMMENT '当前房间ID', + `current_room_no` varchar(20) DEFAULT NULL COMMENT '当前房间号', + `current_bed_id` bigint(20) DEFAULT NULL COMMENT '当前床位ID', + `current_bed_no` varchar(10) DEFAULT NULL COMMENT '当前床位号', + + -- 目标宿舍信息(仅调宿申请使用,退宿申请为空) + `target_campus_id` bigint(20) DEFAULT NULL COMMENT '目标校区ID', + `target_campus_name` varchar(100) DEFAULT NULL COMMENT '目标校区名称', + `target_building_id` bigint(20) DEFAULT NULL COMMENT '目标宿舍楼ID', + `target_building_name` varchar(100) DEFAULT NULL COMMENT '目标宿舍楼名称', + `target_room_id` bigint(20) DEFAULT NULL COMMENT '目标房间ID', + `target_room_no` varchar(20) DEFAULT NULL COMMENT '目标房间号', + `target_bed_id` bigint(20) DEFAULT NULL COMMENT '目标床位ID', + `target_bed_no` varchar(10) DEFAULT NULL COMMENT '目标床位号', + + -- 申请原因和说明 + `reason_detail` text DEFAULT NULL COMMENT '详细原因说明', + + -- 时间相关 + `apply_time` datetime NOT NULL COMMENT '申请时间', + + -- 附件和备注 + `attachment_oss_ids` varchar(1000) DEFAULT NULL COMMENT '附件OSS文件ID,多个用逗号分隔', + `remark` varchar(500) DEFAULT NULL COMMENT '备注信息', + + -- 扩展字段 + `ext_field1` varchar(200) DEFAULT NULL COMMENT '扩展字段1', + `ext_field2` varchar(200) DEFAULT NULL COMMENT '扩展字段2', + `ext_field3` varchar(500) DEFAULT NULL COMMENT '扩展字段3', + + -- 系统字段(符合项目规范) + `deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', + `tenant_id` bigint(20) DEFAULT '0' COMMENT '租户编号', + `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', + `create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) DEFAULT NULL COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_tenant_id` (`tenant_id`), + KEY `idx_deleted` (`deleted`), + KEY `idx_student_id` (`student_id`), + KEY `idx_student_no` (`student_no`), + KEY `idx_application_type` (`application_type`), + KEY `idx_application_status` (`application_status`), + KEY `idx_apply_time` (`apply_time`), + KEY `idx_college_id` (`college_id`), + KEY `idx_current_building_id` (`current_building_id`), + KEY `idx_target_building_id` (`target_building_id`), + KEY `idx_tenant_deleted_status` (`tenant_id`, `deleted`, `application_status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生宿舍申请单表(调宿+退宿)'; + +-- ---------------------------- +-- 2. 字典类型配置 +-- ---------------------------- + +-- 申请类型字典 +INSERT INTO sys_dict_type (dict_id, dict_name, dict_type, status, create_by, create_time, remark) VALUES +(nextval('sys_dict_type_seq'), '宿舍申请类型', 'dorm_application_type', '0', 1, NOW(), '学生宿舍申请类型'); + +-- 申请类型字典数据 +INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark) VALUES +(nextval('sys_dict_data_seq'), 1, '调宿申请', '1', 'dorm_application_type', '', 'primary', 'N', '0', 1, NOW(), '学生调宿申请'), +(nextval('sys_dict_data_seq'), 2, '退宿申请', '2', 'dorm_application_type', '', 'warning', 'N', '0', 1, NOW(), '学生退宿申请'); + + + +-- ---------------------------- +-- 脚本说明 +-- ---------------------------- +/* +使用说明: +1. 执行前需要确认学工管理菜单的实际ID,替换脚本中的占位符 +2. 根据项目实际情况调整字典数据的dict_code值 +3. 测试数据仅供参考,实际使用时可删除 +4. 表设计遵循项目规范:雪花ID、逻辑删除、多租户支持 +5. 支持调宿和退宿两种申请类型,通过application_type字段区分 + +设计特点: +- 统一表结构:一个表同时支持调宿和退宿申请 +- 灵活字段:退宿申请时目标宿舍信息为空 +- 完整流程:支持从草稿到生效的完整状态流转 +- 性能优化:冗余存储宿舍名称,避免关联查询 +- 权限控制:完整的菜单和功能权限配置 +*/ \ No newline at end of file diff --git a/docs/设计文档/database/社团管理/社团管理数据库设计.md b/docs/设计文档/database/社团管理/社团管理数据库设计.md index aae58e51..a6c38e26 100644 --- a/docs/设计文档/database/社团管理/社团管理数据库设计.md +++ b/docs/设计文档/database/社团管理/社团管理数据库设计.md @@ -190,6 +190,8 @@ CREATE TABLE `stgl_activity` ( `location` varchar(200) COMMENT '活动地点', `description` text COMMENT '活动简介(1000字以内)', `poster_url` varchar(500) COMMENT '活动海报路径', + `related_dept_id` bigint(20) COMMENT '关联部门ID', + `related_dept_name` varchar(100) COMMENT '关联部门名称', `related_club_id` bigint(20) COMMENT '关联社团ID', -- 主办承办方信息 @@ -225,6 +227,7 @@ CREATE TABLE `stgl_activity` ( KEY `idx_end_time` (`end_time`), KEY `idx_activity_status` (`activity_status`), KEY `idx_registration_status` (`registration_status`), + KEY `idx_related_dept_id` (`related_dept_id`), KEY `idx_related_club_id` (`related_club_id`), KEY `idx_create_time` (`create_time`), KEY `idx_tenant_deleted` (`tenant_id`, `deleted`) diff --git a/mobile/src/api/xljk.js b/mobile/src/api/xljk.js index 926a3c1c..f2e21380 100644 --- a/mobile/src/api/xljk.js +++ b/mobile/src/api/xljk.js @@ -33,6 +33,19 @@ export const getAvailableDetailSlots = (date, timeSlot) => { }); }; +/** + * 获取日历视图的排班数据(带预约状态)- 直接从xljk_consultation_schedule表查询 + * @param {string} startDate 开始日期 YYYY-MM-DD + * @param {string} endDate 结束日期 YYYY-MM-DD + */ +export const getCalendarSchedules = (startDate, endDate) => { + return request({ + url: '/xljk/consultation/appointment/calendar-schedules', + method: 'GET', + params: { startDate, endDate } + }); +}; + // ===== 预约记录管理 API ===== /** @@ -171,6 +184,16 @@ export const getClassOptions = (collegeName, majorName) => { }); }; +/** + * 获取咨询主题选项(字典数据) + */ +export const getConsultThemeOptions = () => { + return request({ + url: '/system/dict/data/type/xljk_consult_theme', + method: 'GET' + }); +}; + // ===== 移动端专用辅助方法 ===== /** diff --git a/mobile/src/pages/xljk/my-appointments/index.vue b/mobile/src/pages/xljk/my-appointments/index.vue index efb6c804..a9e33ba4 100644 --- a/mobile/src/pages/xljk/my-appointments/index.vue +++ b/mobile/src/pages/xljk/my-appointments/index.vue @@ -19,11 +19,11 @@ {{ tab.label }} @@ -71,12 +71,15 @@ > - {{ appointment.counselorName || '未知咨询师' }} + + {{ appointment.counselorName || '未知咨询师' }} + {{ appointment.office || '暂无办公室信息' }} + {{ appointment.reason || '心理咨询' }} - +