lxm
This commit is contained in:
1 parent
aca0a8636a
commit
03c80657ac
23 files changed
+2236
-141
No files matched your search
+62
@@ -0,0 +1,62 @@
|
||||
package org.dromara.stgl.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 年审配置类
|
||||
*
|
||||
* @author Cursor AI
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "stgl.club.annual-review")
|
||||
@PropertySource("classpath:application-stgl.properties")
|
||||
public class AnnualReviewConfig {
|
||||
|
||||
/**
|
||||
* 项目上线时间(字符串格式:yyyy-MM-dd HH:mm:ss)
|
||||
*/
|
||||
private String projectLaunchDate = "2025-01-01 00:00:00";
|
||||
|
||||
/**
|
||||
* 历史数据宽限期(年)
|
||||
*/
|
||||
private int gracePeriodYears = 1;
|
||||
|
||||
/**
|
||||
* 年审窗口期天数
|
||||
*/
|
||||
private int windowDays = 15;
|
||||
|
||||
/**
|
||||
* 获取项目上线时间的Date对象
|
||||
*
|
||||
* @return 项目上线时间
|
||||
*/
|
||||
public Date getProjectLaunchDateTime() {
|
||||
try {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
return sdf.parse(projectLaunchDate);
|
||||
} catch (ParseException e) {
|
||||
throw new RuntimeException("项目上线时间格式错误: " + projectLaunchDate, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为历史社团
|
||||
*
|
||||
* @param establishDate 社团成立时间
|
||||
* @return true-历史社团,false-新成立社团
|
||||
*/
|
||||
public boolean isHistoricalClub(Date establishDate) {
|
||||
return establishDate.before(getProjectLaunchDateTime());
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
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.idempotent.annotation.RepeatSubmit;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.stgl.domain.StglActivityAudit;
|
||||
import org.dromara.stgl.domain.bo.StglActivityApprovalBo;
|
||||
import org.dromara.stgl.domain.vo.StglActivityAuditVo;
|
||||
import org.dromara.stgl.service.IStglActivityAuditService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 活动审批记录
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-09
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/stgl/activityAudit")
|
||||
public class StglActivityAuditController extends BaseController {
|
||||
|
||||
private final IStglActivityAuditService stglActivityAuditService;
|
||||
|
||||
/**
|
||||
* 查询活动审批记录详细
|
||||
*/
|
||||
@SaCheckPermission("stgl:activity:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<StglActivityAudit> getInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok(stglActivityAuditService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据活动ID查询审批记录
|
||||
*/
|
||||
@SaCheckPermission("stgl:activity:query")
|
||||
@GetMapping("/byActivity/{activityId}")
|
||||
public R<List<StglActivityAuditVo>> getByActivityId(@NotNull(message = "活动ID不能为空")
|
||||
@PathVariable Long activityId) {
|
||||
return R.ok(stglActivityAuditService.queryByActivityId(activityId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据审批人ID查询待审批记录
|
||||
*/
|
||||
@SaCheckPermission("stgl:activity:query")
|
||||
@GetMapping("/pending/{approverId}")
|
||||
public R<List<StglActivityAudit>> getPendingByApproverId(@NotNull(message = "审批人ID不能为空")
|
||||
@PathVariable Long approverId) {
|
||||
return R.ok(stglActivityAuditService.queryPendingByApproverId(approverId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增活动审批记录
|
||||
*/
|
||||
@SaCheckPermission("stgl:activity:add")
|
||||
@Log(title = "活动审批记录", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping()
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody StglActivityAudit stglActivityAudit) {
|
||||
return toAjax(stglActivityAuditService.insertByBo(stglActivityAudit));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改活动审批记录
|
||||
*/
|
||||
@SaCheckPermission("stgl:activity:edit")
|
||||
@Log(title = "活动审批记录", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping()
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody StglActivityAudit stglActivityAudit) {
|
||||
return toAjax(stglActivityAuditService.updateByBo(stglActivityAudit));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除活动审批记录
|
||||
*/
|
||||
@SaCheckPermission("stgl:activity:remove")
|
||||
@Log(title = "活动审批记录", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(stglActivityAuditService.deleteWithValidByIds(List.of(ids), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批活动
|
||||
*/
|
||||
@SaCheckPermission("stgl:activity:approve")
|
||||
@Log(title = "活动审批", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PostMapping("/approve")
|
||||
public R<Void> approve(@Validated @RequestBody StglActivityApprovalBo approvalBo) {
|
||||
return toAjax(stglActivityAuditService.approveActivity(approvalBo));
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -156,4 +156,8 @@ public class StglActivityController extends BaseController {
|
||||
return R.ok(stglActivityService.getActivityRatings(id));
|
||||
}
|
||||
|
||||
// 注意:提交审批和撤回审批功能已改为通过修改接口(edit)更新活动状态实现
|
||||
// 前端调用 updateStglActivity 修改状态为8(已提交)或0(草稿)
|
||||
// 后端在 updateByBo 方法中监听状态变化,自动创建或删除审批记录
|
||||
|
||||
}
|
||||
+10
-2
@@ -3,6 +3,7 @@ package org.dromara.stgl.domain;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
@@ -32,6 +33,13 @@ public class StglActivity extends TenantEntity {
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
@TableLogic
|
||||
@TableField("deleted")
|
||||
private Integer deleted;
|
||||
|
||||
/**
|
||||
* 活动名称
|
||||
*/
|
||||
@@ -107,10 +115,10 @@ public class StglActivity extends TenantEntity {
|
||||
private String relatedDeptNames;
|
||||
|
||||
/**
|
||||
* 关联社团ID
|
||||
* 关联社团ID(String类型以避免雪花ID精度丢失)
|
||||
*/
|
||||
@TableField("related_club_id")
|
||||
private Long relatedClubId;
|
||||
private String relatedClubId;
|
||||
|
||||
/**
|
||||
* 关联类型(1-社团 2-志愿者队伍)
|
||||
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
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_activity_audit
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-09
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("stgl_activity_audit")
|
||||
public class StglActivityAudit extends TenantEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
|
||||
/**
|
||||
* 活动ID
|
||||
*/
|
||||
private Long activityId;
|
||||
|
||||
/**
|
||||
* 节点代码(advisor_approve/dept_approve/school_approve)
|
||||
*/
|
||||
private String nodeCode;
|
||||
|
||||
/**
|
||||
* 节点名称(指导老师审批/部门审批/校级审批)
|
||||
*/
|
||||
private String nodeName;
|
||||
|
||||
/**
|
||||
* 节点顺序(1.1,1.2,2.1,2.2等)
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package org.dromara.stgl.domain.bo;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 活动审批请求业务对象
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-09
|
||||
*/
|
||||
@Data
|
||||
public class StglActivityApprovalBo {
|
||||
|
||||
/**
|
||||
* 审批记录ID
|
||||
*/
|
||||
@NotNull(message = "审批记录ID不能为空")
|
||||
private Long auditId;
|
||||
|
||||
/**
|
||||
* 审批结果
|
||||
*/
|
||||
@NotBlank(message = "审批结果不能为空")
|
||||
private String approveResult;
|
||||
|
||||
/**
|
||||
* 审批意见
|
||||
*/
|
||||
private String approveOpinion;
|
||||
|
||||
/**
|
||||
* 下一步审核人列表
|
||||
*/
|
||||
private List<NextAuditor> nextAuditors;
|
||||
|
||||
/**
|
||||
* 下一步审核人信息
|
||||
*/
|
||||
@Data
|
||||
public static class NextAuditor {
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
private String nickName;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -97,9 +97,9 @@ public class StglActivityBo extends BaseEntity {
|
||||
private String relatedDeptNames;
|
||||
|
||||
/**
|
||||
* 关联社团ID
|
||||
* 关联社团ID(String类型以避免雪花ID精度丢失)
|
||||
*/
|
||||
private Long relatedClubId;
|
||||
private String relatedClubId;
|
||||
|
||||
/**
|
||||
* 关联类型 (1: 社团, 2: 志愿者队伍)
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package org.dromara.stgl.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 活动审批记录视图对象 stgl_activity_audit
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-09
|
||||
*/
|
||||
@Data
|
||||
public class StglActivityAuditVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 活动ID
|
||||
*/
|
||||
private Long activityId;
|
||||
|
||||
/**
|
||||
* 活动名称
|
||||
*/
|
||||
private String activityName;
|
||||
|
||||
/**
|
||||
* 节点代码(advisor_approve/dept_approve/school_approve)
|
||||
*/
|
||||
private String nodeCode;
|
||||
|
||||
/**
|
||||
* 节点名称(指导老师审批/部门审批/校级审批)
|
||||
*/
|
||||
private String nodeName;
|
||||
|
||||
/**
|
||||
* 节点顺序(1.1,1.2,2.1,2.2等)
|
||||
*/
|
||||
private BigDecimal nodeOrder;
|
||||
|
||||
/**
|
||||
* 节点状态(pending/waiting/approved)
|
||||
*/
|
||||
private String nodeStatus;
|
||||
|
||||
/**
|
||||
* 审批人ID
|
||||
*/
|
||||
private Long approverId;
|
||||
|
||||
/**
|
||||
* 审批人姓名
|
||||
*/
|
||||
private String approverName;
|
||||
|
||||
/**
|
||||
* 审批结果(通过/不通过)
|
||||
*/
|
||||
private String approveResult;
|
||||
|
||||
/**
|
||||
* 审批意见
|
||||
*/
|
||||
private String approveOpinion;
|
||||
|
||||
/**
|
||||
* 审批时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date approveTime;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
+2
-2
@@ -110,9 +110,9 @@ public class StglActivityVo implements Serializable {
|
||||
private String relatedDeptNames;
|
||||
|
||||
/**
|
||||
* 关联社团ID
|
||||
* 关联社团ID(String类型以避免雪花ID精度丢失)
|
||||
*/
|
||||
private Long relatedClubId;
|
||||
private String relatedClubId;
|
||||
|
||||
/**
|
||||
* 关联类型(1-社团 2-志愿者队伍)
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package org.dromara.stgl.mapper;
|
||||
|
||||
import org.dromara.stgl.domain.StglActivityAudit;
|
||||
import org.dromara.stgl.domain.vo.StglActivityAuditVo;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 活动审批记录Mapper接口
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-09
|
||||
*/
|
||||
public interface StglActivityAuditMapper extends BaseMapperPlus<StglActivityAudit, StglActivityAudit> {
|
||||
|
||||
/**
|
||||
* 根据活动ID查询审批记录
|
||||
*
|
||||
* @param activityId 活动ID
|
||||
* @return 审批记录列表
|
||||
*/
|
||||
List<StglActivityAuditVo> selectByActivityId(Long activityId);
|
||||
|
||||
/**
|
||||
* 根据活动ID和节点代码查询审批记录
|
||||
*
|
||||
* @param activityId 活动ID
|
||||
* @param nodeCode 节点代码
|
||||
* @return 审批记录列表
|
||||
*/
|
||||
List<StglActivityAudit> selectByActivityIdAndNodeCode(Long activityId, String nodeCode);
|
||||
|
||||
/**
|
||||
* 根据审批人ID查询待审批记录
|
||||
*
|
||||
* @param approverId 审批人ID
|
||||
* @return 审批记录列表
|
||||
*/
|
||||
List<StglActivityAudit> selectPendingByApproverId(Long approverId);
|
||||
}
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
package org.dromara.stgl.service;
|
||||
|
||||
import org.dromara.stgl.domain.StglActivityAudit;
|
||||
import org.dromara.stgl.domain.bo.StglActivityApprovalBo;
|
||||
import org.dromara.stgl.domain.vo.StglActivityAuditVo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 活动审批记录Service接口
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-09
|
||||
*/
|
||||
public interface IStglActivityAuditService {
|
||||
|
||||
/**
|
||||
* 查询活动审批记录
|
||||
*
|
||||
* @param id 主键ID
|
||||
* @return 审批记录
|
||||
*/
|
||||
StglActivityAudit queryById(Long id);
|
||||
|
||||
/**
|
||||
* 根据活动ID查询审批记录
|
||||
*
|
||||
* @param activityId 活动ID
|
||||
* @return 审批记录列表
|
||||
*/
|
||||
List<StglActivityAuditVo> queryByActivityId(Long activityId);
|
||||
|
||||
/**
|
||||
* 根据活动ID和节点代码查询审批记录
|
||||
*
|
||||
* @param activityId 活动ID
|
||||
* @param nodeCode 节点代码
|
||||
* @return 审批记录列表
|
||||
*/
|
||||
List<StglActivityAudit> queryByActivityIdAndNodeCode(Long activityId, String nodeCode);
|
||||
|
||||
/**
|
||||
* 根据审批人ID查询待审批记录
|
||||
*
|
||||
* @param approverId 审批人ID
|
||||
* @return 审批记录列表
|
||||
*/
|
||||
List<StglActivityAudit> queryPendingByApproverId(Long approverId);
|
||||
|
||||
/**
|
||||
* 新增活动审批记录
|
||||
*
|
||||
* @param bo 审批记录业务对象
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean insertByBo(StglActivityAudit bo);
|
||||
|
||||
/**
|
||||
* 修改活动审批记录
|
||||
*
|
||||
* @param bo 审批记录业务对象
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean updateByBo(StglActivityAudit bo);
|
||||
|
||||
/**
|
||||
* 批量删除活动审批记录
|
||||
*
|
||||
* @param ids 主键ID列表
|
||||
* @param isValid 是否进行有效性校验
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
|
||||
/**
|
||||
* 创建指导老师审批记录
|
||||
*
|
||||
* @param activityId 活动ID
|
||||
* @param advisorIds 指导老师ID列表
|
||||
* @param advisorNames 指导老师姓名列表
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean createAdvisorApprovalRecords(Long activityId, List<Long> advisorIds, List<String> advisorNames);
|
||||
|
||||
/**
|
||||
* 审批活动(支持动态审核人选择)
|
||||
*
|
||||
* @param approvalBo 审批业务对象
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean approveActivity(StglActivityApprovalBo approvalBo);
|
||||
}
|
||||
|
||||
+4
@@ -123,4 +123,8 @@ public interface IStglActivityService extends IService<StglActivity> {
|
||||
*/
|
||||
List<ActivityRatingVo> getActivityRatings(Long activityId);
|
||||
|
||||
// 注意:提交审批和撤回审批功能已改为通过 updateByBo 方法监听状态变化实现
|
||||
// 前端调用 updateStglActivity 修改状态为8(已提交)或0(草稿)
|
||||
// 后端在 updateByBo 方法中自动创建或删除审批记录
|
||||
|
||||
}
|
||||
+388
@@ -0,0 +1,388 @@
|
||||
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.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.stgl.domain.StglActivity;
|
||||
import org.dromara.stgl.domain.StglActivityAudit;
|
||||
import org.dromara.stgl.domain.bo.StglActivityApprovalBo;
|
||||
import org.dromara.stgl.domain.vo.StglActivityAuditVo;
|
||||
import org.dromara.stgl.mapper.StglActivityAuditMapper;
|
||||
import org.dromara.stgl.mapper.StglActivityMapper;
|
||||
import org.dromara.stgl.service.IStglActivityAuditService;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 活动审批记录Service业务层处理
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-09
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class StglActivityAuditServiceImpl implements IStglActivityAuditService {
|
||||
|
||||
private final StglActivityAuditMapper baseMapper;
|
||||
private final StglActivityMapper activityMapper;
|
||||
|
||||
/**
|
||||
* 查询活动审批记录
|
||||
*/
|
||||
@Override
|
||||
public StglActivityAudit queryById(Long id) {
|
||||
return baseMapper.selectById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据活动ID查询审批记录
|
||||
*/
|
||||
@Override
|
||||
public List<StglActivityAuditVo> queryByActivityId(Long activityId) {
|
||||
return baseMapper.selectByActivityId(activityId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据活动ID和节点代码查询审批记录
|
||||
*/
|
||||
@Override
|
||||
public List<StglActivityAudit> queryByActivityIdAndNodeCode(Long activityId, String nodeCode) {
|
||||
return baseMapper.selectByActivityIdAndNodeCode(activityId, nodeCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据审批人ID查询待审批记录
|
||||
*/
|
||||
@Override
|
||||
public List<StglActivityAudit> queryPendingByApproverId(Long approverId) {
|
||||
return baseMapper.selectPendingByApproverId(approverId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增活动审批记录
|
||||
*/
|
||||
@Override
|
||||
public Boolean insertByBo(StglActivityAudit bo) {
|
||||
StglActivityAudit add = BeanUtil.toBean(bo, StglActivityAudit.class);
|
||||
validEntityBeforeSave(add);
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
bo.setId(add.getId());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改活动审批记录
|
||||
*/
|
||||
@Override
|
||||
public Boolean updateByBo(StglActivityAudit bo) {
|
||||
StglActivityAudit update = BeanUtil.toBean(bo, StglActivityAudit.class);
|
||||
validEntityBeforeSave(update);
|
||||
return baseMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前的数据校验
|
||||
*/
|
||||
private void validEntityBeforeSave(StglActivityAudit entity) {
|
||||
// TODO 做一些数据校验,如唯一约束
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除活动审批记录
|
||||
*/
|
||||
@Override
|
||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||
if (isValid) {
|
||||
// TODO 做一些业务上的校验,判断是否需要校验
|
||||
}
|
||||
return baseMapper.deleteByIds(ids) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指导老师审批记录
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public Boolean createAdvisorApprovalRecords(Long activityId, List<Long> advisorIds, List<String> advisorNames) {
|
||||
if (ObjectUtil.isEmpty(advisorIds) || ObjectUtil.isEmpty(advisorNames) || advisorIds.size() != advisorNames.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 删除已存在的指导老师审批记录
|
||||
LambdaQueryWrapper<StglActivityAudit> wrapper = Wrappers.lambdaQuery();
|
||||
wrapper.eq(StglActivityAudit::getActivityId, activityId)
|
||||
.eq(StglActivityAudit::getNodeCode, "advisor_approve");
|
||||
baseMapper.delete(wrapper);
|
||||
|
||||
// 创建新的指导老师审批记录
|
||||
for (int i = 0; i < advisorIds.size(); i++) {
|
||||
StglActivityAudit audit = new StglActivityAudit();
|
||||
audit.setActivityId(activityId);
|
||||
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 approveActivity(StglActivityApprovalBo approvalBo) {
|
||||
// 1. 获取当前审批记录
|
||||
StglActivityAudit currentAudit = baseMapper.selectById(approvalBo.getAuditId());
|
||||
if (currentAudit == null || !"pending".equals(currentAudit.getNodeStatus())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 更新当前审批记录
|
||||
currentAudit.setNodeStatus("approved");
|
||||
currentAudit.setApproveResult(approvalBo.getApproveResult());
|
||||
currentAudit.setApproveOpinion(approvalBo.getApproveOpinion());
|
||||
currentAudit.setApproveTime(new Date());
|
||||
baseMapper.updateById(currentAudit);
|
||||
|
||||
// 3. 如果审批不通过,直接更新活动状态为7(已驳回)
|
||||
if ("不通过".equals(approvalBo.getApproveResult())) {
|
||||
StglActivity activity = activityMapper.selectById(currentAudit.getActivityId());
|
||||
if (activity != null) {
|
||||
LambdaUpdateWrapper<StglActivity> updateWrapper = Wrappers.lambdaUpdate();
|
||||
updateWrapper.eq(StglActivity::getId, currentAudit.getActivityId())
|
||||
.set(StglActivity::getActivityStatus, "7");
|
||||
activityMapper.update(null, updateWrapper);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 4. 如果审批通过,检查是否有下一个待审批的同环节审批人
|
||||
List<StglActivityAudit> samePhaseAudits = baseMapper.selectByActivityIdAndNodeCode(
|
||||
currentAudit.getActivityId(), currentAudit.getNodeCode());
|
||||
|
||||
// 查找下一个waiting状态的审批记录
|
||||
StglActivityAudit nextAudit = null;
|
||||
for (StglActivityAudit 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. 如果同环节所有审批人都审批通过,检查是否是第一个审批节点
|
||||
boolean allApproved = samePhaseAudits.stream()
|
||||
.allMatch(audit -> "approved".equals(audit.getNodeStatus()) && "通过".equals(audit.getApproveResult()));
|
||||
|
||||
if (allApproved) {
|
||||
// 只有第一个审批节点(node_order=1.1 或 1.0)通过时才设置为9-审核中
|
||||
if (isFirstApprovalNode(currentAudit)) {
|
||||
StglActivity activity = activityMapper.selectById(currentAudit.getActivityId());
|
||||
if (activity != null) {
|
||||
LambdaUpdateWrapper<StglActivity> updateWrapper = Wrappers.lambdaUpdate();
|
||||
updateWrapper.eq(StglActivity::getId, currentAudit.getActivityId())
|
||||
.set(StglActivity::getActivityStatus, "9");
|
||||
activityMapper.update(null, updateWrapper);
|
||||
log.info("第一个审批节点通过,活动状态更新为9-审核中,活动ID: {}", activity.getId());
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有指定下一步审核人,创建新的审批记录
|
||||
if (approvalBo.getNextAuditors() != null && !approvalBo.getNextAuditors().isEmpty()) {
|
||||
createNextStepAuditRecords(currentAudit.getActivityId(), approvalBo.getNextAuditors());
|
||||
} else if (isLastApprovalNode(currentAudit, samePhaseAudits)) {
|
||||
// 如果是最后一个节点且没有下一步审核人,根据活动时间判断最终状态
|
||||
StglActivity activity = activityMapper.selectById(currentAudit.getActivityId());
|
||||
String finalStatus = determineFinalStatus(activity);
|
||||
|
||||
LambdaUpdateWrapper<StglActivity> updateWrapper = Wrappers.lambdaUpdate();
|
||||
updateWrapper.eq(StglActivity::getId, currentAudit.getActivityId())
|
||||
.set(StglActivity::getActivityStatus, finalStatus);
|
||||
activityMapper.update(null, updateWrapper);
|
||||
log.info("最后审批节点通过,活动状态更新为: {}, 活动ID: {}", finalStatus, activity.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建下一步审批记录
|
||||
*/
|
||||
private void createNextStepAuditRecords(Long activityId, List<StglActivityApprovalBo.NextAuditor> nextAuditors) {
|
||||
// 查询已有的审批记录,确定下一个审批层级
|
||||
List<StglActivityAuditVo> existingAudits = baseMapper.selectByActivityId(activityId);
|
||||
|
||||
// 确定下一个审批层级
|
||||
int nextLevel = determineNextApprovalLevel(existingAudits);
|
||||
|
||||
for (int i = 0; i < nextAuditors.size(); i++) {
|
||||
StglActivityApprovalBo.NextAuditor auditor = nextAuditors.get(i);
|
||||
|
||||
StglActivityAudit nextAudit = new StglActivityAudit();
|
||||
nextAudit.setActivityId(activityId);
|
||||
nextAudit.setNodeCode(getNodeCodeByLevel(nextLevel));
|
||||
nextAudit.setNodeName(getNodeNameByLevel(nextLevel));
|
||||
|
||||
// 根据层级和顺序设置node_order
|
||||
if (nextAuditors.size() == 1) {
|
||||
// 单个审核人为 X.0
|
||||
nextAudit.setNodeOrder(new BigDecimal(nextLevel + ".0"));
|
||||
} else {
|
||||
// 多个审核人为 X.1, X.2, X.3...
|
||||
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<StglActivityAuditVo> existingAudits) {
|
||||
int maxLevel = 1; // 默认从指导老师审批层级开始
|
||||
|
||||
for (StglActivityAuditVo 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 "dept_approve";
|
||||
case 3:
|
||||
return "school_approve";
|
||||
default:
|
||||
return "dept_approve"; // 默认为部门审批
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据层级获取节点名称
|
||||
*/
|
||||
private String getNodeNameByLevel(int level) {
|
||||
switch (level) {
|
||||
case 1:
|
||||
return "指导老师审批";
|
||||
case 2:
|
||||
return "部门审批";
|
||||
case 3:
|
||||
return "校级审批";
|
||||
default:
|
||||
return "部门审批"; // 默认为部门审批
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为第一个审批节点(node_order=1.1 或 1.0)
|
||||
*/
|
||||
private boolean isFirstApprovalNode(StglActivityAudit currentAudit) {
|
||||
if (currentAudit == null || currentAudit.getNodeOrder() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BigDecimal nodeOrder = currentAudit.getNodeOrder();
|
||||
// 检查node_order是否为1.1或1.0
|
||||
return nodeOrder.compareTo(new BigDecimal("1.0")) == 0 ||
|
||||
nodeOrder.compareTo(new BigDecimal("1.1")) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为最后一个审批节点
|
||||
*/
|
||||
private boolean isLastApprovalNode(StglActivityAudit currentAudit, List<StglActivityAudit> allAudits) {
|
||||
if (currentAudit == null || currentAudit.getNodeOrder() == null || allAudits == null || allAudits.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取所有审批记录中最大的nodeOrder
|
||||
BigDecimal maxNodeOrder = allAudits.stream()
|
||||
.map(StglActivityAudit::getNodeOrder)
|
||||
.filter(order -> order != null)
|
||||
.max(BigDecimal::compareTo)
|
||||
.orElse(BigDecimal.ZERO);
|
||||
|
||||
// 当前节点的nodeOrder等于最大nodeOrder时,表示是最后一个节点
|
||||
boolean isLast = currentAudit.getNodeOrder().compareTo(maxNodeOrder) == 0;
|
||||
|
||||
log.info("最后节点判断: 当前节点={}, nodeOrder={}, 最大nodeOrder={}, 是否最后节点={}",
|
||||
currentAudit.getNodeName(), currentAudit.getNodeOrder(), maxNodeOrder, isLast);
|
||||
|
||||
return isLast;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据活动时间判断最终状态
|
||||
*/
|
||||
private String determineFinalStatus(StglActivity activity) {
|
||||
if (activity == null) {
|
||||
return "1"; // 默认未开始
|
||||
}
|
||||
|
||||
Date now = new Date();
|
||||
Date startTime = activity.getStartTime();
|
||||
Date endTime = activity.getEndTime();
|
||||
|
||||
if (startTime != null && now.before(startTime)) {
|
||||
return "1"; // 未开始
|
||||
} else if (endTime != null && now.after(endTime)) {
|
||||
return "5"; // 已结束
|
||||
} else {
|
||||
return "4"; // 进行中
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+112
-2
@@ -17,13 +17,17 @@ import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.stgl.domain.StglActivity;
|
||||
import org.dromara.stgl.domain.StglActivityAudit;
|
||||
import org.dromara.stgl.domain.bo.StglActivityBo;
|
||||
import org.dromara.stgl.domain.vo.StglActivityVo;
|
||||
import org.dromara.stgl.domain.vo.ActivityRatingVo;
|
||||
import org.dromara.stgl.mapper.StglActivityMapper;
|
||||
import org.dromara.stgl.mapper.StglActivityAuditMapper;
|
||||
import org.dromara.stgl.mapper.StglActivityRatingMapper;
|
||||
import org.dromara.stgl.mapper.StglMemberMapper;
|
||||
import org.dromara.stgl.service.IStglActivityService;
|
||||
import org.dromara.stgl.service.IStglActivityAuditService;
|
||||
import org.dromara.stgl.service.IStglClubService;
|
||||
import org.dromara.stgl.utils.AdvisorRoleAssigner;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -69,6 +73,9 @@ public class StglActivityServiceImpl extends ServiceImpl<StglActivityMapper, Stg
|
||||
private final StglActivityRatingMapper stglActivityRatingMapper;
|
||||
private final StglMemberMapper stglMemberMapper;
|
||||
private final ISysUserService sysUserService;
|
||||
private final IStglActivityAuditService activityAuditService;
|
||||
private final StglActivityAuditMapper activityAuditMapper;
|
||||
private final IStglClubService clubService;
|
||||
|
||||
/**
|
||||
* 查询活动信息
|
||||
@@ -237,7 +244,7 @@ public class StglActivityServiceImpl extends ServiceImpl<StglActivityMapper, Stg
|
||||
lqw.ge(ObjectUtil.isNotNull(bo.getStartTime()), StglActivity::getStartTime, bo.getStartTime());
|
||||
lqw.le(ObjectUtil.isNotNull(bo.getEndTime()), StglActivity::getEndTime, bo.getEndTime());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getLocation()), StglActivity::getLocation, bo.getLocation());
|
||||
lqw.eq(ObjectUtil.isNotNull(bo.getRelatedClubId()), StglActivity::getRelatedClubId, bo.getRelatedClubId());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getRelatedClubId()), StglActivity::getRelatedClubId, bo.getRelatedClubId());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getOrganizerName()), StglActivity::getOrganizerName, bo.getOrganizerName());
|
||||
lqw.eq(ObjectUtil.isNotNull(bo.getRegistrationType()), StglActivity::getRegistrationType, bo.getRegistrationType());
|
||||
lqw.ge(ObjectUtil.isNotNull(bo.getRegistrationStart()), StglActivity::getRegistrationStart, bo.getRegistrationStart());
|
||||
@@ -256,6 +263,7 @@ public class StglActivityServiceImpl extends ServiceImpl<StglActivityMapper, Stg
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean insertByBo(StglActivityBo bo) {
|
||||
StglActivity add = MapstructUtils.convert(bo, StglActivity.class);
|
||||
|
||||
validEntityBeforeSave(add);
|
||||
|
||||
// 处理多选部门数据
|
||||
@@ -288,12 +296,13 @@ public class StglActivityServiceImpl extends ServiceImpl<StglActivityMapper, Stg
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean updateByBo(StglActivityBo bo) {
|
||||
StglActivity update = MapstructUtils.convert(bo, StglActivity.class);
|
||||
|
||||
validEntityBeforeSave(update);
|
||||
|
||||
// 处理多选部门数据
|
||||
handleMultiDeptData(bo, update);
|
||||
|
||||
// 获取更新前的活动信息,用于比较指导老师变化
|
||||
// 获取更新前的活动信息,用于比较指导老师变化和状态变化
|
||||
StglActivity oldActivity = null;
|
||||
if (update.getId() != null) {
|
||||
oldActivity = baseMapper.selectById(update.getId());
|
||||
@@ -304,6 +313,104 @@ public class StglActivityServiceImpl extends ServiceImpl<StglActivityMapper, Stg
|
||||
if (updateResult) {
|
||||
// 处理指导老师角色分配
|
||||
handleAdvisorRoleAssignment(oldActivity, update);
|
||||
|
||||
// 监听状态变化:如果状态从0(草稿)变为8(已提交),自动创建审批记录
|
||||
if (oldActivity != null &&
|
||||
"0".equals(oldActivity.getActivityStatus()) &&
|
||||
"8".equals(update.getActivityStatus())) {
|
||||
|
||||
log.info("检测到活动状态从草稿变为已提交,自动创建审批记录 - activityId: {}", update.getId());
|
||||
|
||||
// 获取活动关联的社团信息
|
||||
if (StringUtils.isBlank(update.getRelatedClubId())) {
|
||||
throw new RuntimeException("活动未关联社团,无法提交审批");
|
||||
}
|
||||
|
||||
log.info("查询关联社团信息 - clubId: {}", update.getRelatedClubId());
|
||||
// 将String类型的clubId转换为Long类型进行查询
|
||||
Long clubId;
|
||||
try {
|
||||
clubId = Long.valueOf(update.getRelatedClubId());
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("关联社团ID格式错误: {}", update.getRelatedClubId(), e);
|
||||
throw new RuntimeException("关联社团ID格式错误: " + update.getRelatedClubId());
|
||||
}
|
||||
|
||||
StglClubVo clubVo = clubService.queryById(clubId);
|
||||
if (clubVo == null) {
|
||||
log.error("社团不存在或已被删除 - clubId: {}", update.getRelatedClubId());
|
||||
throw new RuntimeException("活动关联的社团不存在或已被删除(ID: " + update.getRelatedClubId() + ")。请检查社团数据或重新选择关联社团。");
|
||||
}
|
||||
|
||||
// 从社团中提取指导老师信息
|
||||
List<Long> advisorIds = new ArrayList<>();
|
||||
List<String> advisorNames = new ArrayList<>();
|
||||
|
||||
if (StringUtils.isNotBlank(clubVo.getAdvisorsJson())) {
|
||||
// 解析社团指导老师JSON数据
|
||||
List<Map<String, Object>> advisors =
|
||||
JsonUtils.parseObject(clubVo.getAdvisorsJson(),
|
||||
new TypeReference<List<Map<String, Object>>>() {});
|
||||
|
||||
if (advisors != null) {
|
||||
for (Map<String, Object> advisor : advisors) {
|
||||
// 安全地转换userId,支持String和Number类型
|
||||
Long userId = null;
|
||||
Object userIdObj = advisor.get("userId");
|
||||
if (userIdObj != null) {
|
||||
if (userIdObj instanceof Number) {
|
||||
userId = ((Number) userIdObj).longValue();
|
||||
} else if (userIdObj instanceof String) {
|
||||
try {
|
||||
userId = Long.valueOf((String) userIdObj);
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("无法解析指导老师userId: {}", userIdObj, e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String name = (String) advisor.get("name");
|
||||
if (userId != null && StringUtils.isNotBlank(name)) {
|
||||
advisorIds.add(userId);
|
||||
advisorNames.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (advisorIds.isEmpty()) {
|
||||
throw new RuntimeException("活动关联的社团没有配置指导老师,无法提交审批");
|
||||
}
|
||||
|
||||
// 创建指导老师审批记录
|
||||
activityAuditService.createAdvisorApprovalRecords(
|
||||
update.getId(), advisorIds, advisorNames);
|
||||
|
||||
log.info("自动创建审批记录成功 - activityId: {}, advisorCount: {}",
|
||||
update.getId(), advisorIds.size());
|
||||
}
|
||||
|
||||
// 监听状态变化:如果状态从8(已提交)变为0(草稿),自动删除审批记录
|
||||
if (oldActivity != null &&
|
||||
"8".equals(oldActivity.getActivityStatus()) &&
|
||||
"0".equals(update.getActivityStatus())) {
|
||||
|
||||
log.info("检测到活动状态从已提交变为草稿,自动删除审批记录 - activityId: {}", update.getId());
|
||||
|
||||
// 删除所有审批记录
|
||||
LambdaQueryWrapper<StglActivityAudit> queryWrapper = Wrappers.lambdaQuery();
|
||||
queryWrapper.eq(StglActivityAudit::getActivityId, update.getId());
|
||||
List<StglActivityAudit> audits = activityAuditMapper.selectList(queryWrapper);
|
||||
|
||||
if (!audits.isEmpty()) {
|
||||
activityAuditService.deleteWithValidByIds(
|
||||
audits.stream().map(StglActivityAudit::getId).collect(Collectors.toList()),
|
||||
false);
|
||||
log.info("自动删除审批记录成功 - activityId: {}, auditCount: {}",
|
||||
update.getId(), audits.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updateResult;
|
||||
@@ -908,4 +1015,7 @@ public class StglActivityServiceImpl extends ServiceImpl<StglActivityMapper, Stg
|
||||
entity.setRelatedDeptNames(null);
|
||||
}
|
||||
}
|
||||
|
||||
// 注意:submitActivityForApproval 和 withdrawActivityApproval 方法已废弃
|
||||
// 功能已整合到 updateByBo 方法中,通过监听状态变化自动处理审批记录
|
||||
}
|
||||
+1
-1
@@ -705,7 +705,7 @@ public class StglClubAnnualReviewServiceImpl implements IStglClubAnnualReviewSer
|
||||
|
||||
// 构建查询条件
|
||||
StglActivityBo bo = new StglActivityBo();
|
||||
bo.setRelatedClubId(clubId); // 设置社团ID
|
||||
bo.setRelatedClubId(String.valueOf(clubId)); // 设置社团ID(Long转String避免精度丢失)
|
||||
bo.setRelatedType(1); // 关联类型为社团(1表示社团,2表示志愿团队)
|
||||
|
||||
// 设置时间范围查询条件(查询活动开始时间在该年度内的活动)
|
||||
|
||||
+97
-16
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.stgl.config.AnnualReviewConfig;
|
||||
import org.dromara.stgl.domain.StglClub;
|
||||
import org.dromara.stgl.domain.StglClubAnnualReview;
|
||||
import org.dromara.stgl.domain.vo.ClubActivityPermissionResult;
|
||||
@@ -29,11 +30,7 @@ public class StglClubPermissionServiceImpl implements IStglClubPermissionService
|
||||
|
||||
private final StglClubMapper clubMapper;
|
||||
private final StglClubAnnualReviewMapper annualReviewMapper;
|
||||
|
||||
/**
|
||||
* 年审窗口期天数(15天)
|
||||
*/
|
||||
private static final int ANNUAL_REVIEW_WINDOW_DAYS = 15;
|
||||
private final AnnualReviewConfig annualReviewConfig;
|
||||
|
||||
/**
|
||||
* 检查社团活动权限(实时计算,精确到秒级)
|
||||
@@ -57,7 +54,7 @@ public class StglClubPermissionServiceImpl implements IStglClubPermissionService
|
||||
|
||||
// 2. 计算关键时间点(精确到秒)
|
||||
Date annualReviewStartTime = calculateAnnualReviewStartTime(establishDate, currentTime);
|
||||
Date annualReviewDeadlineTime = addDays(annualReviewStartTime, ANNUAL_REVIEW_WINDOW_DAYS);
|
||||
Date annualReviewDeadlineTime = addDays(annualReviewStartTime, annualReviewConfig.getWindowDays());
|
||||
|
||||
log.debug("社团ID: {}, 成立时间: {}, 年审开始时间: {}, 年审截止时间: {}, 当前时间: {}",
|
||||
clubId, establishDate, annualReviewStartTime, annualReviewDeadlineTime, currentTime);
|
||||
@@ -164,7 +161,7 @@ public class StglClubPermissionServiceImpl implements IStglClubPermissionService
|
||||
|
||||
Date currentTime = new Date();
|
||||
Date annualReviewStartTime = calculateAnnualReviewStartTime(clubInfo.getEstablishDate(), currentTime);
|
||||
Date annualReviewDeadlineTime = addDays(annualReviewStartTime, ANNUAL_REVIEW_WINDOW_DAYS);
|
||||
Date annualReviewDeadlineTime = addDays(annualReviewStartTime, annualReviewConfig.getWindowDays());
|
||||
|
||||
// 如果当前不在年审窗口期,返回false
|
||||
if (currentTime.before(annualReviewStartTime) || currentTime.after(annualReviewDeadlineTime)) {
|
||||
@@ -175,30 +172,114 @@ public class StglClubPermissionServiceImpl implements IStglClubPermissionService
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算年审开始时间(精确到具体时刻)
|
||||
* 计算年审开始时间(支持历史社团处理)
|
||||
*
|
||||
* @param establishDate 社团成立时间
|
||||
* @param currentDate 当前时间
|
||||
* @return 今年的年审开始时间
|
||||
* @return 下一次年审开始时间
|
||||
*/
|
||||
private Date calculateAnnualReviewStartTime(Date establishDate, Date currentDate) {
|
||||
// 1. 区分历史社团和新成立社团
|
||||
if (annualReviewConfig.isHistoricalClub(establishDate)) {
|
||||
return handleHistoricalClub(establishDate, currentDate);
|
||||
} else {
|
||||
return handleNewClub(establishDate, currentDate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理历史社团的年审时间计算
|
||||
* 历史社团统一从项目上线后的第N年开始年审
|
||||
*/
|
||||
private Date handleHistoricalClub(Date establishDate, Date currentDate) {
|
||||
Date projectLaunchDate = annualReviewConfig.getProjectLaunchDateTime();
|
||||
|
||||
Calendar launchCal = Calendar.getInstance();
|
||||
launchCal.setTime(projectLaunchDate);
|
||||
|
||||
Calendar establishCal = Calendar.getInstance();
|
||||
establishCal.setTime(establishDate);
|
||||
|
||||
Calendar currentCal = Calendar.getInstance();
|
||||
currentCal.setTime(currentDate);
|
||||
|
||||
// 构造今年同月同日的时间,保持原有的时分秒毫秒
|
||||
|
||||
// 构造年审时间:项目上线年份 + 宽限期 + 社团成立的月日时分秒
|
||||
Calendar annualReviewCal = Calendar.getInstance();
|
||||
annualReviewCal.set(Calendar.YEAR, currentCal.get(Calendar.YEAR));
|
||||
annualReviewCal.set(Calendar.YEAR, launchCal.get(Calendar.YEAR) + annualReviewConfig.getGracePeriodYears());
|
||||
annualReviewCal.set(Calendar.MONTH, establishCal.get(Calendar.MONTH));
|
||||
annualReviewCal.set(Calendar.DAY_OF_MONTH, establishCal.get(Calendar.DAY_OF_MONTH));
|
||||
annualReviewCal.set(Calendar.HOUR_OF_DAY, establishCal.get(Calendar.HOUR_OF_DAY));
|
||||
annualReviewCal.set(Calendar.MINUTE, establishCal.get(Calendar.MINUTE));
|
||||
annualReviewCal.set(Calendar.SECOND, establishCal.get(Calendar.SECOND));
|
||||
annualReviewCal.set(Calendar.MILLISECOND, establishCal.get(Calendar.MILLISECOND));
|
||||
|
||||
Date firstAnnualReviewTime = annualReviewCal.getTime();
|
||||
|
||||
log.debug("历史社团年审计算 - 成立时间: {}, 项目上线: {}, 第1次年审: {}, 当前时间: {}",
|
||||
establishDate, projectLaunchDate, firstAnnualReviewTime, currentDate);
|
||||
|
||||
// 如果第一次年审时间还没到,返回第一次年审时间
|
||||
if (currentDate.before(firstAnnualReviewTime)) {
|
||||
return firstAnnualReviewTime;
|
||||
}
|
||||
|
||||
// 如果已经过了第一次年审时间,计算后续的年审时间
|
||||
return calculateNextAnnualReviewAfterFirst(establishDate, currentDate, firstAnnualReviewTime);
|
||||
}
|
||||
|
||||
return annualReviewCal.getTime();
|
||||
/**
|
||||
* 处理新成立社团的年审时间计算
|
||||
* 新社团:成立满1年后开始年审
|
||||
*/
|
||||
private Date handleNewClub(Date establishDate, Date currentDate) {
|
||||
// 第一次年审时间 = 成立时间 + 1年
|
||||
Calendar firstAnnualReviewCal = Calendar.getInstance();
|
||||
firstAnnualReviewCal.setTime(establishDate);
|
||||
firstAnnualReviewCal.add(Calendar.YEAR, 1);
|
||||
|
||||
Date firstAnnualReviewTime = firstAnnualReviewCal.getTime();
|
||||
|
||||
log.debug("新成立社团年审计算 - 成立时间: {}, 第1次年审: {}, 当前时间: {}",
|
||||
establishDate, firstAnnualReviewTime, currentDate);
|
||||
|
||||
// 如果第一次年审时间还没到,返回第一次年审时间
|
||||
if (currentDate.before(firstAnnualReviewTime)) {
|
||||
return firstAnnualReviewTime;
|
||||
}
|
||||
|
||||
// 如果已经过了第一次年审时间,计算后续的年审时间
|
||||
return calculateNextAnnualReviewAfterFirst(establishDate, currentDate, firstAnnualReviewTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算第一次年审之后的年审时间
|
||||
*/
|
||||
private Date calculateNextAnnualReviewAfterFirst(Date establishDate, Date currentDate, Date firstAnnualReviewTime) {
|
||||
Calendar currentCal = Calendar.getInstance();
|
||||
currentCal.setTime(currentDate);
|
||||
|
||||
Calendar establishCal = Calendar.getInstance();
|
||||
establishCal.setTime(establishDate);
|
||||
|
||||
// 构造今年的年审时间
|
||||
Calendar thisYearAnnualReviewCal = Calendar.getInstance();
|
||||
thisYearAnnualReviewCal.set(Calendar.YEAR, currentCal.get(Calendar.YEAR));
|
||||
thisYearAnnualReviewCal.set(Calendar.MONTH, establishCal.get(Calendar.MONTH));
|
||||
thisYearAnnualReviewCal.set(Calendar.DAY_OF_MONTH, establishCal.get(Calendar.DAY_OF_MONTH));
|
||||
thisYearAnnualReviewCal.set(Calendar.HOUR_OF_DAY, establishCal.get(Calendar.HOUR_OF_DAY));
|
||||
thisYearAnnualReviewCal.set(Calendar.MINUTE, establishCal.get(Calendar.MINUTE));
|
||||
thisYearAnnualReviewCal.set(Calendar.SECOND, establishCal.get(Calendar.SECOND));
|
||||
thisYearAnnualReviewCal.set(Calendar.MILLISECOND, establishCal.get(Calendar.MILLISECOND));
|
||||
|
||||
Date thisYearAnnualReview = thisYearAnnualReviewCal.getTime();
|
||||
|
||||
log.debug("计算后续年审时间 - 今年年审: {}, 当前时间: {}", thisYearAnnualReview, currentDate);
|
||||
|
||||
// 如果今年的年审时间还没到,返回今年的
|
||||
if (currentDate.before(thisYearAnnualReview)) {
|
||||
return thisYearAnnualReview;
|
||||
}
|
||||
|
||||
// 如果今年的年审时间已经过了,返回明年的
|
||||
thisYearAnnualReviewCal.add(Calendar.YEAR, 1);
|
||||
return thisYearAnnualReviewCal.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# 社团管理模块独立配置
|
||||
# 此配置仅适用于stgl社团管理模块
|
||||
|
||||
# 社团年审配置
|
||||
stgl.club.annual-review.project-launch-date=2025-10-15 00:00:00
|
||||
stgl.club.annual-review.grace-period-years=1
|
||||
stgl.club.annual-review.window-days=15
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.dromara.stgl.mapper.StglActivityAuditMapper">
|
||||
|
||||
<resultMap type="org.dromara.stgl.domain.StglActivityAudit" id="StglActivityAuditResult">
|
||||
<result property="id" column="id" />
|
||||
<result property="tenantId" column="tenant_id" />
|
||||
<result property="createDept" column="create_dept" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="deleted" column="deleted" />
|
||||
<result property="activityId" column="activity_id" />
|
||||
<result property="nodeCode" column="node_code" />
|
||||
<result property="nodeName" column="node_name" />
|
||||
<result property="nodeOrder" column="node_order" />
|
||||
<result property="nodeStatus" column="audit_status" />
|
||||
<result property="approverId" column="approver_id" />
|
||||
<result property="approverName" column="approver_name" />
|
||||
<result property="approveResult" column="approve_result" />
|
||||
<result property="approveOpinion" column="audit_opinion" />
|
||||
<result property="approveTime" column="audit_time" />
|
||||
</resultMap>
|
||||
|
||||
<resultMap type="org.dromara.stgl.domain.vo.StglActivityAuditVo" id="StglActivityAuditVoResult">
|
||||
<result property="id" column="id" />
|
||||
<result property="activityId" column="activity_id" />
|
||||
<result property="activityName" column="activity_name" />
|
||||
<result property="nodeCode" column="node_code" />
|
||||
<result property="nodeName" column="node_name" />
|
||||
<result property="nodeOrder" column="node_order" />
|
||||
<result property="nodeStatus" column="audit_status" />
|
||||
<result property="approverId" column="approver_id" />
|
||||
<result property="approverName" column="approver_name" />
|
||||
<result property="approveResult" column="approve_result" />
|
||||
<result property="approveOpinion" column="audit_opinion" />
|
||||
<result property="approveTime" column="audit_time" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectStglActivityAuditVo">
|
||||
select id, tenant_id, create_dept, create_by, create_time, update_by, update_time, deleted,
|
||||
activity_id, node_code, node_name, node_order, audit_status,
|
||||
approver_id, approver_name, approve_result, audit_opinion, audit_time
|
||||
from stgl_activity_audit
|
||||
</sql>
|
||||
|
||||
<!-- 根据活动ID查询审批记录(包含活动名称) -->
|
||||
<select id="selectByActivityId" parameterType="Long" resultMap="StglActivityAuditVoResult">
|
||||
select
|
||||
a.id, a.activity_id, a.node_code, a.node_name, a.node_order, a.audit_status,
|
||||
a.approver_id, a.approver_name, a.approve_result, a.audit_opinion, a.audit_time,
|
||||
a.create_by, a.create_time, a.update_by, a.update_time,
|
||||
act.activity_name
|
||||
from stgl_activity_audit a
|
||||
left join stgl_activity act on a.activity_id = act.id
|
||||
where a.activity_id = #{activityId} and a.deleted = 0
|
||||
order by a.node_order
|
||||
</select>
|
||||
|
||||
<!-- 根据活动ID和节点代码查询审批记录 -->
|
||||
<select id="selectByActivityIdAndNodeCode" resultMap="StglActivityAuditResult">
|
||||
<include refid="selectStglActivityAuditVo"/>
|
||||
where activity_id = #{activityId} and node_code = #{nodeCode} and deleted = 0
|
||||
order by node_order
|
||||
</select>
|
||||
|
||||
<!-- 根据审批人ID查询待审批记录 -->
|
||||
<select id="selectPendingByApproverId" parameterType="Long" resultMap="StglActivityAuditResult">
|
||||
<include refid="selectStglActivityAuditVo"/>
|
||||
where approver_id = #{approverId} and audit_status = 'pending' and deleted = 0
|
||||
order by create_time desc
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
|
||||
/**
|
||||
* 活动审批记录VO
|
||||
*/
|
||||
export interface ActivityAuditVO {
|
||||
id?: number;
|
||||
activityId?: number;
|
||||
activityName?: string;
|
||||
nodeCode?: string;
|
||||
nodeName?: string;
|
||||
nodeOrder?: number;
|
||||
nodeStatus?: string;
|
||||
approverId?: number;
|
||||
approverName?: string;
|
||||
approveResult?: string;
|
||||
approveOpinion?: string;
|
||||
approveTime?: string;
|
||||
createBy?: number;
|
||||
createTime?: string;
|
||||
updateBy?: number;
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 活动审批请求参数
|
||||
*/
|
||||
export interface ActivityApprovalForm {
|
||||
auditId: number;
|
||||
approveResult: string;
|
||||
approveOpinion?: string;
|
||||
nextAuditors?: NextAuditor[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 下一步审核人信息
|
||||
*/
|
||||
export interface NextAuditor {
|
||||
userId: number;
|
||||
userName: string;
|
||||
nickName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询活动审批记录详细
|
||||
* @param id 审批记录主键
|
||||
* @returns 审批记录详情
|
||||
*/
|
||||
export const getActivityAudit = (id: string | number): AxiosPromise<ActivityAuditVO> => {
|
||||
return request({
|
||||
url: '/stgl/activityAudit/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据活动ID查询审批记录
|
||||
* @param activityId 活动ID
|
||||
* @returns 审批记录列表
|
||||
*/
|
||||
export const getActivityAuditsByActivityId = (activityId: string | number): AxiosPromise<ActivityAuditVO[]> => {
|
||||
return request({
|
||||
url: '/stgl/activityAudit/byActivity/' + activityId,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据审批人ID查询待审批记录
|
||||
* @param approverId 审批人ID
|
||||
* @returns 审批记录列表
|
||||
*/
|
||||
export const getPendingActivityAudits = (approverId: string | number): AxiosPromise<ActivityAuditVO[]> => {
|
||||
return request({
|
||||
url: '/stgl/activityAudit/pending/' + approverId,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 审批活动
|
||||
* @param data 审批参数
|
||||
* @returns 操作结果
|
||||
*/
|
||||
export const approveActivity = (data: ActivityApprovalForm) => {
|
||||
return request({
|
||||
url: '/stgl/activityAudit/approve',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增活动审批记录
|
||||
* @param data 审批记录信息
|
||||
* @returns 操作结果
|
||||
*/
|
||||
export const addActivityAudit = (data: ActivityAuditVO) => {
|
||||
return request({
|
||||
url: '/stgl/activityAudit',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改活动审批记录
|
||||
* @param data 审批记录信息
|
||||
* @returns 操作结果
|
||||
*/
|
||||
export const updateActivityAudit = (data: ActivityAuditVO) => {
|
||||
return request({
|
||||
url: '/stgl/activityAudit',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除活动审批记录
|
||||
* @param id 审批记录主键
|
||||
* @returns 操作结果
|
||||
*/
|
||||
export const delActivityAudit = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/stgl/activityAudit/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
||||
@@ -146,3 +146,7 @@ export const getActivityRatings = (activityId: string | number): AxiosPromise<Ac
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
// 注意:submitActivityForApproval 和 withdrawActivityApproval 方法已废弃
|
||||
// 现在直接使用 updateStglActivity 修改活动状态为8(已提交)或0(草稿)
|
||||
// 后端会在 updateByBo 方法中监听状态变化,自动创建或删除审批记录
|
||||
@@ -0,0 +1,310 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="活动审批"
|
||||
v-model="visible"
|
||||
width="600px"
|
||||
append-to-body
|
||||
:before-close="handleClose"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="活动名称:">
|
||||
<span>{{ activityInfo?.activityName || '加载中...' }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="活动类型:">
|
||||
<span>{{ activityInfo?.activityType || '加载中...' }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="审批节点:">
|
||||
<span>{{ auditRecord?.nodeName || '加载中...' }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="审批结果:" prop="approveResult">
|
||||
<el-radio-group v-model="form.approveResult" @change="handleApproveResultChange">
|
||||
<el-radio value="通过">通过</el-radio>
|
||||
<el-radio value="不通过">不通过</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="审批意见:" prop="approveOpinion">
|
||||
<el-input
|
||||
v-model="form.approveOpinion"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请填写审批意见"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="loading">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审批流程选择对话框 -->
|
||||
<ApprovalProcessDialog
|
||||
v-model:visible="showApprovalProcessDialog"
|
||||
:next-node-name="getNextNodeName()"
|
||||
@confirm="handleApprovalProcessConfirm"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { approveActivity, getActivityAudit, getActivityAuditsByActivityId } from '@/api/stgl/activity/audit';
|
||||
import { getStglActivity } from '@/api/stgl/activity';
|
||||
import ApprovalProcessDialog from './ApprovalProcessDialog.vue';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const emit = defineEmits<{
|
||||
success: [];
|
||||
}>();
|
||||
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const formRef = ref<ElFormInstance>();
|
||||
|
||||
const form = ref({
|
||||
auditId: '',
|
||||
approveResult: '',
|
||||
approveOpinion: ''
|
||||
});
|
||||
|
||||
const activityInfo = ref<any>(null);
|
||||
const auditRecord = ref<any>(null);
|
||||
const allAuditRecords = ref<any[]>([]);
|
||||
const showApprovalProcessDialog = ref(false);
|
||||
|
||||
const rules = reactive<ElFormRules>({
|
||||
approveResult: [
|
||||
{ required: true, message: '请选择审批结果', trigger: 'change' }
|
||||
],
|
||||
approveOpinion: [
|
||||
{ required: true, message: '请填写审批意见', trigger: 'blur' },
|
||||
{ max: 500, message: '审批意见不能超过500个字符', trigger: 'blur' }
|
||||
]
|
||||
});
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (auditId: string | number, activityId: string | number) => {
|
||||
visible.value = true;
|
||||
form.value.auditId = auditId.toString();
|
||||
|
||||
// 获取活动信息用于显示
|
||||
try {
|
||||
const res = await getStglActivity(activityId);
|
||||
activityInfo.value = res.data;
|
||||
} catch (error) {
|
||||
console.error('获取活动信息失败:', error);
|
||||
}
|
||||
|
||||
// 获取审批记录信息用于显示节点名称
|
||||
try {
|
||||
const auditRes = await getActivityAudit(auditId);
|
||||
auditRecord.value = auditRes.data;
|
||||
} catch (error) {
|
||||
console.error('获取审批记录信息失败:', error);
|
||||
}
|
||||
|
||||
// 获取当前活动的所有审批记录,用于判断是否是本环节最后一个节点
|
||||
try {
|
||||
const allAuditsRes = await getActivityAuditsByActivityId(activityId);
|
||||
allAuditRecords.value = allAuditsRes.data || [];
|
||||
} catch (error) {
|
||||
console.error('获取所有审批记录失败:', error);
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
resetForm();
|
||||
};
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
form.value.approveResult = '';
|
||||
form.value.approveOpinion = '';
|
||||
formRef.value?.clearValidate();
|
||||
};
|
||||
|
||||
/** 审批结果变化处理 */
|
||||
const handleApproveResultChange = (value: string) => {
|
||||
// 审批结果变化时的处理逻辑(如需要)
|
||||
};
|
||||
|
||||
/** 关闭弹窗 */
|
||||
const handleClose = () => {
|
||||
visible.value = false;
|
||||
resetForm();
|
||||
activityInfo.value = null;
|
||||
auditRecord.value = null;
|
||||
allAuditRecords.value = [];
|
||||
};
|
||||
|
||||
/** 提交审批 */
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return;
|
||||
|
||||
const valid = await formRef.value.validate();
|
||||
if (!valid) return;
|
||||
|
||||
// 如果是校级审批的最后一个节点,直接结束流程,不弹出审批流程选择对话框
|
||||
if (form.value.approveResult === '通过' && isSchoolLevelLastNode()) {
|
||||
console.log('校级审批最后节点,直接结束流程');
|
||||
await submitApproval();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果审批通过,且当前节点是本环节的最后一个节点,弹出审批流程选择对话框
|
||||
if (form.value.approveResult === '通过' && isLastNodeInCurrentPhase()) {
|
||||
showApprovalProcessDialog.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 直接提交审批(审批不通过或不是本环节最后一个节点的情况)
|
||||
await submitApproval();
|
||||
};
|
||||
|
||||
/** 判断当前节点是否是本环节的最后一个节点 */
|
||||
const isLastNodeInCurrentPhase = () => {
|
||||
if (!auditRecord.value || !allAuditRecords.value.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentNodeCode = auditRecord.value.nodeCode;
|
||||
const currentNodeOrder = Number(auditRecord.value.nodeOrder);
|
||||
|
||||
// 获取同一环节的所有节点
|
||||
const samePhaseNodes = allAuditRecords.value.filter(record =>
|
||||
record.nodeCode === currentNodeCode
|
||||
);
|
||||
|
||||
if (samePhaseNodes.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 找出同一环节中最大的nodeOrder
|
||||
const maxNodeOrder = Math.max(...samePhaseNodes.map(node => Number(node.nodeOrder)));
|
||||
|
||||
// 当前节点的nodeOrder等于同环节最大nodeOrder时,表示是本环节的最后一个节点
|
||||
const isLast = currentNodeOrder === maxNodeOrder;
|
||||
|
||||
console.log('判断是否是本环节最后节点:', {
|
||||
currentNodeCode,
|
||||
currentNodeOrder,
|
||||
maxNodeOrder,
|
||||
samePhaseNodesCount: samePhaseNodes.length,
|
||||
isLast
|
||||
});
|
||||
|
||||
return isLast;
|
||||
};
|
||||
|
||||
/** 判断当前是否是校级审批的最后一个节点 */
|
||||
const isSchoolLevelLastNode = () => {
|
||||
if (!auditRecord.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentNodeName = auditRecord.value.nodeName;
|
||||
const currentNodeOrder = Number(auditRecord.value.nodeOrder);
|
||||
|
||||
// 检查是否是校级审批节点且node_order是3.X格式
|
||||
const isSchoolLevelNode = currentNodeName === '校级审批';
|
||||
const isNodeOrder3X = currentNodeOrder >= 3 && currentNodeOrder < 4;
|
||||
|
||||
console.log('校级审批最后节点判断:', {
|
||||
currentNodeName,
|
||||
currentNodeOrder,
|
||||
isSchoolLevelNode,
|
||||
isNodeOrder3X,
|
||||
isLastInPhase: isLastNodeInCurrentPhase()
|
||||
});
|
||||
|
||||
// 校级审批 + node_order=3.X + 是本环节最后一个节点
|
||||
return isSchoolLevelNode && isNodeOrder3X && isLastNodeInCurrentPhase();
|
||||
};
|
||||
|
||||
/** 获取下一步节点名称 */
|
||||
const getNextNodeName = () => {
|
||||
if (!auditRecord.value) return '';
|
||||
|
||||
const currentNodeCode = auditRecord.value.nodeCode;
|
||||
|
||||
// 根据当前节点代码推断下一步节点名称
|
||||
if (currentNodeCode === 'advisor_approve') {
|
||||
return '二级学院审批';
|
||||
} else if (currentNodeCode === 'dept_approve') {
|
||||
return '校级审批';
|
||||
}
|
||||
|
||||
return '下一步审核';
|
||||
};
|
||||
|
||||
/** 审批流程选择确认 */
|
||||
const handleApprovalProcessConfirm = async (nextAuditors: any[]) => {
|
||||
await submitApproval(nextAuditors);
|
||||
};
|
||||
|
||||
/** 提交审批请求 */
|
||||
const submitApproval = async (nextAuditors?: any[]) => {
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
// 判断是否是校级审批最后节点
|
||||
const isSchoolLevelFinalNode = isSchoolLevelLastNode();
|
||||
|
||||
// 准备审批数据
|
||||
const params: any = {
|
||||
auditId: form.value.auditId, // 保持字符串格式,避免精度丢失
|
||||
approveResult: form.value.approveResult,
|
||||
approveOpinion: form.value.approveOpinion
|
||||
};
|
||||
|
||||
// 校级审批最后节点时不设置下一步审核人,表示流程结束
|
||||
// 其他情况:只有审批通过且是本环节最后一个节点时,才包含下一步审核人信息
|
||||
if (!isSchoolLevelFinalNode && nextAuditors && nextAuditors.length > 0) {
|
||||
params.nextAuditors = nextAuditors;
|
||||
}
|
||||
|
||||
console.log('提交审批数据:', {
|
||||
审批结果: params.approveResult,
|
||||
是否本环节最后节点: isLastNodeInCurrentPhase(),
|
||||
是否校级审批最后节点: isSchoolLevelFinalNode,
|
||||
下一步审核人数量: params.nextAuditors?.length || 0,
|
||||
流程状态: isSchoolLevelFinalNode ? '流程结束' : '继续流转'
|
||||
});
|
||||
|
||||
await approveActivity(params);
|
||||
|
||||
if (isSchoolLevelFinalNode) {
|
||||
proxy?.$modal.msgSuccess('审批成功,活动审批流程已完成!');
|
||||
} else {
|
||||
proxy?.$modal.msgSuccess('审批成功');
|
||||
}
|
||||
|
||||
handleClose();
|
||||
emit('success');
|
||||
} catch (error) {
|
||||
console.error('审批失败:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="选择审批流程"
|
||||
v-model="visible"
|
||||
width="70%"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<div class="approval-dialog-content">
|
||||
<el-alert
|
||||
:title="`请选择${props.nextNodeName}审核人`"
|
||||
description="活动审批提交后将按照您选择的审批人员顺序进行审批,请仔细选择审核人员。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 20px"
|
||||
/>
|
||||
|
||||
<!-- 下一步审批节点提示 -->
|
||||
<div class="next-node-info">
|
||||
<div class="node-label">
|
||||
<el-icon><Document /></el-icon>
|
||||
下一步审批节点:{{ props.nextNodeName }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 审核人选择区域 -->
|
||||
<div class="auditor-selection-section">
|
||||
<div class="custom-process-config">
|
||||
<el-form-item label="选择审核人" required>
|
||||
<div class="auditor-selector">
|
||||
<div class="selector-header">
|
||||
<el-button type="primary" icon="Plus" @click="openAuditorSelector">添加审核人</el-button>
|
||||
<el-text v-if="selectedAuditors.length > 0" type="info" size="small">
|
||||
已选择 {{ selectedAuditors.length }} 个审核人,将按添加顺序进行审批
|
||||
</el-text>
|
||||
</div>
|
||||
|
||||
<!-- 已选择的审核人列表 -->
|
||||
<div v-if="selectedAuditors.length > 0" class="selected-auditors">
|
||||
<div class="auditor-flow">
|
||||
<div v-for="(auditor, index) in selectedAuditors" :key="auditor.userId" class="auditor-node">
|
||||
<div class="node-content">
|
||||
<span class="node-index">{{ index + 1 }}</span>
|
||||
<div class="auditor-info">
|
||||
<div class="auditor-name">{{ auditor.nickName || auditor.userName }}</div>
|
||||
<div class="auditor-dept">{{ auditor.deptName || '未知部门' }}</div>
|
||||
</div>
|
||||
<el-button type="danger" icon="Delete" size="small" circle @click="removeAuditor(auditor)" />
|
||||
</div>
|
||||
<div v-if="index < selectedAuditors.length - 1" class="flow-arrow">
|
||||
<el-icon><ArrowRight /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 流程预览说明 -->
|
||||
<div class="flow-description">
|
||||
<el-alert type="info" :closable="false" show-icon>
|
||||
<template #title>
|
||||
<span>审批流程预览:{{ props.nextNodeName }}将按上述顺序进行逐级审批,每个节点审批通过后自动流转到下一节点</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态提示 -->
|
||||
<div v-else class="empty-state">
|
||||
<el-empty description="暂未选择审核人">
|
||||
<el-button type="primary" @click="openAuditorSelector">选择审核人</el-button>
|
||||
</el-empty>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleConfirm"
|
||||
:loading="submitting"
|
||||
:disabled="selectedAuditors.length === 0"
|
||||
>
|
||||
<el-icon><Check /></el-icon>
|
||||
确认提交 ({{ selectedAuditors.length }} 个审核人)
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审核人选择器 -->
|
||||
<AuditorSelector
|
||||
ref="auditorSelectorRef"
|
||||
@confirm="handleAuditorsSelected"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Check, User, UserFilled, Document, Plus, ArrowRight, Delete } from '@element-plus/icons-vue';
|
||||
import AuditorSelector from '@/views/stgl/club/info/annualReview/auditorSelector/index.vue';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
nextNodeName?: string; // 下一步审批节点名称
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
(e: 'confirm', auditors: any[]): void;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
nextNodeName: '部门审批'
|
||||
});
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = ref(props.visible);
|
||||
const submitting = ref(false);
|
||||
const selectedAuditors = ref<any[]>([]);
|
||||
const auditorSelectorRef = ref<InstanceType<typeof AuditorSelector>>();
|
||||
|
||||
// 监听visible变化
|
||||
watch(() => props.visible, (newVal) => {
|
||||
visible.value = newVal;
|
||||
if (newVal) {
|
||||
selectedAuditors.value = [];
|
||||
}
|
||||
});
|
||||
|
||||
// 监听内部visible变化
|
||||
watch(visible, (newVal) => {
|
||||
emit('update:visible', newVal);
|
||||
});
|
||||
|
||||
// 打开审核人选择器
|
||||
const openAuditorSelector = () => {
|
||||
auditorSelectorRef.value?.open(selectedAuditors.value);
|
||||
};
|
||||
|
||||
// 审核人选择确认
|
||||
const handleAuditorsSelected = (auditors: any[]) => {
|
||||
selectedAuditors.value = [...auditors];
|
||||
};
|
||||
|
||||
// 移除审核人
|
||||
const removeAuditor = (auditor: any) => {
|
||||
const index = selectedAuditors.value.findIndex(item => item.userId === auditor.userId);
|
||||
if (index > -1) {
|
||||
selectedAuditors.value.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
// 取消操作
|
||||
const handleCancel = () => {
|
||||
visible.value = false;
|
||||
ElMessage.info('已取消提交');
|
||||
};
|
||||
|
||||
// 确认提交
|
||||
const handleConfirm = async () => {
|
||||
try {
|
||||
if (selectedAuditors.value.length === 0) {
|
||||
ElMessage.warning('请至少选择一个审核人');
|
||||
return;
|
||||
}
|
||||
|
||||
submitting.value = true;
|
||||
|
||||
// 触发确认事件,返回审核人列表
|
||||
emit('confirm', selectedAuditors.value);
|
||||
|
||||
visible.value = false;
|
||||
ElMessage.success('审批流程配置成功');
|
||||
} catch (error) {
|
||||
console.error('确认审批流程失败:', error);
|
||||
ElMessage.error('确认审批流程失败');
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 暴露方法供父组件调用
|
||||
defineExpose({
|
||||
open: (preSelectedAuditors?: any[]) => {
|
||||
visible.value = true;
|
||||
selectedAuditors.value = [...(preSelectedAuditors || [])];
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.approval-dialog-content {
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.next-node-info {
|
||||
margin-bottom: 20px;
|
||||
padding: 12px 16px;
|
||||
background-color: #f0f9ff;
|
||||
border: 1px solid #d1ecf1;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.node-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 500;
|
||||
color: #0c63e4;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.auditor-selection-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.custom-process-config {
|
||||
padding: 24px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
background-color: #fafbfc;
|
||||
}
|
||||
|
||||
.auditor-selector {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.selector-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.selected-auditors {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.auditor-flow {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.auditor-node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.node-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
background: white;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.node-index {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: #409eff;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.auditor-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.auditor-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.auditor-dept {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.flow-arrow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #409eff;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.flow-description {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -117,12 +117,20 @@
|
||||
!single && selectedActivity && getOriginalActivityStatus(selectedActivity) === '0' && hasActivityOperationPermission(selectedActivity)
|
||||
"
|
||||
>
|
||||
<el-button type="success" icon="Upload" @click="handleSubmit()" v-hasPermi="['stgl:activity:edit']">提交</el-button>
|
||||
<el-button type="success" icon="Upload" @click="handleSubmitApproval()" v-hasPermi="['stgl:activity:edit']">提交审批</el-button>
|
||||
</el-col>
|
||||
<el-col
|
||||
:span="1.5"
|
||||
v-if="
|
||||
!single && selectedActivity && getOriginalActivityStatus(selectedActivity) === '2' && hasActivityOperationPermission(selectedActivity)
|
||||
!single && selectedActivity && getOriginalActivityStatus(selectedActivity) === '8' && hasActivityOperationPermission(selectedActivity)
|
||||
"
|
||||
>
|
||||
<el-button type="info" icon="RefreshLeft" @click="handleWithdrawApproval()" v-hasPermi="['stgl:activity:edit']">撤回审批</el-button>
|
||||
</el-col>
|
||||
<el-col
|
||||
:span="1.5"
|
||||
v-if="
|
||||
!single && selectedActivity && ['8', '9'].includes(getOriginalActivityStatus(selectedActivity) || '') && canApproveActivity(selectedActivity)
|
||||
"
|
||||
>
|
||||
<el-button type="warning" icon="Check" @click="handleApprove()" v-hasPermi="['stgl:activity:approve']">审批</el-button>
|
||||
@@ -283,7 +291,7 @@
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
content="提交"
|
||||
content="提交审批"
|
||||
placement="top"
|
||||
v-if="getOriginalActivityStatus(scope.row) === '0' && hasActivityOperationPermission(scope.row)"
|
||||
>
|
||||
@@ -293,14 +301,29 @@
|
||||
icon="Upload"
|
||||
size="default"
|
||||
style="font-size: 16px; margin-right: 8px"
|
||||
@click="handleSubmit(scope.row)"
|
||||
@click="handleSubmitApproval(scope.row)"
|
||||
v-hasPermi="['stgl:activity:edit']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
content="撤回审批"
|
||||
placement="top"
|
||||
v-if="getOriginalActivityStatus(scope.row) === '8' && hasActivityOperationPermission(scope.row)"
|
||||
>
|
||||
<el-button
|
||||
link
|
||||
type="info"
|
||||
icon="RefreshLeft"
|
||||
size="default"
|
||||
style="font-size: 16px; margin-right: 8px"
|
||||
@click="handleWithdrawApproval(scope.row)"
|
||||
v-hasPermi="['stgl:activity:edit']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
content="审批"
|
||||
placement="top"
|
||||
v-if="getOriginalActivityStatus(scope.row) === '2' && hasActivityOperationPermission(scope.row)"
|
||||
v-if="['8', '9'].includes(getOriginalActivityStatus(scope.row) || '') && canApproveActivity(scope.row)"
|
||||
>
|
||||
<el-button
|
||||
link
|
||||
@@ -661,29 +684,8 @@
|
||||
ref="membersDialogRef"
|
||||
/>
|
||||
|
||||
<!-- 审批对话框 -->
|
||||
<el-dialog v-model="approveDialog.visible" title="审批活动" width="500px" append-to-body>
|
||||
<el-form ref="approveFormRef" :model="approveForm" label-width="100px">
|
||||
<el-form-item label="活动名称">
|
||||
<el-input v-model="approveForm.activityName" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item label="审批结果" prop="approvalResult">
|
||||
<el-radio-group v-model="approveForm.approvalResult">
|
||||
<el-radio label="approve">通过</el-radio>
|
||||
<el-radio label="reject">驳回</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="驳回原因" prop="rejectReason" v-if="approveForm.approvalResult === 'reject'">
|
||||
<el-input v-model="approveForm.rejectReason" type="textarea" placeholder="请输入驳回原因" :rows="3" :maxlength="200" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="approveDialog.visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirmApprove" :loading="approveLoading">确认审批</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<!-- 新的审批对话框组件 -->
|
||||
<ApprovalDialog ref="approvalDialogRef" @success="handleApprovalSuccess" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -712,9 +714,11 @@ import ActivityRatingView from './components/ActivityRatingView.vue';
|
||||
import ActivityDetailView from './components/ActivityDetailView.vue';
|
||||
import ActivityMembersDialog from './components/ActivityMembersDialog.vue';
|
||||
import ActivityRegistrationView from './components/ActivityRegistrationView.vue'; // Added import
|
||||
import ApprovalDialog from './components/ApprovalDialog.vue';
|
||||
import { AdvisorVO } from '@/components/AdvisorManager/index.vue';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { checkClubActivityPermission, type ClubActivityPermissionResult } from '@/api/stgl/clubPermission';
|
||||
import { getPendingActivityAudits } from '@/api/stgl/activity/audit';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const route = useRoute();
|
||||
@@ -752,8 +756,8 @@ const queryFormRef = ref<ElFormInstance>();
|
||||
const activityFormRef = ref<ElFormInstance>();
|
||||
const copyFormRef = ref<ElFormInstance>();
|
||||
const summaryFormRef = ref<ElFormInstance>();
|
||||
const approveFormRef = ref<ElFormInstance>();
|
||||
const membersDialogRef = ref();
|
||||
const approvalDialogRef = ref<InstanceType<typeof ApprovalDialog>>();
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
@@ -780,23 +784,11 @@ const membersDialog = reactive<DialogOption>({
|
||||
title: ''
|
||||
});
|
||||
|
||||
// 审批对话框
|
||||
const approveDialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
// 审批表单
|
||||
const approveForm = reactive({
|
||||
id: undefined as string | number | undefined,
|
||||
activityName: '',
|
||||
approvalResult: 'approve' as 'approve' | 'reject',
|
||||
rejectReason: ''
|
||||
});
|
||||
// 待审批活动缓存(用于权限判断)
|
||||
const pendingAuditsCache = ref<Map<number, any>>(new Map());
|
||||
|
||||
const copyLoading = ref(false);
|
||||
const summaryLoading = ref(false);
|
||||
const approveLoading = ref(false);
|
||||
|
||||
// 关联社团禁用状态控制
|
||||
const isRelatedClubDisabled = ref(false);
|
||||
@@ -1005,12 +997,6 @@ const reset = () => {
|
||||
// 重置关联社团禁用状态
|
||||
isRelatedClubDisabled.value = false;
|
||||
|
||||
// 重置审批表单
|
||||
approveForm.id = undefined;
|
||||
approveForm.activityName = '';
|
||||
approveForm.approvalResult = 'approve';
|
||||
approveForm.rejectReason = '';
|
||||
|
||||
// 更新formKey以强制重新渲染组件
|
||||
formKey.value++;
|
||||
|
||||
@@ -1689,6 +1675,12 @@ const handleRelatedDeptsChange = (depts: DeptOption[] | null) => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 查询表单 - 关联部门变化处理 */
|
||||
const handleQueryRelatedDeptChange = (dept: DeptOption | null) => {
|
||||
queryParams.value.relatedDept = dept;
|
||||
queryParams.value.relatedDeptId = dept?.deptId;
|
||||
};
|
||||
|
||||
/** 关联社团变化处理 */
|
||||
const handleRelatedClubChange = (club: ClubOption | null) => {
|
||||
form.value.relatedClub = club;
|
||||
@@ -1776,18 +1768,135 @@ const handleSubmit = async (row?: StglActivityVO) => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 审批按钮操作 */
|
||||
/** 提交活动审批 */
|
||||
const handleSubmitApproval = async (row?: StglActivityVO) => {
|
||||
const activity = row || selectedActivity.value;
|
||||
if (!activity) return;
|
||||
|
||||
// 调试信息:打印关联社团ID
|
||||
console.log('🔍 提交审批 - 活动信息:', {
|
||||
activityId: activity.id,
|
||||
activityName: activity.activityName,
|
||||
relatedClubId: activity.relatedClubId,
|
||||
relatedClubIdType: typeof activity.relatedClubId,
|
||||
relatedClubName: activity.relatedClubName
|
||||
});
|
||||
|
||||
// 前置检查:必须关联社团
|
||||
if (!activity.relatedClubId) {
|
||||
proxy?.$modal.msgError('该活动未关联社团,无法提交审批。请先编辑活动并选择关联社团。');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await proxy?.$modal.confirm(`是否确认提交活动"${activity.activityName}"进行审批?`);
|
||||
|
||||
// 发送必填字段 + 业务必需字段(满足后端验证和业务逻辑)
|
||||
// 注意:relatedClubId 保持字符串类型,避免雪花ID精度丢失
|
||||
// 后端 Bo 已修改为接受 String 类型,在 Service 层转换为 Long
|
||||
const requestData = {
|
||||
id: activity.id,
|
||||
activityName: activity.activityName,
|
||||
activityType: activity.activityType,
|
||||
startTime: activity.startTime,
|
||||
endTime: activity.endTime,
|
||||
relatedClubId: String(activity.relatedClubId), // 保持字符串类型
|
||||
activityStatus: '8' // 8-已提交审批
|
||||
};
|
||||
|
||||
console.log('📤 发送审批请求:', requestData);
|
||||
|
||||
await updateStglActivity(requestData);
|
||||
|
||||
proxy?.$modal.msgSuccess('提交审批成功');
|
||||
await getList();
|
||||
// 重新加载待审批权限
|
||||
await loadPendingAudits();
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('❌ 提交审批失败:', error);
|
||||
proxy?.$modal.msgError('提交审批失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 撤回活动审批 */
|
||||
const handleWithdrawApproval = async (row?: StglActivityVO) => {
|
||||
const activity = row || selectedActivity.value;
|
||||
if (!activity) return;
|
||||
|
||||
try {
|
||||
await proxy?.$modal.confirm(`是否确认撤回活动"${activity.activityName}"的审批?`);
|
||||
|
||||
// 发送必填字段(满足后端EditGroup验证)
|
||||
await updateStglActivity({
|
||||
id: activity.id,
|
||||
activityName: activity.activityName,
|
||||
activityType: activity.activityType,
|
||||
startTime: activity.startTime,
|
||||
endTime: activity.endTime,
|
||||
activityStatus: '0' // 0-草稿
|
||||
});
|
||||
|
||||
proxy?.$modal.msgSuccess('撤回审批成功');
|
||||
await getList();
|
||||
// 重新加载待审批权限
|
||||
await loadPendingAudits();
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('撤回审批失败:', error);
|
||||
proxy?.$modal.msgError('撤回审批失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 审批活动 */
|
||||
const handleApprove = async (row?: StglActivityVO) => {
|
||||
const activity = row || selectedActivity.value;
|
||||
if (!activity) return;
|
||||
|
||||
// 显示审批对话框
|
||||
approveDialog.visible = true;
|
||||
approveDialog.title = `审批活动 - ${activity.activityName}`;
|
||||
approveForm.id = activity.id;
|
||||
approveForm.activityName = activity.activityName || '';
|
||||
approveForm.approvalResult = 'approve';
|
||||
approveForm.rejectReason = '';
|
||||
// 从缓存中获取审批记录
|
||||
const auditRecord = pendingAuditsCache.value.get(Number(activity.id));
|
||||
if (!auditRecord) {
|
||||
proxy?.$modal.msgWarning('无法获取审批记录信息');
|
||||
return;
|
||||
}
|
||||
|
||||
// 打开新的审批对话框
|
||||
approvalDialogRef.value?.open(auditRecord.id, activity.id);
|
||||
};
|
||||
|
||||
/** 审批成功回调 */
|
||||
const handleApprovalSuccess = async () => {
|
||||
await getList();
|
||||
// 重新加载待审批权限
|
||||
await loadPendingAudits();
|
||||
};
|
||||
|
||||
/** 判断是否可以审批活动 */
|
||||
const canApproveActivity = (activity: StglActivityVO | null): boolean => {
|
||||
if (!activity) return false;
|
||||
return pendingAuditsCache.value.has(Number(activity.id));
|
||||
};
|
||||
|
||||
/** 加载待审批权限 */
|
||||
const loadPendingAudits = async () => {
|
||||
try {
|
||||
const userId = userStore.userId;
|
||||
const res = await getPendingActivityAudits(userId);
|
||||
const audits = res.data || [];
|
||||
|
||||
// 构建待审批缓存
|
||||
const cache = new Map<number, any>();
|
||||
audits.forEach((audit: any) => {
|
||||
cache.set(Number(audit.activityId), audit);
|
||||
});
|
||||
pendingAuditsCache.value = cache;
|
||||
|
||||
console.log('待审批活动缓存:', pendingAuditsCache.value);
|
||||
} catch (error) {
|
||||
console.error('加载待审批权限失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/** 取消活动按钮操作 */
|
||||
@@ -1815,63 +1924,6 @@ const handleCancel = async (row?: StglActivityVO) => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 确认审批 */
|
||||
const confirmApprove = async () => {
|
||||
if (!approveFormRef.value) return;
|
||||
|
||||
approveLoading.value = true;
|
||||
try {
|
||||
const activity = activityList.value.find((item) => item.id === approveForm.id);
|
||||
if (!activity) {
|
||||
proxy?.$modal.msgError('活动信息不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
let newStatus: string;
|
||||
let successMessage: string;
|
||||
|
||||
if (approveForm.approvalResult === 'approve') {
|
||||
// 审批通过,根据时间比较确定状态
|
||||
const now = new Date();
|
||||
const startTime = new Date(activity.startTime);
|
||||
const endTime = new Date(activity.endTime);
|
||||
|
||||
if (now < startTime) {
|
||||
newStatus = '3'; // 已通过(活动还未开始)
|
||||
} else if (now >= startTime && now <= endTime) {
|
||||
newStatus = '4'; // 进行中(活动正在进行)
|
||||
} else {
|
||||
newStatus = '5'; // 已结束(活动已结束)
|
||||
}
|
||||
successMessage = '审批通过';
|
||||
} else {
|
||||
// 审批驳回
|
||||
if (!approveForm.rejectReason.trim()) {
|
||||
proxy?.$modal.msgWarning('请输入驳回原因');
|
||||
return;
|
||||
}
|
||||
newStatus = '7'; // 已驳回
|
||||
successMessage = '审批驳回';
|
||||
}
|
||||
|
||||
// 调用后端接口更新活动状态
|
||||
// 传递完整的活动数据,避免后端验证失败
|
||||
await updateStglActivity({
|
||||
...activity,
|
||||
activityStatus: newStatus
|
||||
});
|
||||
|
||||
proxy?.$modal.msgSuccess(successMessage);
|
||||
approveDialog.visible = false;
|
||||
await getList();
|
||||
} catch (error) {
|
||||
console.error('审批失败:', error);
|
||||
proxy?.$modal.msgError('审批失败');
|
||||
} finally {
|
||||
approveLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 格式化日期 */
|
||||
const formatDate = (dateStr: string): string => {
|
||||
if (!dateStr) return '';
|
||||
@@ -1989,6 +2041,28 @@ const getOriginalActivityStatus = (activity: StglActivityVO | null): string => {
|
||||
return activity.originalActivityStatus || activity.activityStatus || '';
|
||||
};
|
||||
|
||||
// 监听对话框打开,在新增模式下自动设置报名开始时间为当前时间
|
||||
watch(
|
||||
() => dialog.visible,
|
||||
(isVisible, wasVisible) => {
|
||||
// 当对话框从关闭变为打开,且是新增模式(无ID)时,自动设置报名开始时间
|
||||
if (isVisible && !wasVisible && !form.value.id) {
|
||||
nextTick(() => {
|
||||
// 获取当前时间并格式化为 YYYY-MM-DD HH:mm:ss
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(now.getDate()).padStart(2, '0');
|
||||
const hours = String(now.getHours()).padStart(2, '0');
|
||||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||
|
||||
form.value.registrationStart = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 监听活动开始时间变化,自动更新报名结束时间
|
||||
watch(
|
||||
() => form.value.startTime,
|
||||
@@ -2019,9 +2093,11 @@ watch(
|
||||
onMounted(() => {
|
||||
initUrlParams();
|
||||
getList();
|
||||
// 加载待审批权限
|
||||
loadPendingAudits();
|
||||
// 如果有关联社团,检查权限
|
||||
if (queryParams.value.relatedClub?.relatedClubId && queryParams.value.relatedClub?.relatedType === 1) {
|
||||
checkClubPermission(Number(queryParams.value.relatedClub.relatedClubId));
|
||||
checkClubPermission(queryParams.value.relatedClub.relatedClubId);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2030,7 +2106,7 @@ watch(
|
||||
() => queryParams.value.relatedClub,
|
||||
(newRelatedClub) => {
|
||||
if (newRelatedClub?.relatedClubId && newRelatedClub.relatedType === 1) {
|
||||
checkClubPermission(Number(newRelatedClub.relatedClubId));
|
||||
checkClubPermission(newRelatedClub.relatedClubId);
|
||||
} else {
|
||||
clubPermissionInfo.value = null;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user