@@ -0,0 +1,28 @@ | |||||
package com.ningdatech.pmapi.common.model.entity; | |||||
import lombok.AllArgsConstructor; | |||||
import lombok.Data; | |||||
import lombok.NoArgsConstructor; | |||||
/** | |||||
* <p> | |||||
* KeyValueDTO | |||||
* </p> | |||||
* | |||||
* @author WendyYang | |||||
* @since 16:40 2022/8/31 | |||||
*/ | |||||
@Data | |||||
@AllArgsConstructor | |||||
@NoArgsConstructor | |||||
public class KeyValDTO<K, V> { | |||||
private K key; | |||||
private V value; | |||||
public static <K, V> KeyValDTO<K, V> of(K k, V v) { | |||||
return new KeyValDTO<>(k, v); | |||||
} | |||||
} |
@@ -0,0 +1,25 @@ | |||||
package com.ningdatech.pmapi.expert.model.dto; | |||||
import com.ningdatech.pmapi.common.model.FileBasicInfo; | |||||
import lombok.Data; | |||||
import javax.validation.constraints.NotBlank; | |||||
import java.util.List; | |||||
/** | |||||
* @author liuxinxin | |||||
* @date 2022/7/28 下午3:48 | |||||
*/ | |||||
@Data | |||||
public class ModifyApplyExtraInfoDTO { | |||||
/** | |||||
* 情况说明 | |||||
*/ | |||||
@NotBlank | |||||
private String factSheet; | |||||
/** | |||||
* 证明材料 | |||||
*/ | |||||
private List<FileBasicInfo> evidenceList; | |||||
} |
@@ -0,0 +1,70 @@ | |||||
package com.ningdatech.pmapi.leave.controller; | |||||
import com.ningdatech.basic.exception.BizException; | |||||
import com.ningdatech.basic.model.IdVo; | |||||
import com.ningdatech.basic.model.PageVo; | |||||
import com.ningdatech.pmapi.leave.entity.po.LeaveCreateReq; | |||||
import com.ningdatech.pmapi.leave.entity.po.LeaveListByCreatorReq; | |||||
import com.ningdatech.pmapi.leave.entity.vo.LeaveDetailVO; | |||||
import com.ningdatech.pmapi.leave.entity.vo.LeaveListItemVO; | |||||
import com.ningdatech.pmapi.leave.manage.LeaveManage; | |||||
import io.swagger.annotations.Api; | |||||
import io.swagger.annotations.ApiOperation; | |||||
import lombok.AllArgsConstructor; | |||||
import org.apache.commons.lang3.ObjectUtils; | |||||
import org.springframework.web.bind.annotation.*; | |||||
import javax.validation.Valid; | |||||
/** | |||||
* <p> | |||||
* 前端控制器 | |||||
* </p> | |||||
* | |||||
* @author WendyYang | |||||
* @since 2022-08-11 | |||||
*/ | |||||
@Api(tags = "请假管理控制器") | |||||
@RestController | |||||
@AllArgsConstructor | |||||
@RequestMapping("/api/v1/leave/") | |||||
public class ExpertLeaveController { | |||||
private final LeaveManage leaveManage; | |||||
@ApiOperation("请假") | |||||
@PostMapping("/save") | |||||
public IdVo<Long> askForLeave(@Valid @RequestBody LeaveCreateReq po) { | |||||
return leaveManage.askForLeave(po); | |||||
} | |||||
@ApiOperation("请假详情") | |||||
@GetMapping(value = {"/detail/{leaveId}", "/detail/{auditId}/byApply"}) | |||||
public LeaveDetailVO detail(@PathVariable(required = false) Long leaveId, | |||||
@PathVariable(required = false) Long auditId) { | |||||
if (ObjectUtils.allNull(leaveId, auditId)) { | |||||
throw BizException.wrap("请假ID或申请ID不能为空"); | |||||
} | |||||
return leaveManage.leaveDetail(leaveId, auditId); | |||||
} | |||||
@ApiOperation("我的请假列表(发起人)") | |||||
@GetMapping("/leaveListByCreator") | |||||
public PageVo<LeaveListItemVO> leaveListByCreator(LeaveListByCreatorReq po) { | |||||
return leaveManage.leaveListByCreator(po); | |||||
} | |||||
@ApiOperation("销假") | |||||
@GetMapping("/endLeave") | |||||
public void endLeave(Long leaveId) { | |||||
leaveManage.endLeave(leaveId); | |||||
} | |||||
@ApiOperation("撤销") | |||||
@GetMapping("/cancel") | |||||
public void cancelLeave(Long leaveId) { | |||||
leaveManage.cancel(leaveId); | |||||
} | |||||
} |
@@ -0,0 +1,83 @@ | |||||
package com.ningdatech.pmapi.leave.entity.domain; | |||||
import com.baomidou.mybatisplus.annotation.IdType; | |||||
import com.baomidou.mybatisplus.annotation.TableId; | |||||
import com.baomidou.mybatisplus.annotation.TableName; | |||||
import io.swagger.annotations.ApiModel; | |||||
import io.swagger.annotations.ApiModelProperty; | |||||
import lombok.Data; | |||||
import java.io.Serializable; | |||||
import java.time.LocalDateTime; | |||||
/** | |||||
* <p> | |||||
* 请假记录表 | |||||
* </p> | |||||
* | |||||
* @author WendyYang | |||||
* @since 2022-08-11 | |||||
*/ | |||||
@Data | |||||
@TableName("expert_leave") | |||||
@ApiModel(value = "ExpertLeave对象", description = "请假记录表") | |||||
public class ExpertLeave implements Serializable { | |||||
private static final long serialVersionUID = 1L; | |||||
@ApiModelProperty("主键") | |||||
@TableId(type = IdType.AUTO) | |||||
private Long id; | |||||
@ApiModelProperty("请假类型") | |||||
private Integer type; | |||||
@ApiModelProperty("请假说明") | |||||
private String remark; | |||||
@ApiModelProperty("附件ID") | |||||
private String attachment; | |||||
@ApiModelProperty("审核表ID") | |||||
private Long auditId; | |||||
@ApiModelProperty("请假人ID") | |||||
private Long leaveUserId; | |||||
@ApiModelProperty("创建人名称") | |||||
private String creator; | |||||
@ApiModelProperty("创建人") | |||||
private Long createBy; | |||||
@ApiModelProperty("创建时间") | |||||
private LocalDateTime createOn; | |||||
@ApiModelProperty("修改人") | |||||
private Long updateBy; | |||||
@ApiModelProperty("修改时间") | |||||
private LocalDateTime updateOn; | |||||
@ApiModelProperty("请假开始时间") | |||||
private LocalDateTime startTime; | |||||
@ApiModelProperty("请假结束时间") | |||||
private LocalDateTime endTime; | |||||
@ApiModelProperty("销假时间") | |||||
private LocalDateTime cancelTime; | |||||
@ApiModelProperty("固定时段模式") | |||||
private String fixedType; | |||||
private Long meetingId; | |||||
private Integer status; | |||||
public void setUpdateAndCreate(Long createBy, LocalDateTime createOn) { | |||||
this.updateBy = this.createBy = createBy; | |||||
this.updateOn = this.createOn = createOn; | |||||
} | |||||
} |
@@ -0,0 +1,49 @@ | |||||
package com.ningdatech.pmapi.leave.entity.domain; | |||||
import com.baomidou.mybatisplus.annotation.IdType; | |||||
import com.baomidou.mybatisplus.annotation.TableId; | |||||
import com.baomidou.mybatisplus.annotation.TableName; | |||||
import io.swagger.annotations.ApiModel; | |||||
import io.swagger.annotations.ApiModelProperty; | |||||
import lombok.Data; | |||||
import lombok.NoArgsConstructor; | |||||
import java.io.Serializable; | |||||
import java.time.LocalDateTime; | |||||
/** | |||||
* <p> | |||||
* 专家请假详情表(维度:天) | |||||
* </p> | |||||
* | |||||
* @author WendyYang | |||||
* @since 2022-08-11 | |||||
*/ | |||||
@Data | |||||
@NoArgsConstructor | |||||
@TableName("expert_leave_detail") | |||||
@ApiModel(value = "ExpertLeaveDetail对象", description = "专家请假详情表(维度:天)") | |||||
public class ExpertLeaveDetail implements Serializable { | |||||
private static final long serialVersionUID = 1L; | |||||
@ApiModelProperty("主键") | |||||
@TableId(type = IdType.AUTO) | |||||
private Long id; | |||||
@ApiModelProperty("专家请假ID") | |||||
private Long expertLeaveId; | |||||
@ApiModelProperty("开始时间") | |||||
private LocalDateTime startTime; | |||||
@ApiModelProperty("结束时间") | |||||
private LocalDateTime endTime; | |||||
public ExpertLeaveDetail(Long expertLeaveId, LocalDateTime startTime, LocalDateTime endTime) { | |||||
this.expertLeaveId = expertLeaveId; | |||||
this.startTime = startTime; | |||||
this.endTime = endTime; | |||||
} | |||||
} |
@@ -0,0 +1,45 @@ | |||||
package com.ningdatech.pmapi.leave.entity.enumeration; | |||||
import lombok.Getter; | |||||
import java.util.Arrays; | |||||
/** | |||||
* <p> | |||||
* LeaveStatus | |||||
* </p> | |||||
* | |||||
* @author WendyYang | |||||
* @since 09:39 2022/8/12 | |||||
*/ | |||||
@Getter | |||||
public enum LeaveStatusEnum { | |||||
/** | |||||
* 请假状态 | |||||
*/ | |||||
APPLYING(1, "审核中"), | |||||
PASSED(2, "审核通过"), | |||||
PASSED_END(3, "审核通过且销假"), | |||||
UN_PASSED(4, "审核不通过"), | |||||
CANCELED(5, "发起人撤销"); | |||||
private final Integer code; | |||||
private final String name; | |||||
public boolean eq(Integer code) { | |||||
return this.getCode().equals(code); | |||||
} | |||||
LeaveStatusEnum(Integer code, String name) { | |||||
this.code = code; | |||||
this.name = name; | |||||
} | |||||
public static LeaveStatusEnum getByCode(Integer code) { | |||||
return Arrays.stream(values()) | |||||
.filter(w -> w.getCode().equals(code)) | |||||
.findFirst() | |||||
.orElseThrow(() -> new IllegalArgumentException("无效的请假状态")); | |||||
} | |||||
} |
@@ -0,0 +1,41 @@ | |||||
package com.ningdatech.pmapi.leave.entity.enumeration; | |||||
import lombok.Getter; | |||||
import java.util.Arrays; | |||||
/** | |||||
* <p> | |||||
* LeaveType-请假类型 | |||||
* </p> | |||||
* | |||||
* @author WendyYang | |||||
* @since 10:23 2022/8/11 | |||||
*/ | |||||
@Getter | |||||
public enum LeaveTypeEnum { | |||||
TEMPORARY(3, "临时请假"), | |||||
LONG_TERM(1, "长期请假"), | |||||
FIXED_TERM(2, "固定时段请假"); | |||||
private final Integer code; | |||||
private final String name; | |||||
public boolean eq(Integer code) { | |||||
return this.getCode().equals(code); | |||||
} | |||||
LeaveTypeEnum(Integer code, String name) { | |||||
this.code = code; | |||||
this.name = name; | |||||
} | |||||
public static LeaveTypeEnum getByCode(Integer code) { | |||||
return Arrays.stream(values()) | |||||
.filter(w -> w.getCode().equals(code)) | |||||
.findFirst() | |||||
.orElseThrow(() -> new IllegalArgumentException("无效的请假类型")); | |||||
} | |||||
} |
@@ -0,0 +1,73 @@ | |||||
package com.ningdatech.pmapi.leave.entity.po; | |||||
import com.ningdatech.basic.exception.BizException; | |||||
import com.ningdatech.pmapi.leave.entity.enumeration.LeaveTypeEnum; | |||||
import io.swagger.annotations.ApiModel; | |||||
import io.swagger.annotations.ApiModelProperty; | |||||
import lombok.Data; | |||||
import org.apache.commons.lang3.ObjectUtils; | |||||
import javax.validation.constraints.NotBlank; | |||||
import javax.validation.constraints.NotNull; | |||||
import java.time.LocalDateTime; | |||||
import java.util.List; | |||||
/** | |||||
* <p> | |||||
* LeaveCreatePo | |||||
* </p> | |||||
* | |||||
* @author WendyYang | |||||
* @since 10:30 2022/8/11 | |||||
*/ | |||||
@Data | |||||
@ApiModel("请假创建实体") | |||||
public class LeaveCreateReq { | |||||
@ApiModelProperty("请假类型:1 长期请假、2 固定时段请假、3 临时请假") | |||||
@NotNull(message = "请假类型不能为空") | |||||
private Integer type; | |||||
@ApiModelProperty("请假开始时间") | |||||
private LocalDateTime startTime; | |||||
@ApiModelProperty("请假结束时间") | |||||
private LocalDateTime endTime; | |||||
@ApiModelProperty("请假说明") | |||||
@NotBlank(message = "请假说明不能为空") | |||||
private String postscript; | |||||
@ApiModelProperty("附件ID") | |||||
private List<Long> attachments; | |||||
@ApiModelProperty("固定时段") | |||||
private List<Integer> fixedType; | |||||
@ApiModelProperty("会议ID") | |||||
private Long meetingId; | |||||
@ApiModelProperty("专家ID:管理员替专家请假时传参") | |||||
private Long expertId; | |||||
public void paramCheck(LeaveTypeEnum leaveTypeEnum) { | |||||
if (leaveTypeEnum.equals(LeaveTypeEnum.TEMPORARY)) { | |||||
if (this.getMeetingId() == null) { | |||||
throw new BizException("会议ID不能为空"); | |||||
} | |||||
} else { | |||||
if (ObjectUtils.anyNull(this.getStartTime(), this.getEndTime())) { | |||||
throw new BizException("开始时间或结束时间不能为空"); | |||||
} | |||||
if (!this.getStartTime().isBefore(this.getEndTime())) { | |||||
throw new BizException("无效的请假时间"); | |||||
} | |||||
if (leaveTypeEnum.equals(LeaveTypeEnum.FIXED_TERM)) { | |||||
if (this.getFixedType() == null || this.getFixedType().isEmpty()) { | |||||
throw new BizException("固定时段不能为空"); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,43 @@ | |||||
package com.ningdatech.pmapi.leave.entity.po; | |||||
import com.ningdatech.basic.model.PagePo; | |||||
import com.ningdatech.pmapi.sms.constant.DatePattern; | |||||
import io.swagger.annotations.ApiModel; | |||||
import io.swagger.annotations.ApiModelProperty; | |||||
import lombok.Data; | |||||
import lombok.EqualsAndHashCode; | |||||
import org.springframework.format.annotation.DateTimeFormat; | |||||
import java.time.LocalDateTime; | |||||
/** | |||||
* <p> | |||||
* LeaveListByCreatorPo | |||||
* </p> | |||||
* | |||||
* @author WendyYang | |||||
* @since 19:32 2022/8/11 | |||||
*/ | |||||
@Data | |||||
@ApiModel("我的请假列表参数实体") | |||||
@EqualsAndHashCode(callSuper = true) | |||||
public class LeaveListByCreatorReq extends PagePo { | |||||
@ApiModelProperty("开始时间") | |||||
@DateTimeFormat(pattern = DatePattern.YMD_HMS) | |||||
private LocalDateTime startTime; | |||||
@ApiModelProperty("结束时间") | |||||
@DateTimeFormat(pattern = DatePattern.YMD_HMS) | |||||
private LocalDateTime endTime; | |||||
@ApiModelProperty("请假类型") | |||||
private Integer leaveType; | |||||
@ApiModelProperty("审核状态") | |||||
private String auditStatus; | |||||
@ApiModelProperty("专家ID") | |||||
private Long expertId; | |||||
} |
@@ -0,0 +1,46 @@ | |||||
package com.ningdatech.pmapi.leave.entity.vo; | |||||
import com.ningdatech.file.entity.vo.result.AttachFileVo; | |||||
import lombok.Builder; | |||||
import lombok.Data; | |||||
import java.time.LocalDateTime; | |||||
import java.util.List; | |||||
/** | |||||
* <p> | |||||
* LeaveDetailVo | |||||
* </p> | |||||
* | |||||
* @author WendyYang | |||||
* @since 11:48 2022/8/12 | |||||
*/ | |||||
@Data | |||||
@Builder | |||||
public class LeaveDetailVO { | |||||
private Long id; | |||||
private String postscript; | |||||
private LocalDateTime startTime; | |||||
private LocalDateTime endTime; | |||||
private LocalDateTime actualEndTime; | |||||
private Integer status; | |||||
private List<AttachFileVo> attachments; | |||||
private Integer type; | |||||
private List<Integer> fixedType; | |||||
private LocalDateTime createOn; | |||||
private String createBy; | |||||
private Long auditId; | |||||
} |
@@ -0,0 +1,61 @@ | |||||
package com.ningdatech.pmapi.leave.entity.vo; | |||||
import com.ningdatech.file.entity.vo.result.AttachFileVo; | |||||
import io.swagger.annotations.ApiModel; | |||||
import io.swagger.annotations.ApiModelProperty; | |||||
import lombok.Data; | |||||
import java.time.LocalDateTime; | |||||
import java.util.List; | |||||
/** | |||||
* <p> | |||||
* LeaveListItemByCreatorVo | |||||
* </p> | |||||
* | |||||
* @author WendyYang | |||||
* @since 20:53 2022/8/11 | |||||
*/ | |||||
@Data | |||||
@ApiModel("我得请假列表实体(发起人)") | |||||
public class LeaveListItemVO { | |||||
@ApiModelProperty("请假ID") | |||||
private Long leaveId; | |||||
@ApiModelProperty("审核ID") | |||||
private Long auditId; | |||||
@ApiModelProperty("开始时间") | |||||
private LocalDateTime startTime; | |||||
@ApiModelProperty("结束时间") | |||||
private LocalDateTime endTime; | |||||
@ApiModelProperty("实际结束时间") | |||||
private LocalDateTime actualEndTime; | |||||
@ApiModelProperty("请假说明") | |||||
private String postscript; | |||||
@ApiModelProperty("说明材料") | |||||
private List<AttachFileVo> attachments; | |||||
@ApiModelProperty("请假类型") | |||||
private Integer leaveType; | |||||
@ApiModelProperty("相关会议") | |||||
private String meetingName; | |||||
@ApiModelProperty("审核状态") | |||||
private String auditStatus; | |||||
@ApiModelProperty("状态") | |||||
private Integer status; | |||||
@ApiModelProperty("审核意见") | |||||
private String auditOption; | |||||
private LocalDateTime createOn; | |||||
} |
@@ -0,0 +1,514 @@ | |||||
package com.ningdatech.pmapi.leave.manage; | |||||
import cn.hutool.core.collection.CollUtil; | |||||
import cn.hutool.core.lang.Assert; | |||||
import cn.hutool.core.util.StrUtil; | |||||
import cn.hutool.json.JSONUtil; | |||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers; | |||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | |||||
import com.ningdatech.basic.exception.BizException; | |||||
import com.ningdatech.basic.model.IdVo; | |||||
import com.ningdatech.basic.model.PagePo; | |||||
import com.ningdatech.basic.model.PageVo; | |||||
import com.ningdatech.basic.util.CollUtils; | |||||
import com.ningdatech.file.entity.vo.result.AttachFileVo; | |||||
import com.ningdatech.file.service.FileService; | |||||
import com.ningdatech.pmapi.common.model.FileBasicInfo; | |||||
import com.ningdatech.pmapi.common.model.entity.KeyValDTO; | |||||
import com.ningdatech.pmapi.common.util.BizUtils; | |||||
import com.ningdatech.pmapi.common.util.StrUtils; | |||||
import com.ningdatech.pmapi.expert.constant.ExpertApplyStatusEnum; | |||||
import com.ningdatech.pmapi.expert.constant.ExpertApplyTypeEnum; | |||||
import com.ningdatech.pmapi.expert.entity.ExpertMetaApply; | |||||
import com.ningdatech.pmapi.expert.entity.ExpertUserFullInfo; | |||||
import com.ningdatech.pmapi.expert.model.dto.ModifyApplyExtraInfoDTO; | |||||
import com.ningdatech.pmapi.expert.service.IExpertMetaApplyService; | |||||
import com.ningdatech.pmapi.expert.service.IExpertUserFullInfoService; | |||||
import com.ningdatech.pmapi.leave.entity.domain.ExpertLeave; | |||||
import com.ningdatech.pmapi.leave.entity.domain.ExpertLeaveDetail; | |||||
import com.ningdatech.pmapi.leave.entity.enumeration.LeaveStatusEnum; | |||||
import com.ningdatech.pmapi.leave.entity.enumeration.LeaveTypeEnum; | |||||
import com.ningdatech.pmapi.leave.entity.po.LeaveCreateReq; | |||||
import com.ningdatech.pmapi.leave.entity.po.LeaveListByCreatorReq; | |||||
import com.ningdatech.pmapi.leave.entity.vo.LeaveDetailVO; | |||||
import com.ningdatech.pmapi.leave.entity.vo.LeaveListItemVO; | |||||
import com.ningdatech.pmapi.leave.service.IExpertLeaveDetailService; | |||||
import com.ningdatech.pmapi.leave.service.IExpertLeaveService; | |||||
import com.ningdatech.pmapi.meeting.entity.domain.Meeting; | |||||
import com.ningdatech.pmapi.meeting.entity.domain.MeetingExpert; | |||||
import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertAttendStatusEnum; | |||||
import com.ningdatech.pmapi.meeting.entity.enumeration.MeetingStatusEnum; | |||||
import com.ningdatech.pmapi.meeting.helper.YxtCallOrSmsHelper; | |||||
import com.ningdatech.pmapi.meeting.service.IMeetingExpertService; | |||||
import com.ningdatech.pmapi.meeting.service.IMeetingService; | |||||
import com.ningdatech.pmapi.meeting.task.ExpertInviteTask; | |||||
import com.ningdatech.pmapi.sms.utils.DateUtil; | |||||
import com.ningdatech.pmapi.user.service.IUserInfoService; | |||||
import com.ningdatech.pmapi.user.util.LoginUserUtil; | |||||
import lombok.AllArgsConstructor; | |||||
import org.springframework.aop.framework.AopContext; | |||||
import org.springframework.stereotype.Component; | |||||
import org.springframework.transaction.annotation.Transactional; | |||||
import java.time.LocalDate; | |||||
import java.time.LocalDateTime; | |||||
import java.util.*; | |||||
import java.util.stream.Collectors; | |||||
/** | |||||
* <p> | |||||
* LeaveManage | |||||
* </p> | |||||
* | |||||
* @author WendyYang | |||||
* @since 10:38 2022/8/11 | |||||
*/ | |||||
@Component | |||||
@AllArgsConstructor | |||||
public class LeaveManage { | |||||
private final ExpertInviteTask inviteTask; | |||||
private final IMeetingService meetingService; | |||||
private final IMeetingExpertService meetingExpertService; | |||||
private final IExpertLeaveService leaveService; | |||||
private final IExpertLeaveDetailService leaveDetailService; | |||||
private final IExpertMetaApplyService metaApplyService; | |||||
private final FileService fileService; | |||||
private final IExpertUserFullInfoService userFullInfoService; | |||||
private final YxtCallOrSmsHelper yxtCallOrSmsHelper; | |||||
private final IUserInfoService userInfoService; | |||||
private static final int HOURS_BEFORE_MEETING = 12; | |||||
public void cancelMeetingExpertByLeave(Long meetingExpertId, ExpertAttendStatusEnum status) { | |||||
MeetingExpert update = buildMeetingExpertUpdate(meetingExpertId, status); | |||||
meetingExpertService.updateById(update); | |||||
} | |||||
private MeetingExpert buildMeetingExpertUpdate(Long meetingExpertId, ExpertAttendStatusEnum status) { | |||||
MeetingExpert expert = new MeetingExpert(); | |||||
expert.setId(meetingExpertId); | |||||
expert.setStatus(status.getCode()); | |||||
return expert; | |||||
} | |||||
private List<MeetingExpert> listMeetingExpert(Long expertId, List<KeyValDTO<LocalDateTime, LocalDateTime>> leaveTimes) { | |||||
// 先查询专家参与的所有会议 | |||||
LambdaQueryWrapper<MeetingExpert> meetingExpertQuery = Wrappers.lambdaQuery(MeetingExpert.class) | |||||
.eq(MeetingExpert::getExpertId, expertId) | |||||
.in(MeetingExpert::getStatus, Arrays.asList(ExpertAttendStatusEnum.NOTICING.getCode(), | |||||
ExpertAttendStatusEnum.AGREED.getCode())); | |||||
List<MeetingExpert> meetingExperts = meetingExpertService.list(meetingExpertQuery); | |||||
if (meetingExperts.isEmpty()) { | |||||
return Collections.emptyList(); | |||||
} | |||||
// 只需要查询待确认的或者是同意参加的 | |||||
Map<Long, MeetingExpert> expertMap = meetingExperts.stream() | |||||
.collect(Collectors.groupingBy(MeetingExpert::getMeetingId, | |||||
Collectors.collectingAndThen(Collectors.toList(), w -> { | |||||
w.sort(Comparator.comparing(MeetingExpert::getUpdateOn).reversed()); | |||||
return w.get(0); | |||||
}))); | |||||
List<Long> meetingIds = CollUtils.fieldList(expertMap.values(), MeetingExpert::getMeetingId); | |||||
LocalDateTime startTime = leaveTimes.get(0).getKey(), endTime = leaveTimes.get(leaveTimes.size() - 1).getValue(); | |||||
LambdaQueryWrapper<Meeting> meetingQuery = Wrappers.lambdaQuery(Meeting.class) | |||||
.in(Meeting::getId, meetingIds) | |||||
.ne(Meeting::getStatus, MeetingStatusEnum.CANCELED.getCode()) | |||||
.and(w -> w.between(Meeting::getStartTime, startTime, endTime) | |||||
.or(or -> or.between(Meeting::getEndTime, startTime, endTime))); | |||||
List<Meeting> meetingList = meetingService.list(meetingQuery); | |||||
List<Long> matchMeetingIds = meetingList.stream() | |||||
.filter(w -> leaveTimes.stream() | |||||
.anyMatch(tp -> DateUtil.intersect(w.getStartTime(), w.getEndTime(), tp.getKey(), tp.getValue()))) | |||||
.map(Meeting::getId).collect(Collectors.toList()); | |||||
if (matchMeetingIds.isEmpty()) { | |||||
return Collections.emptyList(); | |||||
} | |||||
return matchMeetingIds.stream().map(expertMap::get).collect(Collectors.toList()); | |||||
} | |||||
@Transactional(rollbackFor = Exception.class) | |||||
public IdVo<Long> askForLeave(LeaveCreateReq po) { | |||||
Long leaveUserId, applyUserId = Objects.requireNonNull(LoginUserUtil.getUserId()); | |||||
leaveUserId = po.getExpertId() != null ? po.getExpertId() : applyUserId; | |||||
boolean leaveForSelf = leaveUserId.equals(applyUserId); | |||||
if (!leaveForSelf) { | |||||
Assert.isFalse(leaveService.existsToBeReviewed(leaveUserId), "该专家存在待审核请假申请,请及时审核"); | |||||
} | |||||
LeaveTypeEnum type = LeaveTypeEnum.getByCode(po.getType()); | |||||
// 校验参数是否合法 | |||||
po.paramCheck(type); | |||||
ExpertLeave leave = new ExpertLeave(); | |||||
if (po.getAttachments() != null) { | |||||
leave.setAttachment(CollUtils.joinByComma(po.getAttachments())); | |||||
} | |||||
LocalDateTime now = LocalDateTime.now(); | |||||
leave.setType(po.getType()); | |||||
leave.setRemark(po.getPostscript()); | |||||
leave.setLeaveUserId(leaveUserId); | |||||
leave.setUpdateAndCreate(applyUserId, now); | |||||
List<KeyValDTO<LocalDateTime, LocalDateTime>> leaveDetailTimes = new ArrayList<>(); | |||||
if (type.equals(LeaveTypeEnum.FIXED_TERM) || type.equals(LeaveTypeEnum.LONG_TERM)) { | |||||
boolean fixedTerm = CollUtil.isNotEmpty(po.getFixedType()); | |||||
if (fixedTerm) { | |||||
// 固定时段请假 | |||||
leave.setFixedType(CollUtils.joinByComma(po.getFixedType())); | |||||
} | |||||
LocalDateTime tempStart = po.getStartTime(); | |||||
while (!tempStart.isAfter(po.getEndTime())) { | |||||
boolean add = true; | |||||
if (fixedTerm) { | |||||
int weekday = tempStart.getDayOfWeek().getValue(); | |||||
add = po.getFixedType().contains(weekday); | |||||
} | |||||
if (add) { | |||||
LocalDateTime tempEnd = tempStart.toLocalDate().atTime(DateUtil.LOCAL_TIME_3D); | |||||
leaveDetailTimes.add(KeyValDTO.of(tempStart, DateUtil.min(tempEnd, po.getEndTime()))); | |||||
} | |||||
tempStart = tempStart.plusDays(1).toLocalDate().atStartOfDay(); | |||||
} | |||||
if (leaveDetailTimes.isEmpty()) { | |||||
throw BizException.wrap("请假时间无效"); | |||||
} | |||||
if (leaveDetailService.existsLeaveByUserIdAndTime(leaveUserId, leaveDetailTimes)) { | |||||
throw BizException.wrap("该请假时段内已存在请假"); | |||||
} | |||||
List<MeetingExpert> meetingExperts = listMeetingExpert(leaveUserId, leaveDetailTimes); | |||||
if (!meetingExperts.isEmpty()) { | |||||
if (meetingExperts.stream().anyMatch(w -> ExpertAttendStatusEnum.AGREED.eq(w.getStatus()))) { | |||||
throw BizException.wrap("有待参加会议,请先处理完会议,再发起请假"); | |||||
} | |||||
List<MeetingExpert> expertsUpdate = meetingExperts.stream() | |||||
.map(w -> buildMeetingExpertUpdate(w.getExpertId(), ExpertAttendStatusEnum.ON_LEAVE)) | |||||
.collect(Collectors.toList()); | |||||
meetingExpertService.updateBatchById(expertsUpdate); | |||||
} | |||||
leave.setStatus(LeaveStatusEnum.APPLYING.getCode()); | |||||
} else if (type.equals(LeaveTypeEnum.TEMPORARY)) { | |||||
// 临时请假 | |||||
Meeting meeting = meetingService.getById(po.getMeetingId()); | |||||
if (meeting.getStatus().equals(MeetingStatusEnum.CANCELED.getCode())) { | |||||
throw BizException.wrap("该会议已取消"); | |||||
} | |||||
po.setStartTime(meeting.getStartTime()); | |||||
po.setEndTime(meeting.getEndTime()); | |||||
if (now.plusHours(HOURS_BEFORE_MEETING).isAfter(po.getStartTime())) { | |||||
throw BizException.wrap("会议临期" + HOURS_BEFORE_MEETING + "小时内,不能在线请假,请电话该主题事务的联系人。"); | |||||
} | |||||
MeetingExpert expert = meetingExpertService.getByMeetingIdAndExpertId(po.getMeetingId(), leaveUserId); | |||||
if (!expert.getStatus().equals(ExpertAttendStatusEnum.AGREED.getCode())) { | |||||
// 非确认参加状态无法临时请假 | |||||
throw BizException.wrap("未同意参加该会议,无法临时请假"); | |||||
} | |||||
LocalDateTime tempStart = po.getStartTime(); | |||||
while (!tempStart.isAfter(po.getEndTime())) { | |||||
LocalDateTime tempEnd = tempStart.toLocalDate().atTime(DateUtil.LOCAL_TIME_3D); | |||||
leaveDetailTimes.add(KeyValDTO.of(tempStart, DateUtil.min(tempEnd, po.getEndTime()))); | |||||
tempStart = tempStart.plusDays(1).toLocalDate().atStartOfDay(); | |||||
} | |||||
LeaveManage proxy = (LeaveManage) AopContext.currentProxy(); | |||||
proxy.cancelMeetingExpertByLeave(expert.getId(), ExpertAttendStatusEnum.ON_LEAVE); | |||||
inviteTask.notifyInviteTask(meeting.getId(), Boolean.FALSE); | |||||
// 临时请假无需审核 | |||||
leave.setAuditId(0L); | |||||
leave.setStatus(LeaveStatusEnum.PASSED.getCode()); | |||||
// 临时请假需通知专家管理进行专家补抽 | |||||
} | |||||
if (type != LeaveTypeEnum.TEMPORARY) { | |||||
ExpertMetaApply apply = getExpertMetaApply(po, leaveUserId, applyUserId, now); | |||||
leave.setAuditId(apply.getId()); | |||||
if (!leaveForSelf) { | |||||
leave.setStatus(LeaveStatusEnum.PASSED.getCode()); | |||||
} | |||||
} | |||||
leave.setStartTime(po.getStartTime()); | |||||
leave.setEndTime(po.getEndTime()); | |||||
leave.setCancelTime(po.getEndTime()); | |||||
leaveService.save(leave); | |||||
List<ExpertLeaveDetail> detailList = CollUtils.convert(leaveDetailTimes, w -> new ExpertLeaveDetail(leave.getId(), w.getKey(), w.getValue())); | |||||
leaveDetailService.saveBatch(detailList); | |||||
return IdVo.of(leave.getId()); | |||||
} | |||||
private ExpertMetaApply getExpertMetaApply(LeaveCreateReq po, Long leaveUserId, Long applyUserId, LocalDateTime now) { | |||||
// 非临时请假需要审批 | |||||
ExpertMetaApply apply = new ExpertMetaApply(); | |||||
apply.setCreateOn(now); | |||||
apply.setUpdateOn(now); | |||||
apply.setApplyType(ExpertApplyTypeEnum.LONG_TERM_LEAVE.getKey()); | |||||
if (applyUserId.equals(leaveUserId)) { | |||||
apply.setApplyStatus(ExpertApplyStatusEnum.PENDING_REVIEW.getKey()); | |||||
} else { | |||||
// 专家管理员发起请假自动通过 | |||||
apply.setReviewTime(now); | |||||
apply.setApprover(LoginUserUtil.getUsername()); | |||||
apply.setApproverUserId(applyUserId); | |||||
apply.setApplyStatus(ExpertApplyStatusEnum.PASSED.getKey()); | |||||
} | |||||
apply.setUserId(leaveUserId); | |||||
apply.setApplicantId(applyUserId); | |||||
ExpertUserFullInfo userInfo = userFullInfoService.getByUserId(apply.getUserId()); | |||||
apply.setRegionCode(userInfo.getRegionCode()); | |||||
apply.setRegionLevel(userInfo.getRegionLevel()); | |||||
ModifyApplyExtraInfoDTO extraInfo = new ModifyApplyExtraInfoDTO(); | |||||
extraInfo.setFactSheet(po.getPostscript()); | |||||
if (po.getAttachments() != null) { | |||||
List<FileBasicInfo> infoList = CollUtils.convert(po.getAttachments(), w -> { | |||||
FileBasicInfo info = new FileBasicInfo(); | |||||
info.setFileId(w); | |||||
return info; | |||||
}); | |||||
extraInfo.setEvidenceList(infoList); | |||||
} | |||||
apply.setExtraMaterial(JSONUtil.toJsonStr(extraInfo)); | |||||
metaApplyService.save(apply); | |||||
return apply; | |||||
} | |||||
public PageVo<LeaveListItemVO> leaveListByCreator(LeaveListByCreatorReq po) { | |||||
if (po.getExpertId() == null) { | |||||
po.setExpertId(LoginUserUtil.getUserId()); | |||||
} | |||||
LambdaQueryWrapper<ExpertLeave> query = Wrappers.lambdaQuery(ExpertLeave.class) | |||||
.orderByDesc(ExpertLeave::getCreateOn); | |||||
if (po.getLeaveType() != null && po.getLeaveType().equals(LeaveTypeEnum.TEMPORARY.getCode())) { | |||||
// 临时请假 | |||||
if (po.getAuditStatus() != null && !po.getAuditStatus().equals(ExpertApplyStatusEnum.PASSED.getKey())) { | |||||
return PageVo.empty(); | |||||
} | |||||
query.eq(ExpertLeave::getAuditId, 0L); | |||||
query.eq(ExpertLeave::getLeaveUserId, po.getExpertId()); | |||||
} else { | |||||
if (StrUtils.isNotBlank(po.getAuditStatus())) { | |||||
LambdaQueryWrapper<ExpertMetaApply> queryApply = Wrappers.lambdaQuery(ExpertMetaApply.class) | |||||
.select(ExpertMetaApply::getId) | |||||
.eq(ExpertMetaApply::getUserId, po.getExpertId()) | |||||
.eq(ExpertMetaApply::getApplyStatus, po.getAuditStatus()); | |||||
List<ExpertMetaApply> applyList = metaApplyService.list(queryApply); | |||||
List<Long> auditIds = CollUtils.fieldList(applyList, ExpertMetaApply::getId); | |||||
if (po.getAuditStatus().equals(ExpertApplyStatusEnum.PASSED.getKey())) { | |||||
query.eq(ExpertLeave::getLeaveUserId, po.getExpertId()); | |||||
auditIds.add(0L); | |||||
} | |||||
if (auditIds.isEmpty()) { | |||||
return PageVo.empty(); | |||||
} | |||||
query.in(ExpertLeave::getAuditId, auditIds); | |||||
} else { | |||||
query.eq(ExpertLeave::getLeaveUserId, po.getExpertId()); | |||||
} | |||||
query.eq(po.getLeaveType() != null, ExpertLeave::getType, po.getLeaveType()); | |||||
} | |||||
if (po.getStartTime() != null && po.getEndTime() != null) { | |||||
query.and(wrapper -> wrapper.between(ExpertLeave::getStartTime, po.getStartTime(), po.getEndTime()) | |||||
.or(or -> or.between(ExpertLeave::getEndTime, po.getStartTime(), po.getEndTime()))); | |||||
} | |||||
Page<ExpertLeave> page = leaveService.page(po.page(), query); | |||||
if (page.getTotal() == 0) { | |||||
return PageVo.empty(); | |||||
} | |||||
List<Long> meetingIds = new ArrayList<>(), auditIds = new ArrayList<>(), fileIds = new ArrayList<>(); | |||||
page.getRecords().forEach(w -> { | |||||
if (w.getMeetingId() != 0) { | |||||
meetingIds.add(w.getMeetingId()); | |||||
} | |||||
if (w.getAuditId() != 0) { | |||||
auditIds.add(w.getAuditId()); | |||||
} | |||||
if (!w.getAttachment().isEmpty()) { | |||||
List<Long> tempFileIds = BizUtils.splitToLong(w.getAttachment()); | |||||
fileIds.addAll(tempFileIds); | |||||
} | |||||
}); | |||||
Map<Long, Meeting> meetingMap = new HashMap<>(16); | |||||
if (!meetingIds.isEmpty()) { | |||||
meetingMap.putAll(CollUtils.listToMap(meetingService.listByIds(meetingIds), Meeting::getId)); | |||||
} | |||||
Map<Long, ExpertMetaApply> applyMap = new HashMap<>(16); | |||||
if (!auditIds.isEmpty()) { | |||||
applyMap.putAll(CollUtils.listToMap(metaApplyService.listByIds(auditIds), ExpertMetaApply::getId)); | |||||
} | |||||
Map<Long, AttachFileVo> fileMap = new HashMap<>(fileIds.size()); | |||||
if (!fileIds.isEmpty()) { | |||||
fileMap.putAll(CollUtils.listToMap(fileService.getByIds(fileIds), AttachFileVo::getFileId)); | |||||
} | |||||
List<LeaveListItemVO> dataList = CollUtils.convert(page.getRecords(), w -> { | |||||
LeaveListItemVO item = new LeaveListItemVO(); | |||||
item.setLeaveType(w.getType()); | |||||
item.setPostscript(w.getRemark()); | |||||
item.setAuditId(w.getAuditId()); | |||||
item.setLeaveId(w.getId()); | |||||
item.setStartTime(w.getStartTime()); | |||||
item.setEndTime(w.getEndTime()); | |||||
ExpertMetaApply apply = applyMap.get(w.getAuditId()); | |||||
item.setAuditOption(apply != null ? apply.getAuditOpinion() : StrUtil.EMPTY); | |||||
item.setStatus(w.getStatus()); | |||||
item.setAuditStatus(apply != null ? apply.getApplyStatus() : ExpertApplyStatusEnum.PASSED.getKey()); | |||||
item.setActualEndTime(w.getCancelTime()); | |||||
Meeting meeting = meetingMap.get(w.getMeetingId()); | |||||
item.setMeetingName(meeting != null ? meeting.getName() : StrUtil.EMPTY); | |||||
item.setAttachments(new ArrayList<>()); | |||||
if (!w.getAttachment().isEmpty()) { | |||||
Arrays.stream(w.getAttachment().split(",")).forEach(fileId -> item.getAttachments().add(fileMap.get(Long.parseLong(fileId)))); | |||||
} | |||||
return item; | |||||
}); | |||||
return PageVo.of(dataList, page.getTotal()); | |||||
} | |||||
public void endLeave(Long leaveId) { | |||||
ExpertLeave leave = leaveService.getById(leaveId); | |||||
if (LeaveTypeEnum.TEMPORARY.eq(leave.getType())) { | |||||
throw BizException.wrap("临时请假不支持销假"); | |||||
} | |||||
LeaveStatusEnum status = LeaveStatusEnum.getByCode(leave.getStatus()); | |||||
if (!status.equals(LeaveStatusEnum.PASSED) || LocalDateTime.now().isAfter(leave.getEndTime())) { | |||||
throw BizException.wrap("该请假不支持销假"); | |||||
} | |||||
LocalDateTime actualEndTime = LocalDateTime.now(); | |||||
LambdaUpdateWrapper<ExpertLeave> updater = Wrappers.lambdaUpdate(ExpertLeave.class) | |||||
.set(ExpertLeave::getCancelTime, actualEndTime) | |||||
.set(ExpertLeave::getUpdateOn, actualEndTime) | |||||
.set(ExpertLeave::getUpdateBy, LoginUserUtil.getUserId()) | |||||
.set(ExpertLeave::getStatus, LeaveStatusEnum.PASSED_END.getCode()) | |||||
.eq(ExpertLeave::getId, leave.getId()); | |||||
leaveService.update(updater); | |||||
LambdaQueryWrapper<ExpertLeaveDetail> deleter = Wrappers.lambdaQuery(ExpertLeaveDetail.class) | |||||
.ge(ExpertLeaveDetail::getStartTime, actualEndTime.toLocalDate()) | |||||
.eq(ExpertLeaveDetail::getExpertLeaveId, leaveId); | |||||
leaveDetailService.remove(deleter); | |||||
} | |||||
public void cancel(Long leaveId) { | |||||
ExpertLeave leave = leaveService.getById(leaveId); | |||||
if (LeaveTypeEnum.TEMPORARY.eq(leave.getType())) { | |||||
throw BizException.wrap("临时请假不支持撤销"); | |||||
} | |||||
if (LeaveStatusEnum.APPLYING.eq(leave.getStatus())) { | |||||
throw BizException.wrap("该请假不支持撤销"); | |||||
} | |||||
LocalDateTime actualEndTime = LocalDateTime.now(); | |||||
// 修改请假状态 | |||||
LambdaUpdateWrapper<ExpertLeave> updater = Wrappers.lambdaUpdate(ExpertLeave.class) | |||||
.set(ExpertLeave::getUpdateOn, actualEndTime) | |||||
.set(ExpertLeave::getUpdateBy, LoginUserUtil.getUserId()) | |||||
.set(ExpertLeave::getStatus, LeaveStatusEnum.CANCELED.getCode()) | |||||
.eq(ExpertLeave::getId, leave.getId()); | |||||
leaveService.update(updater); | |||||
// 删除请假详情 | |||||
LambdaQueryWrapper<ExpertLeaveDetail> deleter = Wrappers.lambdaQuery(ExpertLeaveDetail.class) | |||||
.eq(ExpertLeaveDetail::getExpertLeaveId, leaveId); | |||||
leaveDetailService.remove(deleter); | |||||
// 修改审核表 | |||||
LambdaUpdateWrapper<ExpertMetaApply> updaterApply = Wrappers.lambdaUpdate(ExpertMetaApply.class) | |||||
.set(ExpertMetaApply::getApplyStatus, ExpertApplyStatusEnum.REVOKED.getKey()) | |||||
.set(ExpertMetaApply::getUpdateOn, LocalDateTime.now()) | |||||
.set(ExpertMetaApply::getAuditOpinion, "发起人撤销") | |||||
.eq(ExpertMetaApply::getId, leave.getAuditId()); | |||||
metaApplyService.update(updaterApply); | |||||
} | |||||
public LeaveDetailVO leaveDetail(Long leaveId, Long auditId) { | |||||
ExpertLeave leave; | |||||
if (leaveId != null) { | |||||
leave = leaveService.getById(leaveId); | |||||
} else { | |||||
leave = leaveService.getByAuditId(auditId); | |||||
} | |||||
LeaveDetailVO detail = LeaveDetailVO.builder() | |||||
.createOn(leave.getCreateOn()) | |||||
.createBy(leave.getCreator()) | |||||
.postscript(leave.getRemark()) | |||||
.id(leave.getId()) | |||||
.status(leave.getStatus()) | |||||
.startTime(leave.getStartTime()) | |||||
.endTime(leave.getEndTime()) | |||||
.attachments(new ArrayList<>()) | |||||
.type(leave.getType()) | |||||
.actualEndTime(leave.getCancelTime()) | |||||
.auditId(leave.getAuditId()) | |||||
.build(); | |||||
if (LeaveTypeEnum.FIXED_TERM.eq(leave.getType())) { | |||||
detail.setFixedType(BizUtils.splitToNum(leave.getFixedType(), Integer.class)); | |||||
} | |||||
if (StrUtils.isNotBlank(leave.getAttachment())) { | |||||
List<Long> fileIds = BizUtils.splitToLong(leave.getAttachment()); | |||||
List<AttachFileVo> files = fileService.getByIds(fileIds); | |||||
detail.getAttachments().addAll(files); | |||||
} | |||||
return detail; | |||||
} | |||||
public List<Long> listLeaveUserIdByTime(LocalDateTime start, LocalDateTime end) { | |||||
LambdaQueryWrapper<ExpertLeaveDetail> leaveDetailQuery = Wrappers.lambdaQuery(ExpertLeaveDetail.class) | |||||
.select(ExpertLeaveDetail::getExpertLeaveId) | |||||
.and(wrapper -> wrapper.between(ExpertLeaveDetail::getStartTime, start, end) | |||||
.or(wrapper1 -> wrapper1.between(ExpertLeaveDetail::getEndTime, start, end))); | |||||
List<ExpertLeaveDetail> detailList = leaveDetailService.list(leaveDetailQuery); | |||||
if (detailList.isEmpty()) { | |||||
return new ArrayList<>(); | |||||
} | |||||
List<Long> leaveIdList = CollUtils.fieldList(detailList, ExpertLeaveDetail::getExpertLeaveId); | |||||
LambdaQueryWrapper<ExpertLeave> leaveQuery = Wrappers.lambdaQuery(ExpertLeave.class) | |||||
.select(ExpertLeave::getCreateBy) | |||||
.in(ExpertLeave::getId, leaveIdList) | |||||
.in(ExpertLeave::getStatus, | |||||
LeaveStatusEnum.APPLYING.getCode(), | |||||
LeaveStatusEnum.PASSED.getCode(), | |||||
LeaveStatusEnum.PASSED_END.getCode()); | |||||
List<ExpertLeave> leaveList = leaveService.list(leaveQuery); | |||||
return CollUtils.fieldList(leaveList, ExpertLeave::getCreateBy); | |||||
} | |||||
@Transactional(rollbackFor = Exception.class) | |||||
public void leaveAuditCallback(boolean status, Long applyId) { | |||||
LambdaUpdateWrapper<ExpertLeave> update = Wrappers.lambdaUpdate(ExpertLeave.class) | |||||
.set(ExpertLeave::getUpdateBy, LoginUserUtil.getUserId()) | |||||
.set(ExpertLeave::getUpdateOn, LocalDateTime.now()) | |||||
.eq(ExpertLeave::getAuditId, applyId); | |||||
ExpertLeave leave = leaveService.getByAuditId(applyId); | |||||
ExpertUserFullInfo leaveUser = userFullInfoService.getByUserId(leave.getLeaveUserId()); | |||||
if (status) { | |||||
update.set(ExpertLeave::getStatus, LeaveStatusEnum.PASSED.getCode()); | |||||
} else { | |||||
ExpertMetaApply metaApply = metaApplyService.getById(applyId); | |||||
update.set(ExpertLeave::getStatus, LeaveStatusEnum.UN_PASSED.getCode()); | |||||
LambdaQueryWrapper<ExpertLeaveDetail> delete = Wrappers.lambdaQuery(ExpertLeaveDetail.class) | |||||
.eq(ExpertLeaveDetail::getExpertLeaveId, leave.getId()); | |||||
leaveDetailService.remove(delete); | |||||
} | |||||
leaveService.update(update); | |||||
} | |||||
public Map<LocalDate, Set<Long>> listLeaveIdByDate(LocalDate startDate, LocalDate endDate, Long expertId) { | |||||
List<ExpertLeaveDetail> leaveDetails = leaveDetailService.listByDateAndLeaveUserId(startDate, endDate, expertId); | |||||
return leaveDetails.stream().collect(Collectors.groupingBy(w -> w.getStartTime().toLocalDate(), | |||||
Collectors.mapping(ExpertLeaveDetail::getExpertLeaveId, Collectors.toSet()))); | |||||
} | |||||
public PageVo<LeaveListItemVO> listLeaveApplyingAndCouldBeStop(PagePo po) { | |||||
LambdaQueryWrapper<ExpertLeave> query = Wrappers.lambdaQuery(ExpertLeave.class) | |||||
.eq(ExpertLeave::getLeaveUserId, LoginUserUtil.getUserId()) | |||||
.and(wrapper -> wrapper.eq(ExpertLeave::getStatus, LeaveStatusEnum.APPLYING.getCode()) | |||||
.or(wrapper1 -> wrapper1.eq(ExpertLeave::getStatus, LeaveStatusEnum.PASSED.getCode()) | |||||
.gt(ExpertLeave::getCancelTime, LocalDateTime.now()))); | |||||
Page<ExpertLeave> page = leaveService.page(new Page<>(po.getPageNumber(), po.getPageSize()), query); | |||||
if (page.getTotal() == 0) { | |||||
return PageVo.empty(); | |||||
} | |||||
List<LeaveListItemVO> result = CollUtils.convert(page.getRecords(), w -> { | |||||
LeaveListItemVO item = new LeaveListItemVO(); | |||||
item.setStartTime(w.getStartTime()); | |||||
item.setEndTime(w.getEndTime()); | |||||
item.setStatus(w.getStatus()); | |||||
item.setLeaveType(w.getType()); | |||||
item.setLeaveId(w.getId()); | |||||
return item; | |||||
}); | |||||
return PageVo.of(result, page.getTotal()); | |||||
} | |||||
} |
@@ -0,0 +1,46 @@ | |||||
package com.ningdatech.pmapi.leave.mapper; | |||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |||||
import com.ningdatech.pmapi.common.model.entity.KeyValDTO; | |||||
import com.ningdatech.pmapi.leave.entity.domain.ExpertLeaveDetail; | |||||
import org.apache.ibatis.annotations.Param; | |||||
import java.time.LocalDate; | |||||
import java.time.LocalDateTime; | |||||
import java.util.List; | |||||
/** | |||||
* <p> | |||||
* 专家请假详情表(维度:天) Mapper 接口 | |||||
* </p> | |||||
* | |||||
* @author WendyYang | |||||
* @since 2022-08-11 | |||||
*/ | |||||
public interface ExpertLeaveDetailMapper extends BaseMapper<ExpertLeaveDetail> { | |||||
/** | |||||
* 查询时间段之内的请假信息(请假状态:已通过、销假) | |||||
* | |||||
* @param leaveUserId 请假人ID | |||||
* @param startDate 开始时间 | |||||
* @param endDate 结束时间 | |||||
* @return 请假详情 | |||||
* @author WendyYang | |||||
**/ | |||||
List<ExpertLeaveDetail> selectLeaveIdByDateAndExpertId(@Param("leaveUserId") Long leaveUserId, | |||||
@Param("startDate") LocalDate startDate, | |||||
@Param("endDate") LocalDate endDate); | |||||
/** | |||||
* 查询用户在指定时间之内是否有请假 | |||||
* | |||||
* @param leaveUserId 用户ID | |||||
* @param times 时间集合 | |||||
* @return 请假天数 | |||||
* @author WendyYang | |||||
**/ | |||||
Integer existsLeaveByLeaveUserIdAndTime(@Param("leaveUserId") Long leaveUserId, | |||||
@Param("times") List<KeyValDTO<LocalDateTime, LocalDateTime>> times); | |||||
} |
@@ -0,0 +1,29 @@ | |||||
<?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="com.ningdatech.pmapi.leave.mapper.ExpertLeaveDetailMapper"> | |||||
<select id="selectLeaveIdByDateAndExpertId" | |||||
resultType="com.ningdatech.pmapi.leave.entity.domain.ExpertLeaveDetail"> | |||||
select eld.expert_leave_id, eld.start_time, eld.end_time | |||||
from expert_leave el | |||||
inner join expert_leave_detail eld on el.id = eld.expert_leave_id | |||||
where leave_user_id = #{leaveUserId} | |||||
and status in (2, 3) | |||||
and ((eld.start_time >= #{startDate} and eld.start_time < #{endDate}) | |||||
or (eld.end_time >= #{startDate} and eld.end_time < #{endDate})) | |||||
</select> | |||||
<select id="existsLeaveByLeaveUserIdAndTime" resultType="integer"> | |||||
select 1 | |||||
from expert_leave el | |||||
inner join expert_leave_detail eld on el.id = eld.expert_leave_id | |||||
where leave_user_id = #{leaveUserId} | |||||
and status not in (4, 5) | |||||
<foreach collection="times" item="item" open="and (" close=")" separator="or"> | |||||
((eld.start_time >= #{item.key} and eld.start_time < #{item.value}) | |||||
or (eld.end_time >= #{item.key} and eld.end_time < #{item.value})) | |||||
</foreach> | |||||
limit 1 | |||||
</select> | |||||
</mapper> |
@@ -0,0 +1,16 @@ | |||||
package com.ningdatech.pmapi.leave.mapper; | |||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |||||
import com.ningdatech.pmapi.leave.entity.domain.ExpertLeave; | |||||
/** | |||||
* <p> | |||||
* Mapper 接口 | |||||
* </p> | |||||
* | |||||
* @author WendyYang | |||||
* @since 2022-08-11 | |||||
*/ | |||||
public interface ExpertLeaveMapper extends BaseMapper<ExpertLeave> { | |||||
} |
@@ -0,0 +1,5 @@ | |||||
<?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="com.ningdatech.pmapi.leave.mapper.ExpertLeaveMapper"> | |||||
</mapper> |
@@ -0,0 +1,25 @@ | |||||
package com.ningdatech.pmapi.leave.service; | |||||
import com.baomidou.mybatisplus.extension.service.IService; | |||||
import com.ningdatech.pmapi.common.model.entity.KeyValDTO; | |||||
import com.ningdatech.pmapi.leave.entity.domain.ExpertLeaveDetail; | |||||
import java.time.LocalDate; | |||||
import java.time.LocalDateTime; | |||||
import java.util.List; | |||||
/** | |||||
* <p> | |||||
* 专家请假详情表(维度:天) 服务类 | |||||
* </p> | |||||
* | |||||
* @author WendyYang | |||||
* @since 2022-08-11 | |||||
*/ | |||||
public interface IExpertLeaveDetailService extends IService<ExpertLeaveDetail> { | |||||
List<ExpertLeaveDetail> listByDateAndLeaveUserId(LocalDate startDate, LocalDate endDate, Long leaveUserId); | |||||
boolean existsLeaveByUserIdAndTime(Long userId, List<KeyValDTO<LocalDateTime, LocalDateTime>> times); | |||||
} |
@@ -0,0 +1,26 @@ | |||||
package com.ningdatech.pmapi.leave.service; | |||||
import com.baomidou.mybatisplus.extension.service.IService; | |||||
import com.ningdatech.pmapi.leave.entity.domain.ExpertLeave; | |||||
/** | |||||
* <p> | |||||
* 服务类 | |||||
* </p> | |||||
* | |||||
* @author WendyYang | |||||
* @since 2022-08-11 | |||||
*/ | |||||
public interface IExpertLeaveService extends IService<ExpertLeave> { | |||||
ExpertLeave getByAuditId(Long auditId); | |||||
/** | |||||
* 是否存在待审核请假记录 | |||||
* | |||||
* @param expertId 专家ID | |||||
* @return boolean | |||||
*/ | |||||
boolean existsToBeReviewed(Long expertId); | |||||
} |
@@ -0,0 +1,35 @@ | |||||
package com.ningdatech.pmapi.leave.service.impl; | |||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; | |||||
import com.ningdatech.pmapi.common.model.entity.KeyValDTO; | |||||
import com.ningdatech.pmapi.leave.entity.domain.ExpertLeaveDetail; | |||||
import com.ningdatech.pmapi.leave.mapper.ExpertLeaveDetailMapper; | |||||
import com.ningdatech.pmapi.leave.service.IExpertLeaveDetailService; | |||||
import org.springframework.stereotype.Service; | |||||
import java.time.LocalDate; | |||||
import java.time.LocalDateTime; | |||||
import java.util.List; | |||||
/** | |||||
* <p> | |||||
* 专家请假详情表(维度:天) 服务实现类 | |||||
* </p> | |||||
* | |||||
* @author WendyYang | |||||
* @since 2022-08-11 | |||||
*/ | |||||
@Service | |||||
public class ExpertLeaveDetailServiceImpl extends ServiceImpl<ExpertLeaveDetailMapper, ExpertLeaveDetail> implements IExpertLeaveDetailService { | |||||
@Override | |||||
public List<ExpertLeaveDetail> listByDateAndLeaveUserId(LocalDate startDate, LocalDate endDate, Long leaveUserId) { | |||||
return baseMapper.selectLeaveIdByDateAndExpertId(leaveUserId, startDate, endDate.plusDays(1)); | |||||
} | |||||
@Override | |||||
public boolean existsLeaveByUserIdAndTime(Long userId, List<KeyValDTO<LocalDateTime, LocalDateTime>> times) { | |||||
return baseMapper.existsLeaveByLeaveUserIdAndTime(userId, times) != null; | |||||
} | |||||
} |
@@ -0,0 +1,41 @@ | |||||
package com.ningdatech.pmapi.leave.service.impl; | |||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers; | |||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; | |||||
import com.ningdatech.pmapi.leave.entity.domain.ExpertLeave; | |||||
import com.ningdatech.pmapi.leave.entity.enumeration.LeaveStatusEnum; | |||||
import com.ningdatech.pmapi.leave.mapper.ExpertLeaveMapper; | |||||
import com.ningdatech.pmapi.leave.service.IExpertLeaveService; | |||||
import org.springframework.stereotype.Service; | |||||
/** | |||||
* <p> | |||||
* 服务实现类 | |||||
* </p> | |||||
* | |||||
* @author WendyYang | |||||
* @since 2022-08-11 | |||||
*/ | |||||
@Service | |||||
public class ExpertLeaveServiceImpl extends ServiceImpl<ExpertLeaveMapper, ExpertLeave> implements IExpertLeaveService { | |||||
@Override | |||||
public ExpertLeave getByAuditId(Long auditId) { | |||||
if (auditId == 0) { | |||||
return null; | |||||
} | |||||
LambdaQueryWrapper<ExpertLeave> query = Wrappers.lambdaQuery(ExpertLeave.class) | |||||
.eq(ExpertLeave::getAuditId, auditId); | |||||
return getOne(query); | |||||
} | |||||
@Override | |||||
public boolean existsToBeReviewed(Long expertId) { | |||||
LambdaQueryWrapper<ExpertLeave> query = Wrappers.lambdaQuery(ExpertLeave.class) | |||||
.eq(ExpertLeave::getLeaveUserId, expertId) | |||||
.eq(ExpertLeave::getStatus, LeaveStatusEnum.APPLYING.getCode()); | |||||
return baseMapper.exists(query); | |||||
} | |||||
} |
@@ -21,6 +21,7 @@ public enum ExpertAttendStatusEnum { | |||||
UNANSWERED("未应答", 1), | UNANSWERED("未应答", 1), | ||||
AGREED("同意参加", 3), | AGREED("同意参加", 3), | ||||
REFUSED("拒绝参加", 4), | REFUSED("拒绝参加", 4), | ||||
ON_LEAVE("已请假", 5), | |||||
RELEASED("已释放", 7); | RELEASED("已释放", 7); | ||||
private final String value; | private final String value; | ||||
@@ -33,8 +33,7 @@ public class MeetingListReq extends PagePo { | |||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") | ||||
private LocalDateTime endTime; | private LocalDateTime endTime; | ||||
@ApiModelProperty("事务状态:1 未完成、2 已完成、3 已取消\n" + | |||||
"专家参与状态:1 待参加、2 已参加、3 已请假") | |||||
@ApiModelProperty("事务状态") | |||||
private Integer status; | private Integer status; | ||||
@ApiModelProperty("会议类型") | @ApiModelProperty("会议类型") | ||||
@@ -49,9 +49,6 @@ public class MeetingByManagerVO { | |||||
@ApiModelProperty("名单确认状态") | @ApiModelProperty("名单确认状态") | ||||
private Boolean confirmedRoster; | private Boolean confirmedRoster; | ||||
@ApiModelProperty("会议参加状态:1 待参加、2 已参加、3 已请假") | |||||
private Integer attendStatus; | |||||
@ApiModelProperty("会议名称") | @ApiModelProperty("会议名称") | ||||
private String meetingName; | private String meetingName; | ||||
@@ -61,4 +58,7 @@ public class MeetingByManagerVO { | |||||
@ApiModelProperty("会议类型名称") | @ApiModelProperty("会议类型名称") | ||||
private String meetingTypeName; | private String meetingTypeName; | ||||
@ApiModelProperty("专家状态") | |||||
private Integer expertStatus; | |||||
} | } |
@@ -284,15 +284,16 @@ public class MeetingManage { | |||||
**/ | **/ | ||||
public PageVo<MeetingByManagerVO> meetingListByExpert(MeetingListReq req) { | public PageVo<MeetingByManagerVO> meetingListByExpert(MeetingListReq req) { | ||||
Long expertId = req.getExpertId() != null ? req.getExpertId() : LoginUserUtil.getUserId(); | Long expertId = req.getExpertId() != null ? req.getExpertId() : LoginUserUtil.getUserId(); | ||||
List<MeetingAndAttendStatusDTO> expertDtoList = meetingExpertService.listByExpertIdAndStatus(expertId, req.getStatus(), null); | |||||
if (expertDtoList.isEmpty()) { | |||||
List<MeetingAndAttendStatusDTO> meetings = meetingExpertService.listByExpertIdAndStatus(expertId, null, null); | |||||
if (meetings.isEmpty()) { | |||||
return PageVo.empty(); | return PageVo.empty(); | ||||
} | } | ||||
Map<Long, MeetingAndAttendStatusDTO> mapByMeetingId = new HashMap<>(16); | Map<Long, MeetingAndAttendStatusDTO> mapByMeetingId = new HashMap<>(16); | ||||
expertDtoList.forEach(w -> mapByMeetingId.put(w.getMeetingId(), w)); | |||||
meetings.forEach(w -> mapByMeetingId.put(w.getMeetingId(), w)); | |||||
LambdaQueryWrapper<Meeting> query = new LambdaQueryWrapper<Meeting>() | LambdaQueryWrapper<Meeting> query = new LambdaQueryWrapper<Meeting>() | ||||
.orderByDesc(Meeting::getCreateOn) | .orderByDesc(Meeting::getCreateOn) | ||||
.in(Meeting::getId, mapByMeetingId.keySet()) | .in(Meeting::getId, mapByMeetingId.keySet()) | ||||
.eq(Meeting::getConfirmedRoster, Boolean.TRUE) | |||||
.ne(Meeting::getStatus, MeetingStatusEnum.CANCELED.getCode()); | .ne(Meeting::getStatus, MeetingStatusEnum.CANCELED.getCode()); | ||||
if (req.getExpertId() == null) { | if (req.getExpertId() == null) { | ||||
meetingManageHelper.buildMeetingQuery(query, req); | meetingManageHelper.buildMeetingQuery(query, req); | ||||
@@ -305,7 +306,7 @@ public class MeetingManage { | |||||
page.getRecords().forEach(meeting -> { | page.getRecords().forEach(meeting -> { | ||||
MeetingByManagerVO item = meetingManageHelper.buildByMeeting(meeting); | MeetingByManagerVO item = meetingManageHelper.buildByMeeting(meeting); | ||||
MeetingAndAttendStatusDTO info = mapByMeetingId.get(meeting.getId()); | MeetingAndAttendStatusDTO info = mapByMeetingId.get(meeting.getId()); | ||||
item.setAttendStatus(meetingManageHelper.getExpertAttendStatus(info)); | |||||
item.setExpertStatus(info.getStatus()); | |||||
result.getRecords().add(item); | result.getRecords().add(item); | ||||
}); | }); | ||||
return result; | return result; | ||||
@@ -1,5 +1,6 @@ | |||||
package com.ningdatech.pmapi.meeting.task; | package com.ningdatech.pmapi.meeting.task; | ||||
import cn.hutool.core.util.ArrayUtil; | |||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | ||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | ||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers; | import com.baomidou.mybatisplus.core.toolkit.Wrappers; | ||||
@@ -21,7 +22,6 @@ import com.ningdatech.pmapi.meeting.service.IExpertInviteAvoidRuleService; | |||||
import com.ningdatech.pmapi.meeting.service.IExpertInviteRuleService; | import com.ningdatech.pmapi.meeting.service.IExpertInviteRuleService; | ||||
import com.ningdatech.pmapi.meeting.service.IMeetingExpertService; | import com.ningdatech.pmapi.meeting.service.IMeetingExpertService; | ||||
import com.ningdatech.pmapi.meeting.service.IMeetingService; | import com.ningdatech.pmapi.meeting.service.IMeetingService; | ||||
import com.ningdatech.pmapi.user.util.LoginUserUtil; | |||||
import lombok.AllArgsConstructor; | import lombok.AllArgsConstructor; | ||||
import lombok.Data; | import lombok.Data; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
@@ -146,18 +146,23 @@ public class ExpertInviteTask { | |||||
/** | /** | ||||
* 唤醒某个会议的抽取任务 | * 唤醒某个会议的抽取任务 | ||||
* | * | ||||
* @param meetingId 会议ID | |||||
* @param meetingId 会议ID | |||||
* @param invitedRefused 是否可邀请已拒绝的专家 | |||||
* @author WendyYang | * @author WendyYang | ||||
**/ | **/ | ||||
public void notifyInviteTask(Long meetingId) { | |||||
public void notifyInviteTask(Long meetingId, boolean... invitedRefused) { | |||||
boolean tmpInvitedRefused = true; | |||||
if (ArrayUtil.isNotEmpty(invitedRefused)) { | |||||
tmpInvitedRefused = invitedRefused[0]; | |||||
} | |||||
if (!INVITE_MAP.containsKey(meetingId)) { | if (!INVITE_MAP.containsKey(meetingId)) { | ||||
addInviteExpertTask(meetingId, false, properties.getInviteDelay(), true); | |||||
addInviteExpertTask(meetingId, false, properties.getInviteDelay(), tmpInvitedRefused); | |||||
log.info("重置会议的随机抽取状态:{}", meetingId); | log.info("重置会议的随机抽取状态:{}", meetingId); | ||||
LambdaUpdateWrapper<Meeting> update = Wrappers.lambdaUpdate(Meeting.class); | LambdaUpdateWrapper<Meeting> update = Wrappers.lambdaUpdate(Meeting.class); | ||||
update.set(Meeting::getInviteStatus, false); | update.set(Meeting::getInviteStatus, false); | ||||
update.eq(Meeting::getId, meetingId); | update.eq(Meeting::getId, meetingId); | ||||
meetingService.update(update); | meetingService.update(update); | ||||
InviteCacheDTO cacheVal = InviteCacheDTO.of(meetingId, true); | |||||
InviteCacheDTO cacheVal = InviteCacheDTO.of(meetingId, tmpInvitedRefused); | |||||
cachePlusOps.hSet(getCacheKey(meetingId), cacheVal); | cachePlusOps.hSet(getCacheKey(meetingId), cacheVal); | ||||
} | } | ||||
} | } | ||||
@@ -252,11 +257,7 @@ public class ExpertInviteTask { | |||||
}); | }); | ||||
if (notIgnoreCnt.get() == 0 || notIgnoreCnt.get() == notSupportCnt.get()) { | if (notIgnoreCnt.get() == 0 || notIgnoreCnt.get() == notSupportCnt.get()) { | ||||
if (notSupportCnt.get() > 0) { | if (notSupportCnt.get() > 0) { | ||||
// TODO | |||||
/*UserInfo inviterBasic = userInfoService.getById(meeting.getCreateBy()); | |||||
UserInfo inviter = userInfoService.getById(inviterBasic.getId()); | |||||
SendSmsContext context = YxtSmsContextBuilder.smsByRandomInviteStop(inviter.getNickname(), meeting.getName(), inviterBasic.getPhoneNo()); | |||||
yxtCallOrSmsHelper.sendSms(context);*/ | |||||
// TODO 发送邀请停止短信 | |||||
} | } | ||||
log.info("停止会议随机邀请:{} 未完成抽取规则数量 {} 无可抽取专家规则数量 {}", meetingId, notIgnoreCnt, notSupportCnt); | log.info("停止会议随机邀请:{} 未完成抽取规则数量 {} 无可抽取专家规则数量 {}", meetingId, notIgnoreCnt, notSupportCnt); | ||||
currProxy().cancelByMeetingId(meetingId); | currProxy().cancelByMeetingId(meetingId); | ||||