@@ -105,4 +105,8 @@ public class BizUtils { | |||||
}))); | }))); | ||||
} | } | ||||
public static String inSqlJoin(List<String> strings) { | |||||
return strings.stream().map(w -> "'" + w + "'").collect(Collectors.joining(StrPool.COMMA, StrPool.LEFT_BRACKET, StrPool.RIGHT_BRACKET)); | |||||
} | |||||
} | } |
@@ -1,8 +1,13 @@ | |||||
package com.ningdatech.pmapi.expert.service; | package com.ningdatech.pmapi.expert.service; | ||||
import cn.hutool.core.collection.CollUtil; | |||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers; | |||||
import com.baomidou.mybatisplus.extension.service.IService; | import com.baomidou.mybatisplus.extension.service.IService; | ||||
import com.ningdatech.basic.util.CollUtils; | |||||
import com.ningdatech.pmapi.expert.entity.ExpertUserFullInfo; | import com.ningdatech.pmapi.expert.entity.ExpertUserFullInfo; | ||||
import java.util.Collection; | |||||
import java.util.List; | import java.util.List; | ||||
/** | /** | ||||
@@ -33,7 +38,6 @@ public interface IExpertUserFullInfoService extends IService<ExpertUserFullInfo> | |||||
List<ExpertUserFullInfo> listByUserId(List<Long> userId); | List<ExpertUserFullInfo> listByUserId(List<Long> userId); | ||||
/** | /** | ||||
* 批量查询专家用户信息 | * 批量查询专家用户信息 | ||||
* | * | ||||
@@ -42,4 +46,12 @@ public interface IExpertUserFullInfoService extends IService<ExpertUserFullInfo> | |||||
*/ | */ | ||||
List<ExpertUserFullInfo> listByUserIds(List<Long> userIds); | List<ExpertUserFullInfo> listByUserIds(List<Long> userIds); | ||||
default List<String> listCompanyUniqCodeByUserIds(Collection<Long> userIds) { | |||||
LambdaQueryWrapper<ExpertUserFullInfo> query = Wrappers | |||||
.lambdaQuery(ExpertUserFullInfo.class) | |||||
.select(ExpertUserFullInfo::getCompanyUniqCode) | |||||
.in(ExpertUserFullInfo::getUserId, userIds); | |||||
return CollUtils.fieldList(list(query), ExpertUserFullInfo::getCompanyUniqCode); | |||||
} | |||||
} | } |
@@ -1,8 +1,6 @@ | |||||
package com.ningdatech.pmapi.leave.entity.domain; | 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 com.baomidou.mybatisplus.annotation.*; | |||||
import io.swagger.annotations.ApiModel; | import io.swagger.annotations.ApiModel; | ||||
import io.swagger.annotations.ApiModelProperty; | import io.swagger.annotations.ApiModelProperty; | ||||
import lombok.Data; | import lombok.Data; | ||||
@@ -48,15 +46,19 @@ public class ExpertLeave implements Serializable { | |||||
private String creator; | private String creator; | ||||
@ApiModelProperty("创建人") | @ApiModelProperty("创建人") | ||||
@TableField(fill = FieldFill.INSERT_UPDATE) | |||||
private Long createBy; | private Long createBy; | ||||
@ApiModelProperty("创建时间") | @ApiModelProperty("创建时间") | ||||
@TableField(fill = FieldFill.INSERT) | |||||
private LocalDateTime createOn; | private LocalDateTime createOn; | ||||
@ApiModelProperty("修改人") | @ApiModelProperty("修改人") | ||||
@TableField(fill = FieldFill.INSERT_UPDATE) | |||||
private Long updateBy; | private Long updateBy; | ||||
@ApiModelProperty("修改时间") | @ApiModelProperty("修改时间") | ||||
@TableField(fill = FieldFill.INSERT_UPDATE) | |||||
private LocalDateTime updateOn; | private LocalDateTime updateOn; | ||||
@ApiModelProperty("请假开始时间") | @ApiModelProperty("请假开始时间") | ||||
@@ -75,9 +77,4 @@ public class ExpertLeave implements Serializable { | |||||
private Integer status; | private Integer status; | ||||
public void setUpdateAndCreate(Long createBy, LocalDateTime createOn) { | |||||
this.updateBy = this.createBy = createBy; | |||||
this.updateOn = this.createOn = createOn; | |||||
} | |||||
} | } |
@@ -150,7 +150,7 @@ public class LeaveManage { | |||||
leave.setType(po.getType()); | leave.setType(po.getType()); | ||||
leave.setRemark(po.getPostscript()); | leave.setRemark(po.getPostscript()); | ||||
leave.setLeaveUserId(leaveUserId); | leave.setLeaveUserId(leaveUserId); | ||||
leave.setUpdateAndCreate(applyUserId, now); | |||||
leave.setCreator(LoginUserUtil.getUsername()); | |||||
List<KeyValDTO<LocalDateTime, LocalDateTime>> leaveDetailTimes = new ArrayList<>(); | List<KeyValDTO<LocalDateTime, LocalDateTime>> leaveDetailTimes = new ArrayList<>(); | ||||
if (type.equals(LeaveTypeEnum.FIXED_TERM) || type.equals(LeaveTypeEnum.LONG_TERM)) { | if (type.equals(LeaveTypeEnum.FIXED_TERM) || type.equals(LeaveTypeEnum.LONG_TERM)) { | ||||
boolean fixedTerm = CollUtil.isNotEmpty(po.getFixedType()); | boolean fixedTerm = CollUtil.isNotEmpty(po.getFixedType()); | ||||
@@ -70,6 +70,9 @@ public class ExpertInviteManage { | |||||
@Value("#{randomInviteProperties.recentMeetingCount}") | @Value("#{randomInviteProperties.recentMeetingCount}") | ||||
private Integer recentMeetingCount; | private Integer recentMeetingCount; | ||||
@Value("#{randomInviteProperties.recentDays}") | |||||
private Integer recentDays; | |||||
private static final Predicate<Collection<?>> COLL_EMPTY = (coll) -> coll != null && coll.isEmpty(); | private static final Predicate<Collection<?>> COLL_EMPTY = (coll) -> coll != null && coll.isEmpty(); | ||||
private LambdaQueryWrapper<ExpertUserFullInfo> buildBaseExpertQuery() { | private LambdaQueryWrapper<ExpertUserFullInfo> buildBaseExpertQuery() { | ||||
@@ -83,6 +86,36 @@ public class ExpertInviteManage { | |||||
.eq(ExpertUserFullInfo::getExpertAccountStatus, ExpertAccountStatusEnum.AVAILABLE.getKey()); | .eq(ExpertUserFullInfo::getExpertAccountStatus, ExpertAccountStatusEnum.AVAILABLE.getKey()); | ||||
} | } | ||||
private void buildAvoidCompanyAndBusinessStrip(LambdaQueryWrapper<ExpertUserFullInfo> query, List<String> units, List<String> strips) { | |||||
if (CollUtil.isNotEmpty(units)) { | |||||
String unitStr = BizUtils.inSqlJoin(units); | |||||
query.notExists("select 1 from expert_avoid_company eac where eac.user_id = nd_expert_user_full_info.user_id" + | |||||
" and company_uniq_code in " + unitStr); | |||||
} | |||||
if (CollUtil.isNotEmpty(strips)) { | |||||
String orgStr = BizUtils.inSqlJoin(strips); | |||||
query.notExists("select 1 from expert_gov_business_strip egbs where egbs.expert_user_id = nd_expert_user_full_info.user_id" + | |||||
" and business_strip_code in " + orgStr); | |||||
} | |||||
} | |||||
/** | |||||
* 获取一周内被抽中并同意参会的专家ID | |||||
* | |||||
* @param agreeCnt 一周内被抽中并同意参会次数 | |||||
* @param sTime 会议开始时间 | |||||
* @param days 天数 | |||||
* @return java.util.List<java.lang.Long> | |||||
* @author WendyYang | |||||
**/ | |||||
private List<Long> listAgreedUserIdByRecentMeetings(int agreeCnt, int days, LocalDateTime sTime) { | |||||
if (agreeCnt == 0 || days == 0) { | |||||
return Collections.emptyList(); | |||||
} | |||||
LocalDateTime beginLimit = sTime.minusDays(days); | |||||
return meetingExpertService.listAgreeExpertIdByRecentDaysAndAgreeCount(agreeCnt, beginLimit, sTime); | |||||
} | |||||
/** | /** | ||||
* 增加专家层级限制 | * 增加专家层级限制 | ||||
* | * | ||||
@@ -249,13 +282,13 @@ public class ExpertInviteManage { | |||||
* | * | ||||
* @param avoidRule 回避信息 | * @param avoidRule 回避信息 | ||||
* @param randomRule 抽取规则 | * @param randomRule 抽取规则 | ||||
* @param appointExpertIds 指定抽取专家ID | |||||
* @param invitedExpertIds 指定抽取专家ID | |||||
* @return 满足抽取条件的专家 | * @return 满足抽取条件的专家 | ||||
* @author WendyYang | * @author WendyYang | ||||
**/ | **/ | ||||
public ExpertChooseDTO expertInviteByRandomRule(AvoidRuleDTO avoidRule, | public ExpertChooseDTO expertInviteByRandomRule(AvoidRuleDTO avoidRule, | ||||
RandomInviteRuleDTO randomRule, | RandomInviteRuleDTO randomRule, | ||||
List<Long> appointExpertIds, | |||||
List<Long> invitedExpertIds, | |||||
LocalDateTime sTime, | LocalDateTime sTime, | ||||
LocalDateTime eTime) { | LocalDateTime eTime) { | ||||
ExpertChooseDTO result = new ExpertChooseDTO(new ArrayList<>(), 0); | ExpertChooseDTO result = new ExpertChooseDTO(new ArrayList<>(), 0); | ||||
@@ -265,17 +298,19 @@ public class ExpertInviteManage { | |||||
} | } | ||||
boolean avoidExpert = CollUtil.isNotEmpty(avoidRule.getExpertIds()); | boolean avoidExpert = CollUtil.isNotEmpty(avoidRule.getExpertIds()); | ||||
boolean avoidCompany = CollUtil.isNotEmpty(avoidRule.getAvoidUnitIdList()); | boolean avoidCompany = CollUtil.isNotEmpty(avoidRule.getAvoidUnitIdList()); | ||||
Set<String> tmpAvoidCompany = new HashSet<>(); | |||||
Set<String> avoidCompanyUniqCodes = new HashSet<>(); | |||||
if (avoidCompany) { | if (avoidCompany) { | ||||
tmpAvoidCompany.addAll(avoidRule.getAvoidUnitIdList()); | |||||
avoidCompanyUniqCodes.addAll(avoidRule.getAvoidUnitIdList()); | |||||
} | |||||
if (CollUtil.isNotEmpty(invitedExpertIds)) { | |||||
List<String> tmpCompanyUniqCodes = expertUserFullInfoService.listCompanyUniqCodeByUserIds(invitedExpertIds); | |||||
avoidCompanyUniqCodes.addAll(tmpCompanyUniqCodes); | |||||
} | } | ||||
// 回避信息 | // 回避信息 | ||||
LambdaQueryWrapper<ExpertUserFullInfo> query = buildBaseExpertQuery(); | LambdaQueryWrapper<ExpertUserFullInfo> query = buildBaseExpertQuery(); | ||||
query.notIn(!tmpAvoidCompany.isEmpty(), ExpertUserFullInfo::getCompanyUniqCode, tmpAvoidCompany); | |||||
if (avoidCompany) { | |||||
query.notExists("select 1 from expert_avoid_company eac where eac.user_id = nd_expert_user_full_info.user_id" + | |||||
" and company_uniq_code in ({0})", CollUtils.joinByComma(avoidRule.getAvoidUnitIdList())); | |||||
} | |||||
query.notIn(!avoidCompanyUniqCodes.isEmpty(), ExpertUserFullInfo::getCompanyUniqCode, avoidCompanyUniqCodes); | |||||
// 处理回避单位与回避条线 | |||||
buildAvoidCompanyAndBusinessStrip(query, avoidRule.getAvoidUnitIdList(), avoidRule.getAvoidOrgIdList()); | |||||
Set<Long> expertIdsIn = new HashSet<>(); | Set<Long> expertIdsIn = new HashSet<>(); | ||||
Set<Long> expertIdsNotIn = new HashSet<>(); | Set<Long> expertIdsNotIn = new HashSet<>(); | ||||
if (CollUtil.isNotEmpty(merge.getExpertIdsIn())) { | if (CollUtil.isNotEmpty(merge.getExpertIdsIn())) { | ||||
@@ -283,6 +318,12 @@ public class ExpertInviteManage { | |||||
} else if (CollUtil.isNotEmpty(merge.getExpertIdsNotIn())) { | } else if (CollUtil.isNotEmpty(merge.getExpertIdsNotIn())) { | ||||
expertIdsNotIn.addAll(merge.getExpertIdsNotIn()); | expertIdsNotIn.addAll(merge.getExpertIdsNotIn()); | ||||
} | } | ||||
// 处理回避专家次数 | |||||
if (avoidRule.getWeekInviteCount() != null) { | |||||
Integer weekInviteCount = avoidRule.getWeekInviteCount(); | |||||
List<Long> tmpExpertIdsNotIn = listAgreedUserIdByRecentMeetings(weekInviteCount, recentDays, sTime); | |||||
expertIdsNotIn.addAll(tmpExpertIdsNotIn); | |||||
} | |||||
// 处理专家层级 | // 处理专家层级 | ||||
addRegionLimit(query, randomRule); | addRegionLimit(query, randomRule); | ||||
if (!expertIdsIn.isEmpty()) { | if (!expertIdsIn.isEmpty()) { | ||||
@@ -293,8 +334,8 @@ public class ExpertInviteManage { | |||||
return result; | return result; | ||||
} | } | ||||
} | } | ||||
if (CollUtil.isNotEmpty(appointExpertIds)) { | |||||
expertIdsIn.removeIf(appointExpertIds::contains); | |||||
if (CollUtil.isNotEmpty(invitedExpertIds)) { | |||||
expertIdsIn.removeIf(invitedExpertIds::contains); | |||||
if (expertIdsIn.isEmpty()) { | if (expertIdsIn.isEmpty()) { | ||||
return result; | return result; | ||||
} | } | ||||
@@ -305,8 +346,8 @@ public class ExpertInviteManage { | |||||
if (expertIdsIn.isEmpty()) { | if (expertIdsIn.isEmpty()) { | ||||
return result; | return result; | ||||
} | } | ||||
} else if (avoidExpert || CollUtil.isNotEmpty(appointExpertIds)) { | |||||
Set<Long> tmpExpert = expertInviteHelper.getAvoidExpert(appointExpertIds, avoidRule, sTime, eTime); | |||||
} else if (avoidExpert || CollUtil.isNotEmpty(invitedExpertIds)) { | |||||
Set<Long> tmpExpert = expertInviteHelper.getAvoidExpert(invitedExpertIds, avoidRule, sTime, eTime); | |||||
expertIdsNotIn.addAll(tmpExpert); | expertIdsNotIn.addAll(tmpExpert); | ||||
} else { | } else { | ||||
Set<Long> tmpNotInUserIds = expertInviteHelper.listExpertLeaveOrInvited(sTime, eTime); | Set<Long> tmpNotInUserIds = expertInviteHelper.listExpertLeaveOrInvited(sTime, eTime); | ||||
@@ -365,8 +406,14 @@ public class ExpertInviteManage { | |||||
LambdaQueryWrapper<ExpertUserFullInfo> query = buildBaseExpertQuery(); | LambdaQueryWrapper<ExpertUserFullInfo> query = buildBaseExpertQuery(); | ||||
// 设置回避单位 | // 设置回避单位 | ||||
Set<String> notInCompanyUniqCodeList = new HashSet<>(avoidRule.getAvoidUnitIdList()); | Set<String> notInCompanyUniqCodeList = new HashSet<>(avoidRule.getAvoidUnitIdList()); | ||||
query.notExists("select 1 from expert_avoid_company eac where eac.user_id = nd_expert_user_full_info.user_id" + | |||||
" and company_uniq_code in ({0})", CollUtils.joinByComma(avoidRule.getAvoidUnitIdList())); | |||||
// 处理回避单位与回避条线 | |||||
buildAvoidCompanyAndBusinessStrip(query, avoidRule.getAvoidUnitIdList(), avoidRule.getAvoidOrgIdList()); | |||||
// 处理回避专家次数 | |||||
if (avoidRule.getWeekInviteCount() != null) { | |||||
Integer weekInviteCount = avoidRule.getWeekInviteCount(); | |||||
List<Long> tmpExpertIdsNotIn = listAgreedUserIdByRecentMeetings(weekInviteCount, recentDays, msTime); | |||||
expertIdsNotIn.addAll(tmpExpertIdsNotIn); | |||||
} | |||||
// 处理专家层级 | // 处理专家层级 | ||||
addRegionLimit(query, randomRule); | addRegionLimit(query, randomRule); | ||||
@@ -9,6 +9,7 @@ import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertAttendStatusEnum; | |||||
import com.ningdatech.pmapi.meeting.entity.req.ReviewProjectListReq; | import com.ningdatech.pmapi.meeting.entity.req.ReviewProjectListReq; | ||||
import org.apache.ibatis.annotations.Param; | import org.apache.ibatis.annotations.Param; | ||||
import java.time.LocalDateTime; | |||||
import java.util.Collection; | import java.util.Collection; | ||||
import java.util.List; | import java.util.List; | ||||
@@ -62,6 +63,19 @@ public interface MeetingExpertMapper extends BaseMapper<MeetingExpert> { | |||||
@Param("meetingIds") Collection<Long> meetingIds); | @Param("meetingIds") Collection<Long> meetingIds); | ||||
/** | /** | ||||
* 查询时间窗口之内参与会议不超过{@code agreeCount}次的专家ID | |||||
* | |||||
* @param agreeCount 参与次数(包含) | |||||
* @param startTime 开始时间 | |||||
* @param endTime 结束时间 | |||||
* @return 专家ID集合 | |||||
* @author WendyYang | |||||
**/ | |||||
List<Long> listAgreeExpertIdByRecentDaysAndAgreeCount(@Param("agreeCount") Integer agreeCount, | |||||
@Param("startTime") LocalDateTime startTime, | |||||
@Param("endTime") LocalDateTime endTime); | |||||
/** | |||||
* 根据会议ID与参与状态统计专家数量 | * 根据会议ID与参与状态统计专家数量 | ||||
* | * | ||||
* @param status 会议状态 | * @param status 会议状态 | ||||
@@ -50,6 +50,15 @@ | |||||
</if> | </if> | ||||
</select> | </select> | ||||
<select id="listAgreeExpertIdByRecentDaysAndAgreeCount" resultType="long"> | |||||
SELECT expert_id FROM (SELECT ROW_NUMBER() OVER ( PARTITION BY expert_id, meeting_id ORDER BY update_on DESC ) | |||||
rowNumber,expert_id, status FROM meeting_expert | |||||
where meeting_id in (select id from meeting m where m.status = 1 | |||||
and ((m.start_time >= #{startTime} and m.start_time < #{endTime}) | |||||
or (m.end_time >= #{startTime} and m.end_time < #{endTime})) )) em | |||||
WHERE rowNumber = 1 and status = 3 group by expert_id having count(1) <= #{agreeCount} | |||||
</select> | |||||
<select id="countExpertByStatusAndMeetingId" | <select id="countExpertByStatusAndMeetingId" | ||||
resultType="int"> | resultType="int"> | ||||
SELECT count(1) FROM (SELECT ROW_NUMBER() OVER ( PARTITION BY expert_id, meeting_id ORDER BY update_on DESC ) | SELECT count(1) FROM (SELECT ROW_NUMBER() OVER ( PARTITION BY expert_id, meeting_id ORDER BY update_on DESC ) | ||||
@@ -9,8 +9,8 @@ import com.ningdatech.pmapi.meeting.entity.dto.ReviewProjectDTO; | |||||
import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertAttendStatusEnum; | import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertAttendStatusEnum; | ||||
import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertInviteTypeEnum; | import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertInviteTypeEnum; | ||||
import com.ningdatech.pmapi.meeting.entity.req.ReviewProjectListReq; | import com.ningdatech.pmapi.meeting.entity.req.ReviewProjectListReq; | ||||
import org.apache.ibatis.annotations.Param; | |||||
import java.time.LocalDateTime; | |||||
import java.util.Collection; | import java.util.Collection; | ||||
import java.util.Collections; | import java.util.Collections; | ||||
import java.util.List; | import java.util.List; | ||||
@@ -155,4 +155,6 @@ public interface IMeetingExpertService extends IService<MeetingExpert> { | |||||
**/ | **/ | ||||
Page<ReviewProjectDTO> pageReviewProjectList(ReviewProjectListReq req); | Page<ReviewProjectDTO> pageReviewProjectList(ReviewProjectListReq req); | ||||
List<Long> listAgreeExpertIdByRecentDaysAndAgreeCount(int agreeCount, LocalDateTime sTime, LocalDateTime eTime); | |||||
} | } |
@@ -19,6 +19,7 @@ import com.ningdatech.pmapi.meeting.service.IMeetingExpertService; | |||||
import lombok.RequiredArgsConstructor; | import lombok.RequiredArgsConstructor; | ||||
import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||
import java.time.LocalDateTime; | |||||
import java.util.Collection; | import java.util.Collection; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
@@ -134,4 +135,9 @@ public class MeetingExpertServiceImpl extends ServiceImpl<MeetingExpertMapper, M | |||||
return baseMapper.pageReviewProjectList(req.page(), req); | return baseMapper.pageReviewProjectList(req.page(), req); | ||||
} | } | ||||
@Override | |||||
public List<Long> listAgreeExpertIdByRecentDaysAndAgreeCount(int agreeCount, LocalDateTime sTime, LocalDateTime eTime) { | |||||
return baseMapper.listAgreeExpertIdByRecentDaysAndAgreeCount(agreeCount, sTime, eTime); | |||||
} | |||||
} | } |
@@ -207,7 +207,7 @@ public class ExpertCallResultRewriteTask { | |||||
status = REFUSED; | status = REFUSED; | ||||
} | } | ||||
} catch (Exception e) { | } catch (Exception e) { | ||||
log.error("获取电话回调结果异常", e); | |||||
log.error("获取电话回调结果异常{}", mrd, e); | |||||
status = UNANSWERED; | status = UNANSWERED; | ||||
} | } | ||||
} else { | } else { | ||||
@@ -42,4 +42,10 @@ public class RandomInviteProperties { | |||||
* 近期会议数量(以此来降低专家抽中间隔) | * 近期会议数量(以此来降低专家抽中间隔) | ||||
*/ | */ | ||||
private Integer recentMeetingCount = 5; | private Integer recentMeetingCount = 5; | ||||
/** | |||||
* 参会次数限制天数 | |||||
*/ | |||||
private Integer recentDays = 7; | |||||
} | } |