@@ -46,6 +46,13 @@ public class MeetingController { | |||
meetingManage.continueInvite(req.getMeetingId()); | |||
} | |||
@PostMapping("/convertToAppoint") | |||
@ApiOperation(value = "转为指定抽取") | |||
@WebLog(value = "转为指定抽取") | |||
public void convertToAppoint(@Valid @RequestBody MeetingIdReq req) { | |||
meetingManage.convertToAppoint(req.getMeetingId()); | |||
} | |||
@PostMapping("/expertInviteByCreate") | |||
@ApiOperation(value = "新建会议-专家抽取", hidden = true) | |||
@WebLog(value = "新建会议-专家抽取") | |||
@@ -1,8 +1,6 @@ | |||
package com.ningdatech.pmapi.meeting.entity.domain; | |||
import com.baomidou.mybatisplus.annotation.IdType; | |||
import com.baomidou.mybatisplus.annotation.TableId; | |||
import com.baomidou.mybatisplus.annotation.TableName; | |||
import com.baomidou.mybatisplus.annotation.*; | |||
import io.swagger.annotations.ApiModel; | |||
import io.swagger.annotations.ApiModelProperty; | |||
import lombok.Builder; | |||
@@ -58,15 +56,18 @@ public class MeetingExpert implements Serializable { | |||
@ApiModelProperty("邀请类型") | |||
private Integer inviteType; | |||
private String submitKey; | |||
@TableField(fill = FieldFill.INSERT) | |||
private Long createBy; | |||
@TableField(fill = FieldFill.INSERT) | |||
private LocalDateTime createOn; | |||
@TableField(fill = FieldFill.INSERT_UPDATE) | |||
private Long updateBy; | |||
@TableField(fill = FieldFill.INSERT_UPDATE) | |||
private LocalDateTime updateOn; | |||
private String submitKey; | |||
} |
@@ -11,14 +11,14 @@ import lombok.Data; | |||
* @since 19:21 2023/3/7 | |||
*/ | |||
@Data | |||
public final class MeetingInviteCacheDTO { | |||
public final class InviteCacheDTO { | |||
private Long meetingId; | |||
private Boolean invitedRefused; | |||
public static MeetingInviteCacheDTO of(Long meetingId, Boolean invitedRefused) { | |||
MeetingInviteCacheDTO bo = new MeetingInviteCacheDTO(); | |||
public static InviteCacheDTO of(Long meetingId, Boolean invitedRefused) { | |||
InviteCacheDTO bo = new InviteCacheDTO(); | |||
bo.setMeetingId(meetingId); | |||
bo.setInvitedRefused(invitedRefused); | |||
return bo; |
@@ -19,11 +19,8 @@ public enum ExpertAttendStatusEnum { | |||
NOTICING("通知中", 0), | |||
UNANSWERED("未应答", 1), | |||
REPLACED("已替换", 2), | |||
AGREED("同意参加", 3), | |||
REFUSED("拒绝参加", 4), | |||
REMOVED("已移除", 5), | |||
ON_LEAVE("已请假", 6), | |||
RELEASED("已释放", 7); | |||
private final String value; | |||
@@ -196,6 +196,7 @@ public class MeetingManageHelper { | |||
* | |||
* @param meetingId 会议ID | |||
* @param expertIds 专家ID | |||
* @return 符合邀请规则的专家 | |||
* @author WendyYang | |||
**/ | |||
public List<ExpertUserFullInfo> appointExpertCheck(Long meetingId, List<Long> expertIds) { | |||
@@ -226,8 +227,6 @@ public class MeetingManageHelper { | |||
switch (ExpertAttendStatusEnum.getByCode(w.getStatus())) { | |||
case REFUSED: | |||
throw BizException.wrap("专家%s已拒绝参加", expertName); | |||
case REMOVED: | |||
throw BizException.wrap("专家%s已被移除", expertName); | |||
case AGREED: | |||
throw BizException.wrap("专家%s已同意参加", expertName); | |||
case NOTICING: | |||
@@ -12,10 +12,9 @@ import com.ningdatech.pmapi.meeting.entity.domain.Meeting; | |||
import com.ningdatech.pmapi.meeting.entity.domain.MeetingExpert; | |||
import com.ningdatech.pmapi.meeting.entity.dto.CountConfirmByMeetingIdDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.MeetingAndAttendStatusDTO; | |||
import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertAttendStatusEnum; | |||
import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertInviteTypeEnum; | |||
import com.ningdatech.pmapi.meeting.entity.enumeration.MeetingStatusEnum; | |||
import com.ningdatech.pmapi.meeting.entity.enumeration.MeetingStatusByDashboard; | |||
import com.ningdatech.pmapi.meeting.entity.enumeration.MeetingStatusEnum; | |||
import com.ningdatech.pmapi.meeting.entity.req.MeetingCalenderReq; | |||
import com.ningdatech.pmapi.meeting.entity.req.MeetingListReq; | |||
import com.ningdatech.pmapi.meeting.entity.vo.*; | |||
@@ -186,8 +185,7 @@ public class DashboardManage { | |||
return PageVo.empty(); | |||
} | |||
List<Long> meetingIds = CollUtils.fieldList(meetings, Meeting::getId); | |||
Page<MeetingExpert> page = meetingExpertService.pageExpertByStatusAndMeetingIds(new Page<>(po.getPageNumber(), po.getPageSize()), | |||
meetingIds, ExpertAttendStatusEnum.ON_LEAVE); | |||
Page<MeetingExpert> page = meetingExpertService.pageExpertByStatusAndMeetingIds(po.page(), meetingIds, null); | |||
if (page.getTotal() == 0) { | |||
return PageVo.empty(); | |||
} | |||
@@ -224,9 +222,7 @@ public class DashboardManage { | |||
List<MeetingAndAttendStatusDTO> attendStatusList = meetingExpertService.listByExpertIdAndStatus(LoginUserUtil.getUserId(), null, null); | |||
MeetingCountByExpertVO result = MeetingCountByExpertVO.init(); | |||
attendStatusList.forEach(w -> { | |||
if (w.getStatus().equals(ExpertAttendStatusEnum.ON_LEAVE.getCode())) { | |||
result.incrLeaved(); | |||
} else if (w.getAttended() != null && w.getAttended()) { | |||
if (w.getAttended() != null && w.getAttended()) { | |||
result.incrAttended(); | |||
} else { | |||
result.incrToBeAttended(); | |||
@@ -407,11 +407,6 @@ public class ExpertInviteManage { | |||
removeExpertIds.addAll(tempRefused); | |||
}); | |||
} | |||
// 被取消的也不可以被再次抽中 | |||
BizUtils.notEmpty(expertGroupByStatus.get(ExpertAttendStatusEnum.REMOVED), w -> { | |||
List<Long> tempCanceled = CollUtils.fieldList(w, MeetingExpert::getExpertId); | |||
removeExpertIds.addAll(tempCanceled); | |||
}); | |||
List<Long> tempExpertIds = CollUtils.fieldList(removeExpertByCompany, MeetingExpert::getExpertId); | |||
// 移除确认参加、通知中的、拒绝参加、已取消 | |||
userFullInfos.removeIf(w -> tempExpertIds.contains(w.getUserId()) || removeExpertIds.contains(w.getUserId())); | |||
@@ -545,13 +540,13 @@ public class ExpertInviteManage { | |||
if (CollectionUtils.isNotEmpty(randomRules)) { | |||
List<ExpertInviteRule> randoms = new ArrayList<>(); | |||
List<ExpertChooseDTO> expertsByRandom = new ArrayList<>(); | |||
List<Long> choosedExpertIds = new ArrayList<>(); | |||
List<Long> chooseExpertIds = new ArrayList<>(); | |||
LocalDateTime startTime = meeting.getStartTime(); | |||
LocalDateTime endTime = meeting.getEndTime(); | |||
randomRules.forEach(rule -> { | |||
ExpertChooseDTO tempExperts = expertInviteByRandomRule(avoidRuled, rule, choosedExpertIds, startTime, endTime); | |||
ExpertChooseDTO tempExperts = expertInviteByRandomRule(avoidRuled, rule, chooseExpertIds, startTime, endTime); | |||
expertsByRandom.add(tempExperts); | |||
choosedExpertIds.addAll(CollUtils.fieldList(tempExperts.getExperts(), ExpertUserFullInfo::getUserId)); | |||
chooseExpertIds.addAll(CollUtils.fieldList(tempExperts.getExperts(), ExpertUserFullInfo::getUserId)); | |||
randoms.add(getExpertInviteRule(rule, meeting.getId())); | |||
}); | |||
inviteRuleService.saveBatch(randoms); | |||
@@ -140,6 +140,7 @@ public class MeetingManage { | |||
} | |||
} | |||
@Transactional(rollbackFor = Exception.class) | |||
public void continueInvite(Long meetingId) { | |||
String key = "CONTINUE_INVITE:" + meetingId; | |||
if (!distributedLock.lock(key, RETRY_TIMES)) { | |||
@@ -157,7 +158,28 @@ public class MeetingManage { | |||
if (!invitedContinue) { | |||
throw BizException.wrap("抽取人员数量已满足抽取规则"); | |||
} | |||
expertInviteTask.notifyInviteTask(meetingId); | |||
} finally { | |||
distributedLock.releaseLock(key); | |||
} | |||
} | |||
@Transactional(rollbackFor = Exception.class) | |||
public void convertToAppoint(Long meetingId) { | |||
String key = "CONVERT_TO_APPOINT:" + meetingId; | |||
if (!distributedLock.lock(key, RETRY_TIMES)) { | |||
throw BizException.wrap("已进行转换,请勿重复点击"); | |||
} | |||
try { | |||
Meeting meeting = meetingService.getById(meetingId); | |||
if (!MeetingStatusEnum.NORMAL.eq(meeting.getStatus())) { | |||
throw BizException.wrap("转换失败,请刷新后重试"); | |||
} | |||
expertInviteTask.cancelByMeetingId(meetingId); | |||
LambdaUpdateWrapper<Meeting> meetingUpdate = Wrappers.lambdaUpdate(Meeting.class) | |||
.set(Meeting::getInviteType, ExpertInviteTypeEnum.APPOINT.getCode()) | |||
.eq(Meeting::getId, meetingId); | |||
meetingService.update(meetingUpdate); | |||
} finally { | |||
distributedLock.releaseLock(key); | |||
} | |||
@@ -4,7 +4,7 @@ | |||
<select id="selectByExpertIdAndStatus" | |||
resultType="com.ningdatech.pmapi.meeting.entity.dto.MeetingAndAttendStatusDTO"> | |||
SELECT em.meeting_id meetingId, em.status, mee.is_attended attended | |||
SELECT em.meeting_id meetingId, em.status | |||
FROM (SELECT ROW_NUMBER() OVER ( PARTITION BY meeting_id ORDER BY update_on DESC ) rowNumber, ID, expert_id, | |||
status, meeting_id | |||
FROM meeting_expert | |||
@@ -14,14 +14,7 @@ | |||
<foreach collection="meetingIds" open="in (" close=")" item="item">#{item}</foreach> | |||
</if> | |||
) em | |||
left join meeting_expert_evaluation mee on mee.expert_meeting_id = em.id | |||
WHERE rowNumber = 1 | |||
<if test="status == 1"> | |||
AND em.status = 3 and mee.is_attended is null | |||
</if> | |||
<if test="status == 2"> | |||
AND em.status = 3 and mee.is_attended = true | |||
</if> | |||
<if test="status == 3"> | |||
AND em.status = 6 | |||
</if> | |||
@@ -23,6 +23,13 @@ public interface IMeetingService extends IService<Meeting> { | |||
**/ | |||
void stopRandomInvite(Long meetingId); | |||
/** | |||
* 工作台会议状态统计 | |||
* | |||
* @param createBy 创建人 | |||
* @return 各状态会议统计 | |||
* @author WendyYang | |||
**/ | |||
Map<MeetingStatusByDashboard, Integer> meetingCountSummary(Long createBy); | |||
} |
@@ -59,12 +59,7 @@ public class MeetingExpertServiceImpl extends ServiceImpl<MeetingExpertMapper, M | |||
.build(); | |||
w.forEach(item -> { | |||
ExpertAttendStatusEnum attendStatus = ExpertAttendStatusEnum.getByCode(item.getStatus()); | |||
if (item.getInviteType().equals(ExpertInviteTypeEnum.APPOINT.getCode())) { | |||
// 被替换和已取消的不计数 | |||
if (attendStatus.equals(ExpertAttendStatusEnum.REMOVED) | |||
|| attendStatus.equals(ExpertAttendStatusEnum.REPLACED)) { | |||
return; | |||
} | |||
if (ExpertInviteTypeEnum.APPOINT.eq(item.getInviteType())) { | |||
confirm.setTotal(confirm.getTotal() + 1); | |||
} | |||
// 除通知中的均为已确认 | |||
@@ -34,8 +34,7 @@ public class MeetingServiceImpl extends ServiceImpl<MeetingMapper, Meeting> impl | |||
@Override | |||
public Map<MeetingStatusByDashboard, Integer> meetingCountSummary(Long createBy) { | |||
List<CountGroupByDTO<String>> meetingCountSummary = baseMapper.meetingCountSummary(createBy); | |||
return CollUtils.listToMap(meetingCountSummary, | |||
w -> MeetingStatusByDashboard.valueOf(w.getGroupKey()), | |||
return CollUtils.listToMap(meetingCountSummary, w -> MeetingStatusByDashboard.valueOf(w.getGroupKey()), | |||
CountGroupByDTO::getTotal); | |||
} | |||
@@ -12,10 +12,9 @@ import com.ningdatech.pmapi.meeting.entity.domain.Meeting; | |||
import com.ningdatech.pmapi.meeting.entity.domain.MeetingExpert; | |||
import com.ningdatech.pmapi.meeting.entity.dto.AvoidRuleDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.ExpertChooseDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.MeetingInviteCacheDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.InviteCacheDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.RandomInviteRuleDTO; | |||
import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertAttendStatusEnum; | |||
import com.ningdatech.pmapi.meeting.helper.ExpertInviteHelper; | |||
import com.ningdatech.pmapi.meeting.helper.YxtCallOrSmsHelper; | |||
import com.ningdatech.pmapi.meeting.manage.ExpertInviteManage; | |||
import com.ningdatech.pmapi.meeting.service.IExpertInviteAvoidRuleService; | |||
@@ -62,15 +61,10 @@ import java.util.stream.Collectors; | |||
@AllArgsConstructor | |||
public class ExpertInviteTask { | |||
private final ExpertInviteHelper expertInviteHelper; | |||
private final RandomInviteProperties properties; | |||
private static final String MEETING_ID_INVITE_RANDOM = "MEETING_ID_INVITE_RANDOM"; | |||
private static final CacheHashKey CACHE_KEY = new CacheHashKey(); | |||
static { | |||
CACHE_KEY.setKey(MEETING_ID_INVITE_RANDOM); | |||
CACHE_KEY.setExpire(Duration.ofDays(100)); | |||
} | |||
private static final Duration EXPIRE_TIME = Duration.ofDays(60); | |||
private final CachePlusOps cachePlusOps; | |||
@Qualifier("expertInviteScheduler") | |||
@@ -92,6 +86,10 @@ public class ExpertInviteTask { | |||
return (ExpertInviteTask) AopContext.currentProxy(); | |||
} | |||
private CacheHashKey getCacheKey(Long meetingId) { | |||
return new CacheHashKey(MEETING_ID_INVITE_RANDOM, meetingId, EXPIRE_TIME); | |||
} | |||
@PostConstruct | |||
public void initTask() { | |||
if (!properties.getEnable()) { | |||
@@ -105,12 +103,12 @@ public class ExpertInviteTask { | |||
* 项目重启之后重新初始化邀请任务 | |||
*/ | |||
private void initInviteTaskAfterAppStarted() { | |||
Map<Long, MeetingInviteCacheDTO> meetingIdMap = cachePlusOps.hGetAll(CACHE_KEY); | |||
if (MapUtils.isEmpty(meetingIdMap)) { | |||
Map<Long, InviteCacheDTO> caches = cachePlusOps.hGetAll(getCacheKey(null)); | |||
if (MapUtils.isEmpty(caches)) { | |||
log.info("暂无需要初始化的抽取会议信息"); | |||
return; | |||
} | |||
for (MeetingInviteCacheDTO cache : meetingIdMap.values()) { | |||
for (InviteCacheDTO cache : caches.values()) { | |||
addInviteExpertTask(cache.getMeetingId(), true, properties.getInviteDelay(), cache.getInvitedRefused()); | |||
} | |||
} | |||
@@ -151,16 +149,16 @@ public class ExpertInviteTask { | |||
* @param meetingId 会议ID | |||
* @author WendyYang | |||
**/ | |||
public void addInviteExpertTask(Long meetingId) { | |||
public void notifyInviteTask(Long meetingId) { | |||
if (!INVITE_MAP.containsKey(meetingId)) { | |||
addInviteExpertTask(meetingId, false, properties.getInviteDelay(), false); | |||
addInviteExpertTask(meetingId, false, properties.getInviteDelay(), true); | |||
log.info("重置会议的随机抽取状态:{}", meetingId); | |||
LambdaUpdateWrapper<Meeting> update = Wrappers.lambdaUpdate(Meeting.class); | |||
update.set(Meeting::getInviteStatus, false); | |||
update.set(Meeting::getUpdateBy, LoginUserUtil.getUserId()); | |||
update.set(Meeting::getUpdateOn, LocalDateTime.now()); | |||
update.eq(Meeting::getId, meetingId); | |||
meetingService.update(update); | |||
InviteCacheDTO cacheVal = InviteCacheDTO.of(meetingId, true); | |||
cachePlusOps.hSet(getCacheKey(meetingId), cacheVal); | |||
} | |||
} | |||
@@ -201,7 +199,8 @@ public class ExpertInviteTask { | |||
public void addInviteExpertTaskByMeetingCreate(Long meetingId, int delayedMinutes) { | |||
Assert.isTrue(properties.getEnable(), "随机邀请已关闭"); | |||
addInviteExpertTask(meetingId, false, delayedMinutes, false); | |||
cachePlusOps.sAdd(CACHE_KEY, meetingId); | |||
InviteCacheDTO cacheVal = InviteCacheDTO.of(meetingId, false); | |||
cachePlusOps.hSet(getCacheKey(meetingId), cacheVal); | |||
} | |||
@Transactional(rollbackFor = Exception.class) | |||
@@ -268,7 +267,7 @@ public class ExpertInviteTask { | |||
public void cancelByMeetingId(Long meetingId) { | |||
log.info("终止专家抽取:{}", meetingId); | |||
meetingService.stopRandomInvite(meetingId); | |||
cachePlusOps.sRem(CACHE_KEY, meetingId); | |||
cachePlusOps.hDel(getCacheKey(meetingId)); | |||
ScheduledFuture<?> future = INVITE_MAP.get(meetingId); | |||
if (future != null) { | |||
INVITE_MAP.remove(meetingId); | |||