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/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/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/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/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/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/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/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/StglClubAnnualReviewServiceImpl.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubAnnualReviewServiceImpl.java index dd4d0d54..b8c77147 100644 --- 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 @@ -24,8 +24,10 @@ 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; @@ -60,6 +62,7 @@ public class StglClubAnnualReviewServiceImpl implements IStglClubAnnualReviewSer private final StglClubAnnualReviewActivityMapper activityMapper; private final IStglActivityService stglActivityService; private final IStglClubService stglClubService; + private final IStglAnnualReviewAuditService auditService; private final ObjectMapper objectMapper = new ObjectMapper(); /** @@ -539,9 +542,82 @@ public class StglClubAnnualReviewServiceImpl implements IStglClubAnnualReviewSer 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()); - return baseMapper.updateById(entity) > 0; + 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; } /** 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-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/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 || '心理咨询' }} - +