diff --git a/pmapi/pom.xml b/pmapi/pom.xml index 016a7fe..8bb2f6d 100644 --- a/pmapi/pom.xml +++ b/pmapi/pom.xml @@ -262,6 +262,11 @@ com.itextpdf html2pdf + + + cn.afterturn + easypoi-base + diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/common/constant/CommonConst.java b/pmapi/src/main/java/com/ningdatech/pmapi/common/constant/CommonConst.java index 7f1dd50..953d02c 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/common/constant/CommonConst.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/common/constant/CommonConst.java @@ -32,6 +32,7 @@ public interface CommonConst { String ARCHIVED = "归档"; String FILE_NAME = "name"; + String BASIS_FILE_NAME = "fileName"; String NULL = "null"; @@ -45,6 +46,12 @@ public interface CommonConst { String NEW_CONSTRUCTION = "新建"; String CONTINUED_CONSTRUCTION = "续建"; String MONTH = "月"; + String ZHI = "至"; + String YEAR = "年"; + + Integer VERSION_ONE = 1; + Integer VERSION_SIZE = 2; + Integer VERSION_JUDGE = -1; diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/common/statemachine/action/ProjectDeclareAction.java b/pmapi/src/main/java/com/ningdatech/pmapi/common/statemachine/action/ProjectDeclareAction.java index e44bd93..02ab1a7 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/common/statemachine/action/ProjectDeclareAction.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/common/statemachine/action/ProjectDeclareAction.java @@ -44,7 +44,7 @@ public class ProjectDeclareAction { } @OnTransition(source = "UNDER_INTERNAL_AUDIT_NOT_PASS", target = "UNDER_INTERNAL_AUDIT") - public void UNDER_INTERNAL_RESUBMIT(Message message) { + public void UNDER_INTERNAL_REJECT_RESUBMIT(Message message) { Project project = (Project) message.getHeaders().get(PROJECT_DECLARE); project.setStatus(ProjectStatusEnum.UNDER_INTERNAL_AUDIT.getCode()); } @@ -99,6 +99,12 @@ public class ProjectDeclareAction { project.setStatus(ProjectStatusEnum.PREQUALIFICATION_FAILED.getCode()); } + @OnTransition(source = "PREQUALIFICATION_FAILED", target = "PENDING_PREQUALIFICATION_CHOICE") + public void PRELIMINARY_REVIEW_REJECT_RESUBMIT(Message message) { + Project project = (Project) message.getHeaders().get(PROJECT_DECLARE); + project.setStatus(ProjectStatusEnum.PENDING_PREQUALIFICATION_CHOICE.getCode()); + } + @OnTransition(source = "PRE_APPLYING", target = "PREQUALIFICATION_WITHDRAW_CHOICE") public void PRE_APPLYING_WITHDRAW(Message message) { Project project = (Project) message.getHeaders().get(PROJECT_DECLARE); @@ -174,6 +180,12 @@ public class ProjectDeclareAction { project.setStatus(ProjectStatusEnum.SCHEME_REVIEW_FAILED.getCode()); } + @OnTransition(source = "SCHEME_REVIEW_FAILED", target = "SCHEME_UNDER_REVIEW") + public void PLAN_REVIEW_REJECT_RESUBMIT(Message message) { + Project project = (Project) message.getHeaders().get(PROJECT_DECLARE); + project.setStatus(ProjectStatusEnum.SCHEME_UNDER_REVIEW.getCode()); + } + @OnTransition(source = "SCHEME_UNDER_REVIEW", target = "PLAN_TO_BE_DECLARED") public void SCHEME_UNDER_REVIEW_WITHDRAW(Message message) { Project project = (Project) message.getHeaders().get(PROJECT_DECLARE); diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/common/statemachine/builder/ProjectDeclareStateMachineBuilder.java b/pmapi/src/main/java/com/ningdatech/pmapi/common/statemachine/builder/ProjectDeclareStateMachineBuilder.java index 4cffea8..07ce7e4 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/common/statemachine/builder/ProjectDeclareStateMachineBuilder.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/common/statemachine/builder/ProjectDeclareStateMachineBuilder.java @@ -86,7 +86,7 @@ public class ProjectDeclareStateMachineBuilder { .withExternal() .source(ProjectStatusEnum.UNDER_INTERNAL_AUDIT_NOT_PASS) .target(ProjectStatusEnum.UNDER_INTERNAL_AUDIT) - .event(ProjectStatusChangeEvent.PROJECT_APPLICATION_SUBMIT).and() + .event(ProjectStatusChangeEvent.UNDER_INTERNAL_REJECT_RESUBMIT).and() // 待预审预审申报,从待预审到待预审选择 .withExternal() @@ -145,7 +145,7 @@ public class ProjectDeclareStateMachineBuilder { .withExternal() .source(ProjectStatusEnum.PREQUALIFICATION_FAILED) .target(ProjectStatusEnum.PENDING_PREQUALIFICATION_CHOICE) - .event(ProjectStatusChangeEvent.PRELIMINARY_REVIEW_DECLARE).and() + .event(ProjectStatusChangeEvent.PRELIMINARY_REVIEW_REJECT_RESUBMIT).and() // 预审不通过重新提交,从待预审选择->省级部门联审中,预审中,完成其中一种状态 .withChoice() .source(ProjectStatusEnum.PENDING_PREQUALIFICATION_CHOICE) @@ -200,7 +200,7 @@ public class ProjectDeclareStateMachineBuilder { .withExternal() .source(ProjectStatusEnum.SCHEME_REVIEW_FAILED) .target(ProjectStatusEnum.SCHEME_UNDER_REVIEW) - .event(ProjectStatusChangeEvent.DECLARE_PLAN).and() + .event(ProjectStatusChangeEvent.PLAN_REVIEW_REJECT_RESUBMIT).and() // 待立项批复批复,从待立项批复到待采购 .withExternal() .source(ProjectStatusEnum.TO_BE_APPROVED) diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/common/statemachine/event/ProjectStatusChangeEvent.java b/pmapi/src/main/java/com/ningdatech/pmapi/common/statemachine/event/ProjectStatusChangeEvent.java index 1d7bbeb..96121f8 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/common/statemachine/event/ProjectStatusChangeEvent.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/common/statemachine/event/ProjectStatusChangeEvent.java @@ -36,6 +36,11 @@ public enum ProjectStatusChangeEvent { */ UNDER_INTERNAL_PASS(ProjectStatusEnum.UNDER_INTERNAL_AUDIT.getCode(), null, null), /** + * 单位内部审核不通过重新提交(项目状态进入:单位内部审核中) + */ + UNDER_INTERNAL_REJECT_RESUBMIT(ProjectStatusEnum.UNDER_INTERNAL_AUDIT_NOT_PASS.getCode(),null,null), + + /** * 预审申报(项目状态进入:待预审选择,有判断条件:市级项目且申报金额大于1000万项目状态变为:省级部门联审中;否则项目状态变为:预审中) */ PRELIMINARY_REVIEW_DECLARE(ProjectStatusEnum.PENDING_PREQUALIFICATION.getCode(), null, null), @@ -57,6 +62,10 @@ public enum ProjectStatusChangeEvent { */ PRELIMINARY_REVIEW_REJECT(null, ProjectStatusEnum.PRE_APPLYING.getCode(), null), /** + * 预审不通过重新提交(项目状态变为:待预审选择) + */ + PRELIMINARY_REVIEW_REJECT_RESUBMIT(ProjectStatusEnum.PREQUALIFICATION_FAILED.getCode(),null,null), + /** * 预审通过(项目状态变为:部门联审中) */ PRELIMINARY_REVIEW_PASS(ProjectStatusEnum.PRE_APPLYING.getCode(), null, null), @@ -85,6 +94,10 @@ public enum ProjectStatusChangeEvent { */ PLAN_REVIEW_REJECT(null, ProjectStatusEnum.SCHEME_UNDER_REVIEW.getCode(), null), /** + * 方案评审不通过重新提交(项目状态变为:方案评审中) + */ + PLAN_REVIEW_REJECT_RESUBMIT(ProjectStatusEnum.SCHEME_REVIEW_FAILED.getCode(),null,null), + /** * 方案评审通过(项目状态变为:待立项批复) */ PLAN_REVIEW_PASS(ProjectStatusEnum.SCHEME_UNDER_REVIEW.getCode(), null, null), diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/common/util/BizUtils.java b/pmapi/src/main/java/com/ningdatech/pmapi/common/util/BizUtils.java index 15d5ec3..809dca5 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/common/util/BizUtils.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/common/util/BizUtils.java @@ -105,4 +105,8 @@ public class BizUtils { }))); } + public static String inSqlJoin(List strings) { + return strings.stream().map(w -> "'" + w + "'").collect(Collectors.joining(StrPool.COMMA, StrPool.LEFT_BRACKET, StrPool.RIGHT_BRACKET)); + } + } diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/datascope/provider/DataScopeContext.java b/pmapi/src/main/java/com/ningdatech/pmapi/datascope/provider/DataScopeContext.java index 7d55cae..c8e5148 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/datascope/provider/DataScopeContext.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/datascope/provider/DataScopeContext.java @@ -4,6 +4,7 @@ import com.ningdatech.basic.util.SpringUtils; import com.ningdatech.pmapi.datascope.model.DataScopeDTO; import lombok.RequiredArgsConstructor; +import java.io.Serializable; import java.util.Map; import java.util.Optional; @@ -16,7 +17,7 @@ import java.util.Optional; * @since 2022/1/9 23:28 */ @RequiredArgsConstructor -public class DataScopeContext { +public class DataScopeContext implements Serializable { private static final String WARN_MSG = "请先创建数据权限[%s]的实现类,使其实现 DataScopeProvider"; diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/expert/controller/ExpertReviewController.java b/pmapi/src/main/java/com/ningdatech/pmapi/expert/controller/ExpertReviewController.java index 176dc26..b4718f4 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/expert/controller/ExpertReviewController.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/expert/controller/ExpertReviewController.java @@ -5,6 +5,7 @@ import com.ningdatech.log.annotation.WebLog; import com.ningdatech.pmapi.expert.manage.ExpertReviewManage; import com.ningdatech.pmapi.expert.model.req.ExpertReviewDetailReq; import com.ningdatech.pmapi.expert.model.vo.ExpertReviewDetailVO; +import com.ningdatech.pmapi.expert.model.vo.ProjectReviewDetailVO; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; @@ -38,14 +39,16 @@ public class ExpertReviewController { expertReviewManage.expertReview(req); } - @GetMapping("/detail/{projectId}/{userId}") + @GetMapping("/detail/{meetingId}/{projectId}/{userId}") @ApiOperation("获取专家评审详情") @ApiImplicitParams({ @ApiImplicitParam(name = "userId", value = "专家ID"), @ApiImplicitParam(name = "projectId", value = "项目ID") }) - public ExpertReviewDetailVO getExpertReviewDetail(@PathVariable Long userId, @PathVariable Long projectId) { - return expertReviewManage.getExpertReviewDetail(userId, projectId); + public ExpertReviewDetailVO getExpertReviewDetail(@PathVariable Long userId, + @PathVariable Long projectId, + @PathVariable Long meetingId) { + return expertReviewManage.getExpertReviewDetail(userId, projectId, meetingId); } @GetMapping("/listForGroupLeader/{projectId}/{meetingId}") @@ -70,4 +73,13 @@ public class ExpertReviewController { return expertReviewManage.listReviews(projectId, meetingId, false); } + @GetMapping("/detail/{projectId}") + @ApiImplicitParams({ + @ApiImplicitParam(name = "projectId", value = "项目ID"), + }) + @ApiOperation("查看项目的所有评审意见") + public ProjectReviewDetailVO projectExpertReviewDetail(@PathVariable Long projectId) { + return expertReviewManage.projectExpertReviewDetail(projectId); + } + } diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/expert/manage/ExpertReviewManage.java b/pmapi/src/main/java/com/ningdatech/pmapi/expert/manage/ExpertReviewManage.java index c6f6b81..f05220b 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/expert/manage/ExpertReviewManage.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/expert/manage/ExpertReviewManage.java @@ -10,19 +10,25 @@ import com.ningdatech.file.entity.vo.result.AttachFileVo; import com.ningdatech.file.service.FileService; import com.ningdatech.pmapi.expert.model.dto.ReviewTemplateOptionDTO; import com.ningdatech.pmapi.expert.model.entity.ExpertReview; +import com.ningdatech.pmapi.expert.model.entity.ReviewTemplateSettings; import com.ningdatech.pmapi.expert.model.req.ExpertReviewDetailReq; import com.ningdatech.pmapi.expert.model.vo.ExpertReviewDetailVO; +import com.ningdatech.pmapi.expert.model.vo.ProjectReviewDetailVO; +import com.ningdatech.pmapi.expert.model.vo.ReviewTemplateVO; import com.ningdatech.pmapi.expert.service.IExpertReviewService; +import com.ningdatech.pmapi.expert.service.IReviewTemplateSettingsService; +import com.ningdatech.pmapi.meeting.entity.domain.Meeting; import com.ningdatech.pmapi.meeting.service.IMeetingExpertService; +import com.ningdatech.pmapi.meeting.service.IMeetingInnerProjectService; +import com.ningdatech.pmapi.meeting.service.IMeetingService; import com.ningdatech.pmapi.user.util.LoginUserUtil; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; +import static com.ningdatech.pmapi.expert.model.vo.ProjectReviewDetailVO.ReviewDetailByTypeVO; import static com.ningdatech.pmapi.meeting.entity.enumeration.ExpertAttendStatusEnum.AGREED; /** @@ -41,6 +47,9 @@ public class ExpertReviewManage { private final DistributedLock distributedLock; private final IMeetingExpertService meetingExpertService; private final FileService fileService; + private final IMeetingInnerProjectService meetingInnerProjectService; + private final IMeetingService meetingService; + private final IReviewTemplateSettingsService templateSettingsService; private static final String EXPERT_REVIEW_KEY = "expert_review:"; @@ -65,12 +74,13 @@ public class ExpertReviewManage { public void expertReview(ExpertReviewDetailReq req) { Long userId = LoginUserUtil.getUserId(); Long projectId = req.getProjectId(); + Long meetingId = req.getMeetingId(); String expertReviewKey = buildExpertReviewKey(projectId, userId); if (!distributedLock.lock(expertReviewKey)) { throw BizException.wrap("保存评审意见失败,请重试"); } try { - List reviews = expertReviewService.listByProjectIdAndExpertId(projectId, userId); + List reviews = expertReviewService.listByProjectIdAndExpertId(projectId, userId, meetingId); if (req.getIsFinal()) { if (reviews.isEmpty()) { throw BizException.wrap("请先填写个人评审意见"); @@ -109,8 +119,8 @@ public class ExpertReviewManage { } } - public ExpertReviewDetailVO getExpertReviewDetail(Long projectId, Long userId) { - List reviews = expertReviewService.listByProjectIdAndExpertId(projectId, userId); + public ExpertReviewDetailVO getExpertReviewDetail(Long projectId, Long userId, Long meetingId) { + List reviews = expertReviewService.listByProjectIdAndExpertId(projectId, userId, meetingId); reviews.removeIf(ExpertReview::getIsFinal); if (reviews.isEmpty()) { throw BizException.wrap("评审记录不存在"); @@ -141,4 +151,51 @@ public class ExpertReviewManage { }); } + public ProjectReviewDetailVO projectExpertReviewDetail(Long projectId) { + ProjectReviewDetailVO detail = new ProjectReviewDetailVO(); + List meetingIds = meetingInnerProjectService.listMeetingIdByProjectId(projectId); + if (meetingIds.isEmpty()) { + return detail; + } + List meetings = meetingService.listByIds(meetingIds); + Collection tmpMeetingIds = meetings.stream() + .collect(Collectors.groupingBy(Meeting::getType, + Collectors.collectingAndThen(Collectors.toList(), w -> { + w.sort(Comparator.comparing(Meeting::getCreateOn)); + return w.get(w.size() - 1).getId(); + }))).values(); + LambdaQueryWrapper erQuery = Wrappers.lambdaQuery(ExpertReview.class) + .in(ExpertReview::getMeetingId, tmpMeetingIds) + .eq(ExpertReview::getProjectId, projectId) + .orderByDesc(ExpertReview::getCreateOn); + List expertReviews = expertReviewService.list(erQuery); + if (expertReviews.isEmpty()) { + return detail; + } + List templateIds = CollUtils.fieldList(expertReviews, ExpertReview::getTemplateId); + List templates = templateSettingsService.listByIds(templateIds); + Map templateMap = CollUtils.listToMap(templates, + ReviewTemplateSettings::getId, + ReviewTemplateSettingsManage::buildTemplateDetail); + detail.setTemplates(templateMap.values()); + Map map = new HashMap<>(8); + expertReviews.forEach(review -> { + ReviewTemplateVO template = templateMap.get(review.getTemplateId()); + ReviewDetailByTypeVO reviewDetailByType = map.computeIfAbsent(template.getTemplateType(), k -> { + ReviewDetailByTypeVO tmpReviewDetail = new ReviewDetailByTypeVO(); + tmpReviewDetail.setReviewType(k); + tmpReviewDetail.setTeamMemberReviews(new ArrayList<>()); + return tmpReviewDetail; + }); + ExpertReviewDetailVO tmpReview = buildExpertReviewDetail(review); + if (review.getIsFinal()) { + reviewDetailByType.setFinalReview(tmpReview); + } else { + reviewDetailByType.getTeamMemberReviews().add(tmpReview); + } + }); + detail.setReviews(map.values()); + return detail; + } + } diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/expert/manage/ReviewTemplateSettingsManage.java b/pmapi/src/main/java/com/ningdatech/pmapi/expert/manage/ReviewTemplateSettingsManage.java index c81ca99..8341463 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/expert/manage/ReviewTemplateSettingsManage.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/expert/manage/ReviewTemplateSettingsManage.java @@ -67,10 +67,10 @@ public class ReviewTemplateSettingsManage { public List listReviewTemplateSettings(List templateIds) { List settings = reviewTemplateSettingsService.listByIds(templateIds); - return CollUtils.convert(settings, this::buildTemplateDetail); + return CollUtils.convert(settings, ReviewTemplateSettingsManage::buildTemplateDetail); } - private ReviewTemplateVO buildTemplateDetail(ReviewTemplateSettings settings) { + protected static ReviewTemplateVO buildTemplateDetail(ReviewTemplateSettings settings) { if (settings == null) { throw BizException.wrap("模版不存在"); } diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/expert/model/vo/ProjectReviewDetailVO.java b/pmapi/src/main/java/com/ningdatech/pmapi/expert/model/vo/ProjectReviewDetailVO.java new file mode 100644 index 0000000..8bb3919 --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/expert/model/vo/ProjectReviewDetailVO.java @@ -0,0 +1,40 @@ +package com.ningdatech.pmapi.expert.model.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Collection; +import java.util.List; + +/** + *

+ * ProjectExpertReviewDetailVO + *

+ * + * @author WendyYang + * @since 2023/4/19 + **/ +@Data +public class ProjectReviewDetailVO { + + @ApiModelProperty("评审模版详情") + private Collection templates; + + private Collection reviews; + + @Data + public static class ReviewDetailByTypeVO { + + @ApiModelProperty("评审类型") + private Integer reviewType; + + @ApiModelProperty("最终评审意见") + private ExpertReviewDetailVO finalReview; + + @ApiModelProperty("组员评审意见") + private List teamMemberReviews; + + } + + +} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/expert/service/IExpertReviewService.java b/pmapi/src/main/java/com/ningdatech/pmapi/expert/service/IExpertReviewService.java index 91074c2..05bbbc0 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/expert/service/IExpertReviewService.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/expert/service/IExpertReviewService.java @@ -21,10 +21,11 @@ public interface IExpertReviewService extends IService { * * @param projectId 项目ID * @param expertId 专家ID + * @param meetingId 会议ID * @return 评审记录 * @author WendyYang **/ - List listByProjectIdAndExpertId(Long projectId, Long expertId); + List listByProjectIdAndExpertId(Long projectId, Long expertId, Long meetingId); /** * 获取最终评审结果 diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/expert/service/IExpertUserFullInfoService.java b/pmapi/src/main/java/com/ningdatech/pmapi/expert/service/IExpertUserFullInfoService.java index a0a6c27..129e68b 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/expert/service/IExpertUserFullInfoService.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/expert/service/IExpertUserFullInfoService.java @@ -1,8 +1,13 @@ 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.ningdatech.basic.util.CollUtils; import com.ningdatech.pmapi.expert.entity.ExpertUserFullInfo; +import java.util.Collection; import java.util.List; /** @@ -33,7 +38,6 @@ public interface IExpertUserFullInfoService extends IService List listByUserId(List userId); - /** * 批量查询专家用户信息 * @@ -42,4 +46,12 @@ public interface IExpertUserFullInfoService extends IService */ List listByUserIds(List userIds); + default List listCompanyUniqCodeByUserIds(Collection userIds) { + LambdaQueryWrapper query = Wrappers + .lambdaQuery(ExpertUserFullInfo.class) + .select(ExpertUserFullInfo::getCompanyUniqCode) + .in(ExpertUserFullInfo::getUserId, userIds); + return CollUtils.fieldList(list(query), ExpertUserFullInfo::getCompanyUniqCode); + } + } diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/expert/service/impl/ExpertReviewServiceImpl.java b/pmapi/src/main/java/com/ningdatech/pmapi/expert/service/impl/ExpertReviewServiceImpl.java index e07a0e3..7b4810a 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/expert/service/impl/ExpertReviewServiceImpl.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/expert/service/impl/ExpertReviewServiceImpl.java @@ -25,10 +25,11 @@ import java.util.Map; public class ExpertReviewServiceImpl extends ServiceImpl implements IExpertReviewService { @Override - public List listByProjectIdAndExpertId(Long projectId, Long expertId) { + public List listByProjectIdAndExpertId(Long projectId, Long expertId, Long meetingId) { LambdaQueryWrapper query = Wrappers.lambdaQuery(ExpertReview.class); query.eq(ExpertReview::getProjectId, projectId); query.eq(ExpertReview::getCreateBy, expertId); + query.eq(ExpertReview::getMeetingId, meetingId); query.orderByAsc(ExpertReview::getCreateOn); return list(query); } diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/leave/entity/domain/ExpertLeave.java b/pmapi/src/main/java/com/ningdatech/pmapi/leave/entity/domain/ExpertLeave.java index 4aca8d3..12ed67f 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/leave/entity/domain/ExpertLeave.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/leave/entity/domain/ExpertLeave.java @@ -1,8 +1,6 @@ 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.ApiModelProperty; import lombok.Data; @@ -48,15 +46,19 @@ public class ExpertLeave implements Serializable { private String creator; @ApiModelProperty("创建人") + @TableField(fill = FieldFill.INSERT_UPDATE) private Long createBy; @ApiModelProperty("创建时间") + @TableField(fill = FieldFill.INSERT) private LocalDateTime createOn; @ApiModelProperty("修改人") + @TableField(fill = FieldFill.INSERT_UPDATE) private Long updateBy; @ApiModelProperty("修改时间") + @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateOn; @ApiModelProperty("请假开始时间") @@ -75,9 +77,4 @@ public class ExpertLeave implements Serializable { private Integer status; - public void setUpdateAndCreate(Long createBy, LocalDateTime createOn) { - this.updateBy = this.createBy = createBy; - this.updateOn = this.createOn = createOn; - } - } diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/leave/manage/LeaveManage.java b/pmapi/src/main/java/com/ningdatech/pmapi/leave/manage/LeaveManage.java index 8246530..aaae8c4 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/leave/manage/LeaveManage.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/leave/manage/LeaveManage.java @@ -150,7 +150,7 @@ public class LeaveManage { leave.setType(po.getType()); leave.setRemark(po.getPostscript()); leave.setLeaveUserId(leaveUserId); - leave.setUpdateAndCreate(applyUserId, now); + leave.setCreator(LoginUserUtil.getUsername()); List> leaveDetailTimes = new ArrayList<>(); if (type.equals(LeaveTypeEnum.FIXED_TERM) || type.equals(LeaveTypeEnum.LONG_TERM)) { boolean fixedTerm = CollUtil.isNotEmpty(po.getFixedType()); @@ -215,6 +215,12 @@ public class LeaveManage { if (ExpertInviteTypeEnum.RANDOM.eq(meeting.getInviteType())) { inviteTask.notifyInviteTask(meeting.getId(), Boolean.FALSE); } + if (meeting.getConfirmedRoster()) { + LambdaUpdateWrapper mUpdate = Wrappers.lambdaUpdate(Meeting.class) + .set(Meeting::getConfirmedRoster, Boolean.FALSE) + .eq(Meeting::getId, meeting.getId()); + meetingService.update(mUpdate); + } // 临时请假无需审核 leave.setAuditId(0L); leave.setStatus(LeaveStatusEnum.PASSED.getCode()); diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/constant/MeetingMsgTemplateConst.java b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/constant/MeetingMsgTemplateConst.java new file mode 100644 index 0000000..1bdbfcd --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/constant/MeetingMsgTemplateConst.java @@ -0,0 +1,24 @@ +package com.ningdatech.pmapi.meeting.constant; + +/** + *

+ * MeetingMsgConst + *

+ * + * @author WendyYang + * @since 2023/4/20 + **/ +public interface MeetingMsgTemplateConst { + + /** + * 已结束:自动抽取结束,结束时给会议发起人发送浙政钉工作通知、短信:“注意,xxx会议自动抽取已结束,请及时确认是否召开会议”。 + */ + String INVITE_END = "注意,%s会议自动抽取已结束,请及时确认是否召开会议"; + + /** + * 尊敬的【姓名】专家您好,您于【确认时间】接受了信息化项目评审会议邀请,会议时间:【会议时间】,会议地点:【会议地点】。请准时参加评审会议。如有疑问请联系【联系人】(【联系方式】)。 + */ + String CONFIRMED_ROSTER = "尊敬的%s专家您好,您于%s接受了信息化项目评审会议邀请,会议时间:%s,会议地点:%s。请准时参加评审会议。如有疑问请联系%s(%s)。"; + + +} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/controller/MeetingController.java b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/controller/MeetingController.java index 91f7e1e..77ff065 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/controller/MeetingController.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/controller/MeetingController.java @@ -153,6 +153,13 @@ public class MeetingController { meetingManage.confirmAttendByManager(req); } + @ApiOperation("专家移除") + @PostMapping("/expertRemove") + @WebLog(value = "专家移除") + public void expertRemove(@Valid @RequestBody ExpertRemoveReq req) { + meetingManage.expertRemove(req); + } + @ApiOperation("释放专家") @PostMapping("/expert/release") @WebLog(value = "释放专家") @@ -167,11 +174,11 @@ public class MeetingController { meetingManage.setUpHeadman(req); } - @ApiOperation("重发短信") + @ApiOperation("重发短信 | 确认名单") @PostMapping("/confirmedRoster") @WebLog(value = "重发短信") - public void resendSms(@RequestBody MeetingCancelReq req) { - meetingManage.confirmedRoster(req.getMeetingId()); + public void resendSms(@RequestBody ConfirmedRosterReq req) { + meetingManage.confirmedRoster(req); } @GetMapping("/listReviewProject") diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/domain/MeetingExpert.java b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/domain/MeetingExpert.java index 73533f4..a19cd8d 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/domain/MeetingExpert.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/domain/MeetingExpert.java @@ -56,6 +56,9 @@ public class MeetingExpert implements Serializable { @ApiModelProperty("邀请类型") private Integer inviteType; + @ApiModelProperty("是否已确认名单") + private Boolean confirmedRoster; + private String submitKey; @TableField(fill = FieldFill.INSERT) diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/req/ConfirmedRosterReq.java b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/req/ConfirmedRosterReq.java new file mode 100644 index 0000000..e16b55f --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/req/ConfirmedRosterReq.java @@ -0,0 +1,28 @@ +package com.ningdatech.pmapi.meeting.entity.req; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + *

+ * 会议确认名单实体 + *

+ * + * @author WendyYang + * @since 10:43 2022/8/26 + */ +@Data +@ApiModel("会议确认名单实体") +public class ConfirmedRosterReq { + + @NotNull(message = "会议ID不能为空") + @ApiModelProperty("会议ID") + private Long meetingId; + + @ApiModelProperty + private Boolean reconfirmed; + +} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/req/ExpertRemoveReq.java b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/req/ExpertRemoveReq.java index ad08f87..6d5eaf6 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/req/ExpertRemoveReq.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/req/ExpertRemoveReq.java @@ -4,6 +4,8 @@ import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import javax.validation.constraints.NotNull; + /** *

* ExpertRemovePo @@ -13,15 +15,14 @@ import lombok.Data; * @since 08:59 2022/8/10 */ @Data -@ApiModel("专家(移除/替换)实体") +@ApiModel("专家移除实体") public class ExpertRemoveReq { + @NotNull(message = "会议ID不能为空") @ApiModelProperty("会议ID") private Long meetingId; - @ApiModelProperty("专家ID") - private Long expertId; - + @NotNull(message = "专家会议ID不能为空") @ApiModelProperty("专家会议ID") private Long expertMeetingId; diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/req/ExpertReplaceReq.java b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/req/ExpertReplaceReq.java new file mode 100644 index 0000000..ee9327d --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/req/ExpertReplaceReq.java @@ -0,0 +1,28 @@ +package com.ningdatech.pmapi.meeting.entity.req; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + *

+ * ExpertRemovePo + *

+ * + * @author WendyYang + * @since 08:59 2022/8/10 + */ +@Data +@ApiModel("专家替换实体") +public class ExpertReplaceReq { + + @ApiModelProperty("会议ID") + private Long meetingId; + + @ApiModelProperty("专家ID") + private Long expertId; + + @ApiModelProperty("专家会议ID") + private Long expertMeetingId; + +} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/vo/MeetingDetailBasicVO.java b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/vo/MeetingDetailBasicVO.java index b5ed097..7b5d798 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/vo/MeetingDetailBasicVO.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/vo/MeetingDetailBasicVO.java @@ -36,6 +36,8 @@ public class MeetingDetailBasicVO { @ApiModelProperty("会议类型名称") private String typeName; + private String regionCode; + @ApiModelProperty("会议类型代码") private String meetingType; diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/helper/MeetingMsgHelper.java b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/helper/MeetingMsgHelper.java new file mode 100644 index 0000000..43e8188 --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/helper/MeetingMsgHelper.java @@ -0,0 +1,151 @@ +package com.ningdatech.pmapi.meeting.helper; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.ningdatech.basic.util.CollUtils; +import com.ningdatech.pmapi.meeting.constant.MeetingMsgTemplateConst; +import com.ningdatech.pmapi.meeting.entity.domain.Meeting; +import com.ningdatech.pmapi.meeting.entity.domain.MeetingExpert; +import com.ningdatech.pmapi.organization.model.entity.DingEmployeeInfo; +import com.ningdatech.pmapi.organization.model.entity.DingOrganization; +import com.ningdatech.pmapi.organization.service.IDingEmployeeInfoService; +import com.ningdatech.pmapi.organization.service.IDingOrganizationService; +import com.ningdatech.pmapi.staging.enums.MsgTypeEnum; +import com.ningdatech.pmapi.staging.service.INdWorkNoticeStagingService; +import com.ningdatech.pmapi.sys.model.entity.Notify; +import com.ningdatech.pmapi.sys.service.INotifyService; +import com.ningdatech.pmapi.todocenter.bean.entity.WorkNoticeInfo; +import com.ningdatech.pmapi.user.entity.UserInfo; +import com.ningdatech.pmapi.user.service.IUserInfoService; +import com.ningdatech.yxt.model.cmd.SendSmsCmd.SendSmsContext; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

+ * MeetingMsgHelper + *

+ * + * @author WendyYang + * @since 2023/4/20 + **/ +@Component +@AllArgsConstructor +public class MeetingMsgHelper { + + private final IUserInfoService userInfoService; + private final YxtCallOrSmsHelper yxtCallOrSmsHelper; + private final INdWorkNoticeStagingService workNoticeStagingService; + private final IDingEmployeeInfoService dingEmployeeInfoService; + private final IDingOrganizationService dingOrganizationService; + private final INotifyService notifyService; + + private static String officialTime(LocalDateTime time) { + return time.format(DatePattern.NORM_DATETIME_MINUTE_FORMATTER); + } + + private Notify getNotify(Long userId, String msg, MsgTypeEnum type, Map extraPara) { + Notify notify = new Notify(); + notify.setUserId(userId); + notify.setContent(msg); + notify.setType(type.name()); + notify.setReaded(Boolean.FALSE); + notify.setCreateTime(LocalDateTime.now()); + String extraJson = JSON.toJSONString(extraPara); + notify.setExtraInfo(extraJson); + return notify; + } + + private WorkNoticeInfo getSendWorkNoticeInfo(Long accountId) { + WorkNoticeInfo workNoticeInfo = new WorkNoticeInfo(); + workNoticeInfo.setAccountId(accountId); + // 根据浙政钉用户ID获取部门code + DingEmployeeInfo employeeInfo = dingEmployeeInfoService.getOne(Wrappers.lambdaQuery(DingEmployeeInfo.class) + .eq(DingEmployeeInfo::getAccountId, accountId) + .eq(DingEmployeeInfo::getMainJob, String.valueOf(Boolean.TRUE)) + .last("limit 1")); + String organizationCode = employeeInfo.getOrganizationCode(); + workNoticeInfo.setOrganizationCode(organizationCode); + // 根据部门code获取部门名称 + DingOrganization dingOrganization = dingOrganizationService.getOne(Wrappers.lambdaQuery(DingOrganization.class) + .eq(DingOrganization::getOrganizationCode, organizationCode)); + String organizationName = dingOrganization.getOrganizationName(); + workNoticeInfo.setOrganizationName(organizationName); + // 构建唯一的消息ID + String bizMsgId = "ZD_WORK_NOTICE_" + StrUtil.UNDERLINE + organizationCode + StrUtil.UNDERLINE + + organizationName + accountId + StrUtil.UNDERLINE + System.currentTimeMillis(); + workNoticeInfo.setBizMsgId(bizMsgId); + String receiverUserId = String.valueOf(accountId); + workNoticeInfo.setReceiverUserId(receiverUserId); + return workNoticeInfo; + } + + @Transactional(rollbackFor = Exception.class) + public void sendInviteStopMsg(Long userId, Long meetingId, String meetingName) { + UserInfo info = userInfoService.getById(userId); + String msgContent = String.format(MeetingMsgTemplateConst.INVITE_END, meetingName); + // 音信通消息 + SendSmsContext yxtContent = new SendSmsContext(); + yxtContent.setContent(msgContent); + yxtContent.setReceiveNumber(info.getMobile()); + yxtCallOrSmsHelper.sendSms(yxtContent); + // 发送工作通知 + if (info.getAccountId() != null) { + WorkNoticeInfo swn = getSendWorkNoticeInfo(info.getAccountId()); + swn.setMsg(msgContent); + workNoticeStagingService.addByWorkNotice(swn, MsgTypeEnum.REVIEW_MEETING); + Map map = new HashMap<>(); + map.put("meetingId", meetingId); + Notify notify = getNotify(userId, msgContent, MsgTypeEnum.REVIEW_MEETING, map); + notifyService.save(notify); + } + } + + @Transactional(rollbackFor = Exception.class) + public void sendConfirmedRosterMsg(List experts, Meeting meeting) { + List userIds = CollUtils.fieldList(experts, MeetingExpert::getExpertId); + List userInfos = userInfoService.listByIds(userIds); + Map userMap = CollUtils.listToMap(userInfos, UserInfo::getId); + String sTime = officialTime(meeting.getStartTime()); + String eTime = officialTime(meeting.getEndTime()); + String meetingTime = sTime + "至" + eTime; + List yxtContents = new ArrayList<>(); + List notifies = new ArrayList<>(); + List workingNotices = new ArrayList<>(); + experts.forEach(w -> { + String msgContent = String.format(MeetingMsgTemplateConst.CONFIRMED_ROSTER, + w.getExpertName(), officialTime(w.getCreateOn()), meetingTime, meeting.getMeetingAddress(), + meeting.getConnecter(), meeting.getContact()); + // 音信通消息 + SendSmsContext yxtContent = new SendSmsContext(); + yxtContent.setContent(msgContent); + yxtContent.setReceiveNumber(w.getMobile()); + yxtContents.add(yxtContent); + UserInfo info = userMap.get(w.getExpertId()); + // 发送工作通知 + if (info.getAccountId() != null) { + WorkNoticeInfo swn = getSendWorkNoticeInfo(info.getAccountId()); + swn.setMsg(msgContent); + workingNotices.add(swn); + Map map = new HashMap<>(); + map.put("meetingId", meeting.getId()); + Notify notify = getNotify(info.getId(), msgContent, MsgTypeEnum.EXPERT_REVIEW, map); + notifies.add(notify); + } + }); + notifyService.saveBatch(notifies); + yxtCallOrSmsHelper.sendSms(yxtContents); + workNoticeStagingService.addByWorkNotice(workingNotices, MsgTypeEnum.EXPERT_REVIEW); + } + + +} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/ExpertInviteManage.java b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/ExpertInviteManage.java index 1cc38c1..69134e4 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/ExpertInviteManage.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/ExpertInviteManage.java @@ -70,6 +70,9 @@ public class ExpertInviteManage { @Value("#{randomInviteProperties.recentMeetingCount}") private Integer recentMeetingCount; + @Value("#{randomInviteProperties.recentDays}") + private Integer recentDays; + private static final Predicate> COLL_EMPTY = (coll) -> coll != null && coll.isEmpty(); private LambdaQueryWrapper buildBaseExpertQuery() { @@ -83,6 +86,36 @@ public class ExpertInviteManage { .eq(ExpertUserFullInfo::getExpertAccountStatus, ExpertAccountStatusEnum.AVAILABLE.getKey()); } + private void buildAvoidCompanyAndBusinessStrip(LambdaQueryWrapper query, List units, List 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 + * @author WendyYang + **/ + private List 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 randomRule 抽取规则 - * @param appointExpertIds 指定抽取专家ID + * @param invitedExpertIds 指定抽取专家ID * @return 满足抽取条件的专家 * @author WendyYang **/ public ExpertChooseDTO expertInviteByRandomRule(AvoidRuleDTO avoidRule, RandomInviteRuleDTO randomRule, - List appointExpertIds, + List invitedExpertIds, LocalDateTime sTime, LocalDateTime eTime) { ExpertChooseDTO result = new ExpertChooseDTO(new ArrayList<>(), 0); @@ -265,17 +298,19 @@ public class ExpertInviteManage { } boolean avoidExpert = CollUtil.isNotEmpty(avoidRule.getExpertIds()); boolean avoidCompany = CollUtil.isNotEmpty(avoidRule.getAvoidUnitIdList()); - Set tmpAvoidCompany = new HashSet<>(); + Set avoidCompanyUniqCodes = new HashSet<>(); if (avoidCompany) { - tmpAvoidCompany.addAll(avoidRule.getAvoidUnitIdList()); + avoidCompanyUniqCodes.addAll(avoidRule.getAvoidUnitIdList()); + } + if (CollUtil.isNotEmpty(invitedExpertIds)) { + List tmpCompanyUniqCodes = expertUserFullInfoService.listCompanyUniqCodeByUserIds(invitedExpertIds); + avoidCompanyUniqCodes.addAll(tmpCompanyUniqCodes); } // 回避信息 LambdaQueryWrapper 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 expertIdsIn = new HashSet<>(); Set expertIdsNotIn = new HashSet<>(); if (CollUtil.isNotEmpty(merge.getExpertIdsIn())) { @@ -283,6 +318,12 @@ public class ExpertInviteManage { } else if (CollUtil.isNotEmpty(merge.getExpertIdsNotIn())) { expertIdsNotIn.addAll(merge.getExpertIdsNotIn()); } + // 处理回避专家次数 + if (avoidRule.getWeekInviteCount() != null) { + Integer weekInviteCount = avoidRule.getWeekInviteCount(); + List tmpExpertIdsNotIn = listAgreedUserIdByRecentMeetings(weekInviteCount, recentDays, sTime); + expertIdsNotIn.addAll(tmpExpertIdsNotIn); + } // 处理专家层级 addRegionLimit(query, randomRule); if (!expertIdsIn.isEmpty()) { @@ -293,8 +334,8 @@ public class ExpertInviteManage { return result; } } - if (CollUtil.isNotEmpty(appointExpertIds)) { - expertIdsIn.removeIf(appointExpertIds::contains); + if (CollUtil.isNotEmpty(invitedExpertIds)) { + expertIdsIn.removeIf(invitedExpertIds::contains); if (expertIdsIn.isEmpty()) { return result; } @@ -305,8 +346,8 @@ public class ExpertInviteManage { if (expertIdsIn.isEmpty()) { return result; } - } else if (avoidExpert || CollUtil.isNotEmpty(appointExpertIds)) { - Set tmpExpert = expertInviteHelper.getAvoidExpert(appointExpertIds, avoidRule, sTime, eTime); + } else if (avoidExpert || CollUtil.isNotEmpty(invitedExpertIds)) { + Set tmpExpert = expertInviteHelper.getAvoidExpert(invitedExpertIds, avoidRule, sTime, eTime); expertIdsNotIn.addAll(tmpExpert); } else { Set tmpNotInUserIds = expertInviteHelper.listExpertLeaveOrInvited(sTime, eTime); @@ -365,8 +406,14 @@ public class ExpertInviteManage { LambdaQueryWrapper query = buildBaseExpertQuery(); // 设置回避单位 Set 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 tmpExpertIdsNotIn = listAgreedUserIdByRecentMeetings(weekInviteCount, recentDays, msTime); + expertIdsNotIn.addAll(tmpExpertIdsNotIn); + } // 处理专家层级 addRegionLimit(query, randomRule); @@ -403,7 +450,11 @@ public class ExpertInviteManage { List tmpUniqCompanyCodes = CollUtils.fieldList(agreeOrNoticingUserInfos, ExpertUserFullInfo::getCompanyUniqCode); notInCompanyUniqCodeList.addAll(tmpUniqCompanyCodes); } - + // 已请假的专家不再抽取 + List expertsOnLeave = expertGroupByStatus.get(ON_LEAVE); + if (CollUtil.isNotEmpty(expertsOnLeave)) { + expertIdsNotIn.addAll(CollUtils.fieldList(expertsOnLeave, MeetingExpert::getExpertId)); + } // 处理已拒绝专家与重复抽取 BizUtils.notEmpty(expertGroupByStatus.get(REFUSED), refuseExperts -> { List tmpExpertIdsNotIn; diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/MeetingManage.java b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/MeetingManage.java index 4ca317d..a710160 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/MeetingManage.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/MeetingManage.java @@ -36,6 +36,7 @@ import com.ningdatech.pmapi.meeting.entity.req.*; import com.ningdatech.pmapi.meeting.entity.vo.*; import com.ningdatech.pmapi.meeting.helper.ExpertInviteHelper; import com.ningdatech.pmapi.meeting.helper.MeetingManageHelper; +import com.ningdatech.pmapi.meeting.helper.MeetingMsgHelper; import com.ningdatech.pmapi.meeting.helper.YxtCallOrSmsHelper; import com.ningdatech.pmapi.meeting.service.*; import com.ningdatech.pmapi.meeting.task.ExpertInviteTask; @@ -60,6 +61,7 @@ import java.util.function.BiFunction; import java.util.stream.Collectors; import static com.ningdatech.pmapi.meeting.entity.enumeration.ExpertAttendStatusEnum.*; +import static com.ningdatech.pmapi.meeting.entity.enumeration.ExpertInviteTypeEnum.APPOINT; import static com.ningdatech.pmapi.meeting.helper.ExpertInviteHelper.getExpertInviteRule; /** @@ -95,6 +97,7 @@ public class MeetingManage { private final IDingOrganizationService dingOrganizationService; private final IExpertReviewService expertReviewService; private final ExpertInviteHelper expertInviteHelper; + private final MeetingMsgHelper meetingMsgHelper; private static final String INVITED_RULE_CREATE = "INVITED_RULE_CREATE:"; private static final String MEETING_CREATE_KEY = "MEETING_CREATE:"; @@ -199,7 +202,7 @@ public class MeetingManage { } expertInviteTask.cancelByMeetingId(meetingId); LambdaUpdateWrapper meetingUpdate = Wrappers.lambdaUpdate(Meeting.class) - .set(Meeting::getInviteType, ExpertInviteTypeEnum.APPOINT.getCode()) + .set(Meeting::getInviteType, APPOINT.getCode()) .eq(Meeting::getId, meetingId); meetingService.update(meetingUpdate); saveAppointRuleByConvertFromRandomRule(meetingId); @@ -212,7 +215,7 @@ public class MeetingManage { AppointInviteRuleDTO rule = new AppointInviteRuleDTO(); rule.setInviteDesc("转为指定抽取"); rule.setExpertIdList(Collections.emptyList()); - rule.setInviteType(ExpertInviteTypeEnum.APPOINT.getCode()); + rule.setInviteType(APPOINT.getCode()); rule.setCount(0); ExpertInviteRule inviteRule = new ExpertInviteRule(); inviteRule.setMeetingId(meetingId); @@ -410,6 +413,7 @@ public class MeetingManage { Assert.notNull(meeting, "会议不存在"); MeetingDetailBasicVO detail = MeetingDetailBasicVO.builder() .meetingId(meeting.getId()) + .regionCode(meeting.getRegionCode()) .meetingName(meeting.getName()) .meetingType(meeting.getType()) .meetingAddress(meeting.getMeetingAddress()) @@ -481,7 +485,7 @@ public class MeetingManage { item.setRuleId(me.getRuleId()); item.setIsHeadman(me.getIsHeadman()); ExpertInviteRule rule = ruleMap.get(me.getRuleId()); - item.setInviteType(rule == null ? ExpertInviteTypeEnum.APPOINT.getCode() : rule.getInviteType()); + item.setInviteType(rule == null ? APPOINT.getCode() : rule.getInviteType()); if (NOTICING.eq(me.getStatus())) { item.setNoticeStatus("通知中"); } else { @@ -577,6 +581,7 @@ public class MeetingManage { }); AvoidRuleDTO avoidInfo = inviteAvoidRuleService.getAvoidInfoDto(meetingId); AvoidInfoVO vo = new AvoidInfoVO(); + vo.setWeekInviteCount(avoidInfo.getWeekInviteCount()); vo.setAvoidOrgIds(avoidInfo.getAvoidOrgIdList()); vo.setAvoidUnitIds(avoidInfo.getAvoidUnitIdList()); if (CollUtil.isNotEmpty(vo.getAvoidOrgIds())) { @@ -590,7 +595,7 @@ public class MeetingManage { } result.setAvoidInfo(vo); } else { - List appoints = groupByType.get(ExpertInviteTypeEnum.APPOINT); + List appoints = groupByType.get(APPOINT); ExpertInviteRule appoint = appoints.get(0); AppointInviteRuleDTO appointRule = JSON.parseObject(appoint.getInviteRule(), AppointInviteRuleDTO.class); appointRule.setId(appoint.getId()); @@ -610,7 +615,7 @@ public class MeetingManage { } try { Meeting meeting = meetingService.getById(meetingId); - if (!ExpertInviteTypeEnum.APPOINT.eq(meeting.getInviteType())) { + if (!APPOINT.eq(meeting.getInviteType())) { throw BizException.wrap("该会议不能指定邀请专家"); } if (meeting.getConfirmedRoster()) { @@ -713,6 +718,37 @@ public class MeetingManage { } } + @Transactional(rollbackFor = Exception.class) + public void expertRemove(ExpertRemoveReq req) { + String key = "EXPERT_REMOVE:" + req.getExpertMeetingId(); + if (!distributedLock.lock(key, RETRY_TIMES)) { + throw BizException.wrap("删除专家失败,请重试!"); + } + try { + Meeting meeting = meetingService.getById(req.getMeetingId()); + if (MeetingStatusEnum.CANCELED.eq(meeting.getStatus())) { + throw BizException.wrap("会议已取消!"); + } + if (LocalDateTime.now().isAfter(meeting.getStartTime())) { + throw BizException.wrap("会议已开始,不允许移除专家!"); + } + MeetingExpert expert = meetingExpertService.getById(req.getExpertMeetingId()); + if (!APPOINT.eq(expert.getInviteType())) { + throw BizException.wrap("随机抽取的专家不允许移除!"); + } + if (!NOTICING.eq(expert.getStatus())) { + throw BizException.wrap("已确认过的专家不允许移除!"); + } + LambdaUpdateWrapper mUpdate = Wrappers.lambdaUpdate(Meeting.class) + .set(Meeting::getConfirmedRoster, false) + .eq(Meeting::getId, req.getMeetingId()); + meetingService.update(mUpdate); + meetingExpertService.removeById(req.getExpertMeetingId()); + } finally { + distributedLock.releaseLock(key); + } + } + public void releaseExperts(MeetingCancelReq req) { String key = "EXPERT_RELEASE:" + req.getMeetingId(); if (!distributedLock.lock(key, RETRY_TIMES)) { @@ -762,7 +798,8 @@ public class MeetingManage { } } - public void confirmedRoster(Long meetingId) { + public void confirmedRoster(ConfirmedRosterReq req) { + Long meetingId = req.getMeetingId(); String key = "MEETING_RESEND_SMS:" + meetingId; if (!distributedLock.lock(key, RETRY_TIMES)) { throw BizException.wrap("请刷新后重试!"); @@ -780,7 +817,18 @@ public class MeetingManage { meetingService.update(update); } List experts = meetingExpertService.listAgreedExperts(meetingId); - // TODO 发送会议通知 + List expertNoticing = experts.stream() + .filter(w -> meeting.getConfirmedRoster() || !w.getConfirmedRoster()) + .collect(Collectors.toList()); + if (expertNoticing.isEmpty()) { + return; + } + List currConfirmedMeIds = CollUtils.fieldList(expertNoticing, MeetingExpert::getId); + LambdaUpdateWrapper meUpdate = Wrappers.lambdaUpdate(MeetingExpert.class) + .in(MeetingExpert::getId, currConfirmedMeIds) + .set(MeetingExpert::getConfirmedRoster, Boolean.TRUE); + meetingExpertService.update(meUpdate); + meetingMsgHelper.sendConfirmedRosterMsg(expertNoticing, meeting); } finally { distributedLock.releaseLock(key); } diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/mapper/MeetingExpertMapper.java b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/mapper/MeetingExpertMapper.java index e6332e3..d13b407 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/mapper/MeetingExpertMapper.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/mapper/MeetingExpertMapper.java @@ -9,6 +9,7 @@ import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertAttendStatusEnum; 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.List; @@ -62,6 +63,19 @@ public interface MeetingExpertMapper extends BaseMapper { @Param("meetingIds") Collection meetingIds); /** + * 查询时间窗口之内参与会议不超过{@code agreeCount}次的专家ID + * + * @param agreeCount 参与次数(包含) + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 专家ID集合 + * @author WendyYang + **/ + List listAgreeExpertIdByRecentDaysAndAgreeCount(@Param("agreeCount") Integer agreeCount, + @Param("startTime") LocalDateTime startTime, + @Param("endTime") LocalDateTime endTime); + + /** * 根据会议ID与参与状态统计专家数量 * * @param status 会议状态 diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/mapper/MeetingExpertMapper.xml b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/mapper/MeetingExpertMapper.xml index 4dc5475..5f5f1d1 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/mapper/MeetingExpertMapper.xml +++ b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/mapper/MeetingExpertMapper.xml @@ -50,6 +50,15 @@ + +