Преглед на файлове

Merge remote-tracking branch 'origin/master'

tags/24080901
CMM преди 1 година
родител
ревизия
4833dddaed
променени са 66 файла, в които са добавени 1456 реда и са изтрити 228 реда
  1. +5
    -0
      pmapi/pom.xml
  2. +0
    -5
      pmapi/src/main/java/com/ningdatech/pmapi/common/config/ConfigurerAdapter.java
  3. +6
    -5
      pmapi/src/main/java/com/ningdatech/pmapi/common/handler/GlobalResponseHandler.java
  4. +1
    -1
      pmapi/src/main/java/com/ningdatech/pmapi/expert/entity/ExpertUserFullInfo.java
  5. +5
    -2
      pmapi/src/main/java/com/ningdatech/pmapi/expert/helper/PermissionCheckHelper.java
  6. +34
    -4
      pmapi/src/main/java/com/ningdatech/pmapi/expert/manage/ExpertManage.java
  7. +1
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/expert/manage/ExpertReviewManage.java
  8. +3
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/expert/model/entity/ExpertReview.java
  9. +3
    -1
      pmapi/src/main/java/com/ningdatech/pmapi/expert/model/enumeration/ReviewTemplateTypeEnum.java
  10. +4
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/expert/model/req/ExpertReviewDetailReq.java
  11. +50
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/irs/config/IrsSealPlatformProperties.java
  12. +8
    -1
      pmapi/src/main/java/com/ningdatech/pmapi/meeting/controller/MeetingController.java
  13. +45
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/enumeration/MeetingReviewTypeEnum.java
  14. +21
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/req/MeetingOptionProjectReq.java
  15. +3
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/vo/MeetingByManagerVO.java
  16. +1
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/meeting/helper/MeetingManageHelper.java
  17. +3
    -2
      pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/ExpertInviteManage.java
  18. +48
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/MeetingManage.java
  19. +2
    -1
      pmapi/src/main/java/com/ningdatech/pmapi/meeting/task/ExpertInviteTask.java
  20. +27
    -3
      pmapi/src/main/java/com/ningdatech/pmapi/organization/controller/GovBusinessStripController.java
  21. +10
    -9
      pmapi/src/main/java/com/ningdatech/pmapi/organization/entity/GovBusinessStrip.java
  22. +42
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/organization/manage/GovBusinessStripManage.java
  23. +28
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/organization/model/vo/GovBusinessStripTreeVO.java
  24. +113
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/projectdeclared/converter/ApplicationConverter.java
  25. +2
    -2
      pmapi/src/main/java/com/ningdatech/pmapi/projectdeclared/manage/ConstructionPlanManage.java
  26. +22
    -19
      pmapi/src/main/java/com/ningdatech/pmapi/projectdeclared/manage/DeclaredProjectManage.java
  27. +27
    -2
      pmapi/src/main/java/com/ningdatech/pmapi/projectdeclared/manage/DefaultDeclaredProjectManage.java
  28. +4
    -9
      pmapi/src/main/java/com/ningdatech/pmapi/projectdeclared/manage/ProjectAdjustmentManage.java
  29. +1
    -1
      pmapi/src/main/java/com/ningdatech/pmapi/projectdeclared/manage/ReviewByDeptJointManage.java
  30. +31
    -11
      pmapi/src/main/java/com/ningdatech/pmapi/projectdeclared/manage/ReviewByProvincialDeptManage.java
  31. +1
    -1
      pmapi/src/main/java/com/ningdatech/pmapi/projectlib/model/entity/ProjectApplication.java
  32. +9
    -2
      pmapi/src/main/java/com/ningdatech/pmapi/projectlib/model/vo/ProjectLibListItemVO.java
  33. +13
    -5
      pmapi/src/main/java/com/ningdatech/pmapi/provincial/controller/TestController.java
  34. +27
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/provincial/enumeration/ProjectProvincialAuditStatusEnum.java
  35. +8
    -2
      pmapi/src/main/java/com/ningdatech/pmapi/provincial/model/dto/ProvincialApplicationDTO.java
  36. +4
    -3
      pmapi/src/main/java/com/ningdatech/pmapi/provincial/model/dto/ProvincialProjectDTO.java
  37. +36
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/provincial/model/res/ProcessCommentRes.java
  38. +102
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/provincial/model/res/ProvincialApplicationRes.java
  39. +132
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/provincial/model/res/ProvincialProjectRes.java
  40. +3
    -2
      pmapi/src/main/java/com/ningdatech/pmapi/provincial/service/IJoinReviewProvincialBureauService.java
  41. +20
    -9
      pmapi/src/main/java/com/ningdatech/pmapi/provincial/service/impl/JoinReviewProvincialBureauServiceImpl.java
  42. +91
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/scheduler/task/CheckProvincialReviewResultTask.java
  43. +2
    -2
      pmapi/src/main/java/com/ningdatech/pmapi/scheduler/task/ProjectStatusFlowTask.java
  44. +2
    -2
      pmapi/src/main/java/com/ningdatech/pmapi/scheduler/task/WorkNoticeFlowTask.java
  45. +1
    -1
      pmapi/src/main/java/com/ningdatech/pmapi/sms/controller/VerificationCodeController.java
  46. +64
    -59
      pmapi/src/main/java/com/ningdatech/pmapi/sms/manage/SmsManage.java
  47. +2
    -2
      pmapi/src/main/java/com/ningdatech/pmapi/sms/model/po/ReqVerificationCodePO.java
  48. +29
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/sms/task/YxtPollingTask.java
  49. +5
    -3
      pmapi/src/main/java/com/ningdatech/pmapi/sms/utils/SmsRedisKeyUtils.java
  50. +1
    -1
      pmapi/src/main/java/com/ningdatech/pmapi/sys/service/IRegionService.java
  51. +3
    -3
      pmapi/src/main/java/com/ningdatech/pmapi/sys/service/impl/RegionServiceImpl.java
  52. +7
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/controller/UserAuthController.java
  53. +0
    -6
      pmapi/src/main/java/com/ningdatech/pmapi/user/controller/UserInfoController.java
  54. +5
    -38
      pmapi/src/main/java/com/ningdatech/pmapi/user/manage/UserInfoManage.java
  55. +5
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/AuthProperties.java
  56. +6
    -2
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/WebSecurityConfig.java
  57. +74
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/agent/AgentAuthFilter.java
  58. +40
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/agent/AgentAuthProvider.java
  59. +55
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/agent/AgentAuthSecurityConfig.java
  60. +76
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/agent/AgentAuthToken.java
  61. +48
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/agent/AgentLoginUserDetailService.java
  62. +1
    -1
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultLoginSuccessHandler.java
  63. +5
    -2
      pmapi/src/main/java/com/ningdatech/pmapi/user/service/IUserInfoService.java
  64. +17
    -4
      pmapi/src/main/java/com/ningdatech/pmapi/user/service/impl/UserInfoServiceImpl.java
  65. +9
    -0
      pmapi/src/main/resources/application-dev.yml
  66. +3
    -0
      pmapi/src/main/resources/security/auth-dev.yml

+ 5
- 0
pmapi/pom.xml Целия файл

@@ -191,6 +191,11 @@
<artifactId>nd-basic</artifactId>
</dependency>
<dependency>
<groupId>com.ningdatech</groupId>
<artifactId>nd-yxt-starter</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel-core</artifactId>
</dependency>


+ 0
- 5
pmapi/src/main/java/com/ningdatech/pmapi/common/config/ConfigurerAdapter.java Целия файл

@@ -3,15 +3,10 @@ package com.ningdatech.pmapi.common.config;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.nio.charset.StandardCharsets;


+ 6
- 5
pmapi/src/main/java/com/ningdatech/pmapi/common/handler/GlobalResponseHandler.java Целия файл

@@ -1,6 +1,5 @@
package com.ningdatech.pmapi.common.handler;

import cn.hutool.json.JSONUtil;
import com.ningdatech.basic.model.ApiResponse;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
@@ -24,7 +23,9 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
"com.ningdatech.pmapi.sys.controller",
"com.ningdatech.pmapi.todocenter.controller",
"com.ningdatech.pmapi.user.controller",
"com.ningdatech.pmapi.expert.controller"
"com.ningdatech.pmapi.meeting.controller",
"com.ningdatech.pmapi.expert.controller",
"com.ningdatech.pmapi.sms.controller"
})
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {

@@ -44,9 +45,9 @@ public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {
// ConverterType是StringHttpMessageConverter这个类型转换,
// 由于将结果封装成了自定义的ApiResponse类型,所以有ApiResponse转换成String报错
// 所以需要对String类型的返回值单独进行处理
if (o instanceof String) {
return JSONUtil.toJsonStr(apiResponse);
}
// if (o instanceof String) {
// return JSONUtil.toJsonStr(apiResponse);
// }
return ApiResponse.ofSuccess(o);
// return o;
}


+ 1
- 1
pmapi/src/main/java/com/ningdatech/pmapi/expert/entity/ExpertUserFullInfo.java Целия файл

@@ -17,8 +17,8 @@ import java.time.LocalDateTime;
* @author Liuxinxin
* @since 2023-02-22
*/
@TableName("nd_expert_user_full_info")
@Data
@TableName("nd_expert_user_full_info")
@ApiModel(value = "NdExpertUserFullInfo对象", description = "")
public class ExpertUserFullInfo implements Serializable {



+ 5
- 2
pmapi/src/main/java/com/ningdatech/pmapi/expert/helper/PermissionCheckHelper.java Целия файл

@@ -1,5 +1,8 @@
package com.ningdatech.pmapi.expert.helper;

import com.ningdatech.pmapi.user.entity.enumeration.RoleEnum;
import com.ningdatech.pmapi.user.security.auth.model.UserInfoDetails;
import com.ningdatech.pmapi.user.util.LoginUserUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@@ -13,8 +16,8 @@ import org.springframework.stereotype.Component;
public class PermissionCheckHelper {

public boolean isSuperAdmin() {
// TODO
return false;
UserInfoDetails details = LoginUserUtil.loginUserDetail();
return details.getUserRoleList().stream().anyMatch(w -> w.getCode().equals(RoleEnum.SUPER_ADMIN.name()));
}

}

+ 34
- 4
pmapi/src/main/java/com/ningdatech/pmapi/expert/manage/ExpertManage.java Целия файл

@@ -22,11 +22,16 @@ import com.ningdatech.pmapi.expert.service.ExpertInfoService;
import com.ningdatech.pmapi.expert.service.IExpertUserFullInfoService;
import com.ningdatech.pmapi.meta.constant.DictExpertInfoTypeEnum;
import com.ningdatech.pmapi.meta.model.ExpertRegionInfo;
import com.ningdatech.pmapi.user.constant.UserAvailableEnum;
import com.ningdatech.pmapi.user.entity.UserInfo;
import com.ningdatech.pmapi.user.service.IUserInfoService;
import com.ningdatech.pmapi.user.util.LoginUserUtil;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -47,6 +52,7 @@ public class ExpertManage {
private final ExpertInfoCommonHelper expertInfoCommonHelper;
private final FileService fileService;
private final ExpertUserInfoAssembler expertUserInfoAssembler;
private final IUserInfoService iUserInfoService;


/**
@@ -54,10 +60,12 @@ public class ExpertManage {
*
* @param request
*/
@Transactional(rollbackFor = Exception.class)
public void expertBasicInfoSubmit(ExpertUserBasicInfoSubmitRequest request) {
// 用户id
Long userId = LoginUserUtil.getUserId();
ExpertBasicInfo basicInfo = request.getBasicInfo();
Long userId = generateOrGetUserId(basicInfo);

// 校验区域编码合法性 校验履职意向编码合法性
ExpertRegionInfo expertRegionInfo = basicInfo.getExpertRegionInfo();
expertManageHelper.expertRegionInfoCheck(expertRegionInfo);
@@ -90,13 +98,13 @@ public class ExpertManage {
List<DictionaryFieldInfo> recommendedWay = recommendInfo.getRecommendedWay();
// 推荐方式
List<FileBasicInfo> recommendProofFile = recommendInfo.getRecommendationProofFile();
expertRecommendProofSubmit(recommendedWay, recommendProofFile);
expertRecommendProofSubmit(recommendedWay, recommendProofFile, userId);
}


public void expertRecommendProofSubmit(List<DictionaryFieldInfo> recommendedWay, List<FileBasicInfo> recommendProofFile) {
@Transactional(rollbackFor = Exception.class)
public void expertRecommendProofSubmit(List<DictionaryFieldInfo> recommendedWay, List<FileBasicInfo> recommendProofFile, Long expertUserId) {
// 用户id
Long expertUserId = LoginUserUtil.getUserId();
ExpertUserFullInfo expertUserFullInfo = iExpertUserFullInfoService.getByUserId(expertUserId);
// 判断专家状态,是否可以进行证明材料提交
if (Objects.isNull(expertUserFullInfo)
@@ -137,4 +145,26 @@ public class ExpertManage {
List<AttachFileVo> attachFiles = fileService.getByIds(fileIdList);
return expertUserInfoAssembler.buildExpertFullInfoVO(attachFiles, expertUserFullInfoAll);
}

@Transactional(rollbackFor = Exception.class)
public Long generateOrGetUserId(ExpertBasicInfo basicInfo) {
String phoneNo = basicInfo.getPhoneNo();
UserInfo userInfo = iUserInfoService.getUserInfoByPhoneNo(phoneNo);
if (Objects.isNull(userInfo)) {
userInfo = UserInfo.builder()
// .accountId(dingEmployeeInfo.getAccountId())
.username(basicInfo.getName())
.realName(basicInfo.getName())
// .employeeCode(dingEmployeeInfo.getEmployeeCode())
.available(UserAvailableEnum.DISABLE.name())
.mobile(phoneNo)
.createBy(LoginUserUtil.getUserId())
.updateBy(LoginUserUtil.getUserId())
.createOn(LocalDateTime.now())
.updateOn(LocalDateTime.now())
.build();
iUserInfoService.save(userInfo);
}
return userInfo.getId();
}
}

+ 1
- 0
pmapi/src/main/java/com/ningdatech/pmapi/expert/manage/ExpertReviewManage.java Целия файл

@@ -79,6 +79,7 @@ public class ExpertReviewManage {
review.setContent(JSONUtil.toJsonStr(req.getReviewTemplateOptions()));
review.setProjectId(req.getProjectId());
review.setTemplateId(req.getTemplateId());
review.setMeetingId(req.getMeetingId());
review.setOtherAdvice(req.getOtherAdvice());
review.setAttachFileId(req.getAttachFileId());
review.setIsFinal(req.getIsFinal());


+ 3
- 0
pmapi/src/main/java/com/ningdatech/pmapi/expert/model/entity/ExpertReview.java Целия файл

@@ -30,6 +30,9 @@ public class ExpertReview implements Serializable {
@ApiModelProperty("项目ID")
private Long projectId;

@ApiModelProperty("会议ID")
private Long meetingId;

@ApiModelProperty("评审模版配置ID")
private Long templateId;



+ 3
- 1
pmapi/src/main/java/com/ningdatech/pmapi/expert/model/enumeration/ReviewTemplateTypeEnum.java Целия файл

@@ -21,7 +21,9 @@ public enum ReviewTemplateTypeEnum {

CONSTRUCTION_SCHEME_REVIEW("建设方案评审", 2),

ACCEPTANCE_SCHEME_REVIEW("验收方案评审", 3);
ACCEPTANCE_SCHEME_REVIEW("验收方案评审", 3),

DEPT_JOIN_REVIEW("部门联审", 4);


private final String value;


+ 4
- 0
pmapi/src/main/java/com/ningdatech/pmapi/expert/model/req/ExpertReviewDetailReq.java Целия файл

@@ -28,6 +28,10 @@ public class ExpertReviewDetailReq {
@NotNull(message = "项目ID不能为空")
private Long projectId;

@ApiModelProperty("会议ID")
@NotNull(message = "会议ID不能为空")
private Long meetingId;

@Valid
@ApiModelProperty("配置模版")
@NotEmpty(message = "配置不能为空")


+ 50
- 0
pmapi/src/main/java/com/ningdatech/pmapi/irs/config/IrsSealPlatformProperties.java Целия файл

@@ -0,0 +1,50 @@
package com.ningdatech.pmapi.irs.config;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
* @author liuxinxin
* @date 2023/3/16 下午6:03
*/

@Component
@Data
public class IrsSealPlatformProperties {
public static String projectId;

public static String projectSecret;

public static String accessKey;

public static String secretKey;

public static String apiUrl;

@Value("${irs.seal-platform.project-id}")
public void setProjectId(String projectId) {
IrsSealPlatformProperties.projectId = projectId;
}

@Value("${irs.seal-platform.project-secret}")
public void setProjectSecret(String projectSecret) {
IrsSealPlatformProperties.projectSecret = projectSecret;
}

@Value("${irs.seal-platform.access-key}")
public void setAccessKey(String accessKey) {
IrsSealPlatformProperties.accessKey = accessKey;
}

@Value("${irs.seal-platform.secret-key}")
public void setSecretKey(String secretKey) {
IrsSealPlatformProperties.secretKey = secretKey;
}

@Value("${irs.seal-platform.api-url}")
public void setApiUrl(String apiUrl) {
IrsSealPlatformProperties.apiUrl = apiUrl;
}

}

+ 8
- 1
pmapi/src/main/java/com/ningdatech/pmapi/meeting/controller/MeetingController.java Целия файл

@@ -8,6 +8,7 @@ import com.ningdatech.pmapi.meeting.entity.dto.ReviewProjectDTO;
import com.ningdatech.pmapi.meeting.entity.req.*;
import com.ningdatech.pmapi.meeting.entity.vo.*;
import com.ningdatech.pmapi.meeting.manage.MeetingManage;
import com.ningdatech.pmapi.projectlib.model.vo.ProjectLibListItemVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
@@ -168,8 +169,14 @@ public class MeetingController {

@GetMapping("/listReviewProject")
@ApiOperation("评审会议列表")
public PageVo<ReviewProjectDTO> listReviewProject(ReviewProjectListReq req){
public PageVo<ReviewProjectDTO> listReviewProject(ReviewProjectListReq req) {
return meetingManage.pageReviewProject(req);
}

@GetMapping("/option/project")
@ApiOperation("项目列表(创建会议添加项目)")
public PageVo<ProjectLibListItemVO> projectList(MeetingOptionProjectReq req) {
return meetingManage.optionProject(req);
}

}

+ 45
- 0
pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/enumeration/MeetingReviewTypeEnum.java Целия файл

@@ -0,0 +1,45 @@
package com.ningdatech.pmapi.meeting.entity.enumeration;

import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Arrays;

/**
* <p>
* MeetingReviewTypeEnum
* </p>
*
* @author WendyYang
* @since 16:32 2023/3/15
*/
@Getter
@AllArgsConstructor
public enum MeetingReviewTypeEnum {

/**
* 会议评审类型
*/
PRELIMINARY_SCHEME_REVIEW("初步方案评审", "1"),

CONSTRUCTION_SCHEME_REVIEW("建设方案评审", "2"),

ACCEPTANCE_SCHEME_REVIEW("验收方案评审", "3"),

DEPT_JOIN_REVIEW("部门联审", "4");

private final String value;
private final String code;

public boolean eq(String code) {
return this.getCode().equals(code);
}

public static MeetingReviewTypeEnum getByCode(String code) {
return Arrays.stream(values())
.filter(w -> w.getCode().equals(code))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("无效的会议评审类型编码"));
}

}

+ 21
- 0
pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/req/MeetingOptionProjectReq.java Целия файл

@@ -0,0 +1,21 @@
package com.ningdatech.pmapi.meeting.entity.req;

import com.ningdatech.basic.model.PagePo;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
* <p>
* MeetingOptionProjectReq
* </p>
*
* @author WendyYang
* @since 11:50 2023/3/16
*/
@Data
@EqualsAndHashCode
public class MeetingOptionProjectReq extends PagePo {

private String meetingType;

}

+ 3
- 0
pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/vo/MeetingByManagerVO.java Целия файл

@@ -61,4 +61,7 @@ public class MeetingByManagerVO {
@ApiModelProperty("专家状态")
private Integer expertStatus;

@ApiModelProperty("创建时间")
private LocalDateTime createOn;

}

+ 1
- 0
pmapi/src/main/java/com/ningdatech/pmapi/meeting/helper/MeetingManageHelper.java Целия файл

@@ -118,6 +118,7 @@ public class MeetingManageHelper {
.confirmedRoster(meeting.getConfirmedRoster())
.inviteStatus(meeting.getInviteStatus())
.status(meeting.getStatus())
.createOn(meeting.getCreateOn())
.build();
}



+ 3
- 2
pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/ExpertInviteManage.java Целия файл

@@ -77,6 +77,7 @@ public class ExpertInviteManage {
.select(ExpertUserFullInfo::getUserId,
ExpertUserFullInfo::getId,
ExpertUserFullInfo::getCompany,
ExpertUserFullInfo::getExpertName,
ExpertUserFullInfo::getPhoneNo)
.eq(ExpertUserFullInfo::getExpertAccountStatus, ExpertAccountStatusEnum.AVAILABLE.getKey());
}
@@ -281,7 +282,7 @@ public class ExpertInviteManage {
LambdaQueryWrapper<ExpertUserFullInfo> query = buildBaseExpertQuery();
query.notIn(!tmpAvoidCompany.isEmpty(), ExpertUserFullInfo::getCompany, tmpAvoidCompany);
if (avoidCompany) {
query.notExists("select 1 from expert_avoid_company eac where eac.user_id = expert_user_full_info.user_id" +
query.notExists("select 1 from expert_avoid_company eac where eac.user_id = nd_expert_user_full_info.user_id" +
" and company_name in ({0})", CollUtils.joinByComma(avoidRule.getAvoidUnitIdList()));
}
// 处理专家层级
@@ -357,7 +358,7 @@ public class ExpertInviteManage {
}
LambdaQueryWrapper<ExpertUserFullInfo> query = buildBaseExpertQuery();
query.notIn(ExpertUserFullInfo::getCompany, avoidRule.getAvoidUnitIdList());
query.notExists("select 1 from expert_avoid_company eac where eac.user_id = expert_user_full_info.user_id" +
query.notExists("select 1 from expert_avoid_company eac where eac.user_id = nd_expert_user_full_info.user_id" +
" and company_name in ({0})", CollUtils.joinByComma(avoidRule.getAvoidOrgIdList()));

// 处理专家层级


+ 48
- 0
pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/MeetingManage.java Целия файл

@@ -25,6 +25,7 @@ import com.ningdatech.pmapi.meeting.entity.domain.*;
import com.ningdatech.pmapi.meeting.entity.dto.*;
import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertAttendStatusEnum;
import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertInviteTypeEnum;
import com.ningdatech.pmapi.meeting.entity.enumeration.MeetingReviewTypeEnum;
import com.ningdatech.pmapi.meeting.entity.enumeration.MeetingStatusEnum;
import com.ningdatech.pmapi.meeting.entity.req.*;
import com.ningdatech.pmapi.meeting.entity.vo.*;
@@ -35,7 +36,9 @@ import com.ningdatech.pmapi.meeting.service.*;
import com.ningdatech.pmapi.meeting.task.ExpertInviteTask;
import com.ningdatech.pmapi.meta.helper.DictionaryCache;
import com.ningdatech.pmapi.meta.helper.TagCache;
import com.ningdatech.pmapi.projectlib.enumeration.ProjectStatusEnum;
import com.ningdatech.pmapi.projectlib.model.entity.Project;
import com.ningdatech.pmapi.projectlib.model.vo.ProjectLibListItemVO;
import com.ningdatech.pmapi.projectlib.service.IProjectService;
import com.ningdatech.pmapi.sys.model.dto.RegionDTO;
import com.ningdatech.pmapi.user.security.auth.model.UserInfoDetails;
@@ -702,4 +705,49 @@ public class MeetingManage {
return PageVo.of(page.getRecords(), page.getTotal());
}

public PageVo<ProjectLibListItemVO> optionProject(MeetingOptionProjectReq req) {
String meetingType = req.getMeetingType();
LambdaQueryWrapper<Project> query = Wrappers.lambdaQuery(Project.class);
switch (MeetingReviewTypeEnum.getByCode(meetingType)) {
case PRELIMINARY_SCHEME_REVIEW:
buildOptionProjectQuery(query, meetingType, ProjectStatusEnum.PRE_APPLYING);
break;
case CONSTRUCTION_SCHEME_REVIEW:
buildOptionProjectQuery(query, meetingType, ProjectStatusEnum.SCHEME_UNDER_REVIEW);
break;
case ACCEPTANCE_SCHEME_REVIEW:
query.eq(Project::getStatus, ProjectStatusEnum.FINAL_ACCEPTANCE_IS_UNDER_REVIEW);
break;
case DEPT_JOIN_REVIEW:
query.eq(Project::getStatus, ProjectStatusEnum.DEPARTMENT_JOINT_REVIEW);
break;
default:
return PageVo.empty();
}
Page<Project> page = projectService.page(req.page(), query);
PageVo<ProjectLibListItemVO> result = PageVo.of(null, page.getTotal());
if (result.getTotal() > 0) {
List<ProjectLibListItemVO> projects = CollUtils.convert(page.getRecords(),
w -> ProjectLibListItemVO
.builder()
.id(w.getId())
.projectName(w.getProjectName())
.declaredAmount(w.getDeclareAmount())
.projectType(w.getProjectType())
.projectYear(w.getProjectYear())
.buildOrg(w.getBuildOrgName())
.build());
result.setRecords(projects);
}
return result;
}

private void buildOptionProjectQuery(LambdaQueryWrapper<Project> query, String meetingType, ProjectStatusEnum status) {
String sql = String.format("select 1 from meeting m inner join meeting_inner_project mip on" +
" m.is_inner_project = true and m.id = mip.meeting_id and m.type = %s and m.status != 3", meetingType);
query.eq(Project::getStatus, status.getCode());
query.and(q1 -> q1.notExists(sql)).or(q2 -> q2.exists(sql + " inner join nd_expert_review ner " +
"on ner.meeting_id = m.id and ner.is_final = true and review_result in (2, 3)"));
}

}

+ 2
- 1
pmapi/src/main/java/com/ningdatech/pmapi/meeting/task/ExpertInviteTask.java Целия файл

@@ -87,7 +87,8 @@ public class ExpertInviteTask {
}

private CacheHashKey getCacheKey(Long meetingId) {
return new CacheHashKey(MEETING_ID_INVITE_RANDOM, meetingId, EXPIRE_TIME);
String meetingIdStr = meetingId == null ? null : meetingId.toString();
return new CacheHashKey(MEETING_ID_INVITE_RANDOM, meetingIdStr, EXPIRE_TIME);
}

@PostConstruct


+ 27
- 3
pmapi/src/main/java/com/ningdatech/pmapi/organization/controller/GovBusinessStripController.java Целия файл

@@ -1,20 +1,44 @@
package com.ningdatech.pmapi.organization.controller;


import com.ningdatech.pmapi.organization.manage.GovBusinessStripManage;
import com.ningdatech.pmapi.organization.model.vo.GovBusinessStripTreeVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import org.springframework.stereotype.Controller;
import java.util.List;

/**
* <p>
* 前端控制器
* 前端控制器
* </p>
*
* @author Liuxinxin
* @since 2023-03-08
*/
@Controller
@RequestMapping("/pmapi.organization/gov-business-strip")
@Slf4j
@Validated
@RestController
@RequiredArgsConstructor
@Api(value = "GovBusinessStripController", tags = "条线管理")
@RequestMapping("/api/v1/gov-business-strip")
public class GovBusinessStripController {

private final GovBusinessStripManage govBusinessStripManage;

@GetMapping("/get-child-list")
@ApiOperation("获取条线标签的的树状结构")
public List<GovBusinessStripTreeVO> getChildOrganizationList(@RequestParam(value = "parentCode", required = false) String parentCode) {
return govBusinessStripManage.getChildOrganizationList(parentCode);
}

}

+ 10
- 9
pmapi/src/main/java/com/ningdatech/pmapi/organization/entity/GovBusinessStrip.java Целия файл

@@ -5,7 +5,6 @@ import io.swagger.annotations.ApiModel;
import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
* <p>
@@ -24,14 +23,6 @@ public class GovBusinessStrip implements Serializable {

private Long id;

private LocalDateTime createOn;

private LocalDateTime updateOn;

private Long createBy;

private Long updateBy;

/**
* 条线code
*/
@@ -41,4 +32,14 @@ public class GovBusinessStrip implements Serializable {
* 条线名称
*/
private String businessStripName;

/**
* 父级条线code
*/
private String parentCode;

/**
* 父级条线名称
*/
private String parentName;
}

+ 42
- 0
pmapi/src/main/java/com/ningdatech/pmapi/organization/manage/GovBusinessStripManage.java Целия файл

@@ -0,0 +1,42 @@
package com.ningdatech.pmapi.organization.manage;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ningdatech.pmapi.organization.entity.GovBusinessStrip;
import com.ningdatech.pmapi.organization.model.vo.GovBusinessStripTreeVO;
import com.ningdatech.pmapi.organization.service.IGovBusinessStripService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
* @author liuxinxin
* @date 2023/3/14 下午4:48
*/

@Component
@RequiredArgsConstructor
public class GovBusinessStripManage {

private final IGovBusinessStripService iGovBusinessStripService;


public List<GovBusinessStripTreeVO> getChildOrganizationList(String parentCode) {
if (Objects.isNull(parentCode)) {
parentCode = "-1";
}
List<GovBusinessStrip> govBusinessStripList = iGovBusinessStripService.list(Wrappers.lambdaQuery(GovBusinessStrip.class)
.eq(GovBusinessStrip::getParentCode, parentCode));

return govBusinessStripList.stream().map(r -> {
GovBusinessStripTreeVO govBusinessStripTreeVO = new GovBusinessStripTreeVO();
govBusinessStripTreeVO.setBusinessStripCode(r.getBusinessStripCode());
govBusinessStripTreeVO.setBusinessStripName(r.getBusinessStripName());
govBusinessStripTreeVO.setParentCode(r.getParentCode());
govBusinessStripTreeVO.setParentName(r.getParentName());
return govBusinessStripTreeVO;
}).collect(Collectors.toList());
}
}

+ 28
- 0
pmapi/src/main/java/com/ningdatech/pmapi/organization/model/vo/GovBusinessStripTreeVO.java Целия файл

@@ -0,0 +1,28 @@
package com.ningdatech.pmapi.organization.model.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
* @author Liuxinxin
* @since 2023-03-08
*/
@Data
@ApiModel(value = "条线树形 responseVO", description = "")
public class GovBusinessStripTreeVO {

private Long id;

@ApiModelProperty("条线code")
private String businessStripCode;

@ApiModelProperty("条线名称")
private String businessStripName;

@ApiModelProperty("父级条线code")
private String parentCode;

@ApiModelProperty("父级条线名称")
private String parentName;
}

+ 113
- 0
pmapi/src/main/java/com/ningdatech/pmapi/projectdeclared/converter/ApplicationConverter.java Целия файл

@@ -0,0 +1,113 @@
package com.ningdatech.pmapi.projectdeclared.converter;

import cn.hutool.core.collection.CollUtil;
import com.ningdatech.pmapi.projectlib.model.entity.Project;
import com.ningdatech.pmapi.projectlib.model.entity.ProjectApplication;
import com.ningdatech.pmapi.provincial.model.dto.ProvincialApplicationDTO;
import com.ningdatech.pmapi.provincial.model.dto.ProvincialProjectDTO;
import org.assertj.core.util.Lists;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
* @Classname ApplicationConverter
* @Description
* @Date 2023/3/13 17:38
* @Author PoffyZhang
*/
public class ApplicationConverter {

public static ProvincialProjectDTO convertProject(Project projectInfo,List<ProjectApplication> applications) {
return ProvincialProjectDTO.builder()
.regionCode(projectInfo.getAreaCode())
.regionName(projectInfo.getArea())
.projectName(projectInfo.getProjectName())
.projectId(String.valueOf(projectInfo.getId()))
.projectType(projectInfo.getProjectType())
.totalMoney(projectInfo.getDeclareAmount())
.yearBudget(projectInfo.getAnnualPlanAmount())
.budgetFrom(projectInfo.getDeclareHaveAmount() + "," +
projectInfo.getDeclareGovOwnFinanceAmount() + ","
+ projectInfo.getDeclareGovSuperiorFinanceAmount())
.year(String.valueOf(projectInfo.getProjectYear()))
.financialCode(projectInfo.getFinancialCode())
.developCode(projectInfo.getDevelopCode())
.beginTime(projectInfo.getBeginTime())
.endTime(projectInfo.getEndTime())
.buildBasis("立项依据")
.buildBasisFile(projectInfo.getBuildBasis())
.projectSummary(projectInfo.getProjectIntroduction())
.responsibleMan(projectInfo.getResponsibleMan())
.responsibleManPhone(projectInfo.getResponsibleManMobile())
.contactName(projectInfo.getContactName())
.contactPhone(projectInfo.getContactPhone())
.buildUnit(projectInfo.getBuildOrgName())
.buildUnitCode(projectInfo.getBuildOrgCode())
.superUnit(projectInfo.getSuperOrg())
.superUnitCode(projectInfo.getSuperOrgCode())
.projectApplyFile(projectInfo.getProjectPdf())
.projectEstimateFile(projectInfo.getCalculationTotalInvestmentFile())
.unitThreePlan(projectInfo.getMainResponsibilitiesApplicantFile())
.otherFile(projectInfo.getPreliminaryPlanFile())
.projectRemark(projectInfo.getProjectRemarks())
.includeApplication(projectInfo.getIncludeApplication())
.applicationInfo(convertApplications(applications))
.build();
}

//放入项目 app
private static List<ProvincialApplicationDTO> convertApplications(List<ProjectApplication> applications) {
if(CollUtil.isEmpty(applications)){
Collections.emptyList();
}

return applications.stream().map(ApplicationConverter::convertApp).collect(Collectors.toList());
}

private static ProvincialApplicationDTO convertApp(ProjectApplication projectApplication) {
return ProvincialApplicationDTO.builder()
.clouds(convertCloud(projectApplication))
.isFirst(projectApplication.getIsFirst())
.applicationName(projectApplication.getApplicationName())
.applicationCode(projectApplication.getRelatedExistsApplicationCode())
.relatedExistsApplication(projectApplication.getRelatedExistsApplication())
.applicationType(2)
.buildLevel(projectApplication.getBuildLevel())
.isUniteBuild(projectApplication.getIsUniteBuild())
.unionBuildKind(projectApplication.getUnionBuildKind())
.applicationSummary(projectApplication.getApplicationSummary())
.applicationRemark(projectApplication.getApplicationRemark())
.applicationEstimateFile(projectApplication.getApplicationEstimateFile())
.isFiveDomain(projectApplication.getIsDigitalModification())
.fiveDomain(projectApplication.getDigitalModification())
.bizDomain(projectApplication.getBizDomain())
.isBizCooperate(projectApplication.getIsBizCooperate())
.userRange(projectApplication.getUsesRangeRemark())
.useGovCloud(projectApplication.getUseGovCloud())
.nationalITSpec(projectApplication.getNationalItSpec())
.netEnv(String.valueOf(projectApplication.getNetEnv()))
.secrecyGrade(projectApplication.getSecrecyGrade())
.passwordGrade(projectApplication.getPasswordGrade())
.accountAppName(projectApplication.getAccountAppName())
.brainAccountAppName(projectApplication.getDomainBrainAccount())
.useCommonData(projectApplication.getUseCommonData())
.dataName(projectApplication.getDataName())
.commonComponents(projectApplication.getCommonComponents())
.useCommonComponent(projectApplication.getUseCommonComponent())
.isProduceCommonComponent(projectApplication.getProduceCommonComponent())
.produceCommonComponent(projectApplication.getProduceCommonComponents())
.publishSide(projectApplication.getPublishSide())
.build();
}

private static List<ProvincialApplicationDTO.Cloud> convertCloud(ProjectApplication projectApplication) {
return Lists.newArrayList(ProvincialApplicationDTO.Cloud.builder()
.cloudType(projectApplication.getCloudsType())
.cloudNums(projectApplication.getCloudsNumber())
.cloudBasicSpec(projectApplication.getCloudsFoundationSpecifications())
.cloudUseDescription(projectApplication.getCloudsDescription())
.build());
}

}

+ 2
- 2
pmapi/src/main/java/com/ningdatech/pmapi/projectdeclared/manage/ConstructionPlanManage.java Целия файл

@@ -102,7 +102,7 @@ public class ConstructionPlanManage {
VUtils.isTrue(Objects.isNull(projectDto.getId())).throwMessage("提交失败 缺少项目ID!");
Project projectInfo = projectService.getById(projectDto.getId());
VUtils.isTrue(Objects.isNull(projectInfo)).throwMessage("提交失败 此项目不存在!");
VUtils.isTrue(StringUtils.isBlank(projectInfo.getConstructionPlanFile())).throwMessage("提交失败 请提交建设方案!");
VUtils.isTrue(StringUtils.isBlank(projectDto.getConstructionPlanFile())).throwMessage("提交失败 请提交建设方案!");

String regionCode = projectInfo.getAreaCode();

@@ -133,7 +133,7 @@ public class ConstructionPlanManage {
);
params.setFormData(dto.getFormData());
// 获取发起单位、发起单位主管单位、发起单位上级条线主管单位信息
Map<String, OrgInfoDTO> orgModelMap = defaultDeclaredProjectManage.getOrgModelInfo(userId,projectInfo);
Map<String, OrgInfoDTO> orgModelMap = defaultDeclaredProjectManage.buildOrgModelMap(userId,projectInfo);
String instanceId = processService.newStartProcess(model.getProcessDefId(),model.getFormId(), params,orgModelMap);
log.info("建设方案项目申报成功 【{}】", instanceId);



+ 22
- 19
pmapi/src/main/java/com/ningdatech/pmapi/projectdeclared/manage/DeclaredProjectManage.java Целия файл

@@ -53,6 +53,8 @@ import com.wflow.workflow.bean.dto.OrgInfoDTO;
import com.wflow.workflow.bean.vo.ProcessStartParamsVo;
import com.wflow.workflow.service.ProcessInstanceService;
import com.wflow.workflow.service.ProcessModelService;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@@ -64,6 +66,7 @@ import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
@@ -107,8 +110,7 @@ public class DeclaredProjectManage {
public String startTheProcess(DefaultDeclaredDTO dto) {
UserInfoDetails userInfoDetails = LoginUserUtil.loginUserDetail();
Long userId = userInfoDetails.getUserId();
VUtils.isTrue(Objects.isNull(userInfoDetails) ||Objects.isNull(userInfoDetails.getUserId()))
.throwMessage("获取登录用户失败!");
VUtils.isTrue(Objects.isNull(userId)).throwMessage("获取登录用户失败!");

ProjectDTO projectInfo = dto.getProjectInfo();
projectInfo.setAreaCode(userInfoDetails.getRegionCode());
@@ -116,6 +118,11 @@ public class DeclaredProjectManage {
projectInfo.setBuildOrgCode(userInfoDetails.getOrganizationCode());
projectInfo.setBuildOrgName(userInfoDetails.getOrganizationName());

//项目名称去重
defaultDeclaredProjectManage.checkDuplication(projectInfo);
//判断申报金额 是否等于总的 判断年度支付金额 是否等于总金额
defaultDeclaredProjectManage.checkAmount(projectInfo);

//如果主管单位没有 那么主管单位就是自己
if(CommonEnum.NO.getCode().equals(projectInfo.getIsSuperOrg())){
projectInfo.setSuperOrgCode(userInfoDetails.getOrganizationCode());
@@ -147,9 +154,6 @@ public class DeclaredProjectManage {
throw new BusinessException(String.format("此 【%s】区域找不到单位流程配置", regionCode));
}

//项目名称去重
defaultDeclaredProjectManage.checkDuplication(projectInfo);

ProcessStartParamsVo params = new ProcessStartParamsVo();
params.setUser(defaultDeclaredProjectManage.buildUser(userId));
params.setProcessUsers(Collections.emptyMap());
@@ -165,7 +169,7 @@ public class DeclaredProjectManage {
// 获取发起单位、发起单位主管单位、发起单位上级主管条线单位信息
Project project = new Project();
BeanUtils.copyProperties(projectInfo,project);
Map<String, OrgInfoDTO> orgModelMap = defaultDeclaredProjectManage.getOrgModelInfo(userId,project);
Map<String, OrgInfoDTO> orgModelMap = defaultDeclaredProjectManage.buildOrgModelMap(userId,project);
String instanceId = processService.newStartProcess(model.getProcessDefId(),model.getFormId(), params,orgModelMap);
log.info("申报项目成功 【{}】", instanceId);

@@ -201,6 +205,11 @@ public class DeclaredProjectManage {
Project projectInfo = projectService.getById(projectDto.getId());
VUtils.isTrue(Objects.isNull(projectInfo)).throwMessage("提交失败 此项目不存在!");

//项目名称去重
defaultDeclaredProjectManage.checkDuplication(projectDto);
//判断申报金额 是否等于总的 判断年度支付金额 是否等于总金额
defaultDeclaredProjectManage.checkAmount(projectDto);

String regionCode = projectInfo.getAreaCode();
WflowModels model = processModelService.getOne(Wrappers.lambdaQuery(WflowModels.class)
.eq(WflowModels::getRegionCode, regionCode)
@@ -231,7 +240,7 @@ public class DeclaredProjectManage {
// 获取发起单位、发起单位主管单位、发起单位上级主管条线单位信息
Project project = new Project();
BeanUtils.copyProperties(projectInfo,project);
Map<String, OrgInfoDTO> orgModelMap = defaultDeclaredProjectManage.getOrgModelInfo(userId,project);
Map<String, OrgInfoDTO> orgModelMap = defaultDeclaredProjectManage.buildOrgModelMap(userId,project);
String instanceId = processService.newStartProcess(model.getProcessDefId(),model.getFormId(), params,orgModelMap);
log.info("重新申报项目成功 【{}】", instanceId);

@@ -375,19 +384,15 @@ public class DeclaredProjectManage {
}

public void exportList(HttpServletResponse response, ProjectListReq param) {
Long userId = LoginUserUtil.getUserId();
UserInfoDetails userInfoDetails = LoginUserUtil.loginUserDetail();
Long userId = userInfoDetails.getUserId();
VUtils.isTrue(Objects.isNull(userId)).throwMessage("获取登录用户失败!");
UserFullInfoDTO userFullInfo = userInfoHelper.getUserFullInfo(userId);
//放入用户的单位
param.setBuildOrgCode(userFullInfo.getOrganizationCode());
param.setPageNumber(CommonConst.EXPORT_PAGE_NUMBER);
param.setPageSize(CommonConst.EXPORT_PAGE_SIZE);

param.setBuildOrgCode(userInfoDetails.getOrganizationCode());
LambdaQueryWrapper<Project> query = ProjectHelper.projectQuery(param);
Page<Project> page = projectService.page(param.page(), query);
List<Project> records = page.getRecords();

List<Project> records = projectService.list(query);

AtomicInteger serialNumber = new AtomicInteger(0);
List<DeclaredProjectExportDTO> collect = records.stream().map(r -> {
DeclaredProjectExportDTO exportDTO = new DeclaredProjectExportDTO();
BeanUtils.copyProperties(r, exportDTO);
@@ -395,11 +400,9 @@ public class DeclaredProjectManage {
exportDTO.setStatusName(ProjectStatusEnum.getDesc(r.getStatus()));
String createOnStr = NdDateUtils.format(r.getCreateOn(), "yyyy-MM-dd HH:mm");
exportDTO.setCreateOn(createOnStr);
exportDTO.setSerialNumber(serialNumber.incrementAndGet());
return exportDTO;
}).collect(Collectors.toList());
for (int i = 0; i < collect.size(); i++) {
collect.get(i).setSerialNumber(i + 1);
}
String fileName = "项目申报列表";
ExcelDownUtil.setFileName(fileName,response);
//数据导出处理函数


+ 27
- 2
pmapi/src/main/java/com/ningdatech/pmapi/projectdeclared/manage/DefaultDeclaredProjectManage.java Целия файл

@@ -34,6 +34,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@@ -73,6 +74,30 @@ public class DefaultDeclaredProjectManage {
.throwMessage(String.format("修改失败 此项目名 【%s】 已存在!",project.getProjectName()));
}

public void checkAmount(ProjectDTO projectInfo) {
BigDecimal declareAmount = projectInfo.getDeclareAmount();
BigDecimal govSuperFinanceAmount = projectInfo.getDeclareGovSuperiorFinanceAmount();
BigDecimal govOwnFinanceAmount = projectInfo.getDeclareGovOwnFinanceAmount();
BigDecimal bankLendingAmount = projectInfo.getDeclareBankLendingAmount();
BigDecimal haveAmount = projectInfo.getDeclareHaveAmount();
BigDecimal otherAmount = projectInfo.getDeclareOtherAmount();
BigDecimal totalAmount = govSuperFinanceAmount
.add(govOwnFinanceAmount).add(bankLendingAmount).add(haveAmount).add(otherAmount);
VUtils.isTrue(declareAmount.compareTo(totalAmount) != 0)
.throwMessage(String.format("申报失败! 申报总金额【{}】 不等于其它申报金额 【{}】",declareAmount,totalAmount));

BigDecimal annualPlanAmount = projectInfo.getAnnualPlanAmount();
BigDecimal annualGovSuperAmount = projectInfo.getAnnualPlanGovSuperiorFinanceAmount();
BigDecimal annualOwnSuperAmount = projectInfo.getAnnualPlanGovOwnFinanceAmount();
BigDecimal annualBankLendingAmount = projectInfo.getAnnualPlanBankLendingAmount();
BigDecimal annualHaveAmount = projectInfo.getAnnualPlanHaveAmount();
BigDecimal anualOtherAmount = projectInfo.getAnnualPlanOtherAmount();
BigDecimal totalAnnual = annualGovSuperAmount
.add(annualOwnSuperAmount).add(annualBankLendingAmount).add(annualHaveAmount).add(anualOtherAmount);
VUtils.isTrue(annualPlanAmount.compareTo(totalAnnual) != 0)
.throwMessage(String.format("申报失败! 年度支付总金额【{}】 不等于其它年度支付金额 【{}】",annualPlanAmount,totalAnnual));
}

public ProcessInstanceUserDto buildUser(Long userId){
UserFullInfoDTO userFullInfo = userInfoHelper.getUserFullInfo(userId);

@@ -87,7 +112,7 @@ public class DefaultDeclaredProjectManage {
.build();
}

public Map<String, OrgInfoDTO> getOrgModelInfo(Long userId,Project project) {
public Map<String, OrgInfoDTO> buildOrgModelMap(Long userId,Project project) {
Map<String, OrgInfoDTO> orgMap = new HashMap<>();

// 查出所有的单位流程配置
@@ -168,7 +193,7 @@ public class DefaultDeclaredProjectManage {
}

// 获取发起单位、发起单位主管单位、发起单位上级主管条线单位信息
Map<String, OrgInfoDTO> orgModelMap = getOrgModelInfo(userId,projectInfo);
Map<String, OrgInfoDTO> orgModelMap = buildOrgModelMap(userId,projectInfo);
String instanceId = processService.newStartProcess(model.getProcessDefId(),model.getFormId(), params,orgModelMap);
log.info("提交预审项目成功 【{}】", instanceId);



+ 4
- 9
pmapi/src/main/java/com/ningdatech/pmapi/projectdeclared/manage/ProjectAdjustmentManage.java Целия файл

@@ -43,6 +43,7 @@ import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;

@@ -163,13 +164,9 @@ public class ProjectAdjustmentManage {
req.setBuildOrgCode(userFullInfo.getOrganizationCode());

LambdaQueryWrapper<Project> query = ProjectHelper.projectQuery(req);
Page<Project> page = projectService.page(req.page(), query);

req.setPageNumber(CommonConst.EXPORT_PAGE_NUMBER);
req.setPageSize(CommonConst.EXPORT_PAGE_SIZE);
List<Project> records = page.getRecords();

List<Project> records = projectService.list(query);

AtomicInteger serialNumber = new AtomicInteger(0);
List<ProjectAdjustmentExportDTO> collect = records.stream().map(r -> {
ProjectAdjustmentExportDTO exportDTO = new ProjectAdjustmentExportDTO();
BeanUtils.copyProperties(r, exportDTO);
@@ -177,11 +174,9 @@ public class ProjectAdjustmentManage {
exportDTO.setStatusName(ProjectStatusEnum.getDesc(r.getStatus()));
String createOnStr = NdDateUtils.format(r.getCreateOn(), "yyyy-MM-dd HH:mm");
exportDTO.setCreateOn(createOnStr);
exportDTO.setSerialNumber(serialNumber.incrementAndGet());
return exportDTO;
}).collect(Collectors.toList());
for (int i = 0; i < collect.size(); i++) {
collect.get(i).setSerialNumber(i + 1);
}
String fileName = "项目内容调整列表";
ExcelDownUtil.setFileName(fileName,response);
//数据导出处理函数


+ 1
- 1
pmapi/src/main/java/com/ningdatech/pmapi/projectdeclared/manage/ReviewByDeptJointManage.java Целия файл

@@ -103,7 +103,7 @@ public class ReviewByDeptJointManage {
params.setFormData(formData);

// 获取发起单位、发起单位主管单位、发起单位上级主管条线单位信息
Map<String, OrgInfoDTO> orgModelMap = defaultDeclaredProjectManage.getOrgModelInfo(userId,project);
Map<String, OrgInfoDTO> orgModelMap = defaultDeclaredProjectManage.buildOrgModelMap(userId,project);
String instanceId = processService.newStartProcess(model.getProcessDefId(),model.getFormId(), params,orgModelMap);
log.info("部门联审申报成功 【{}】", instanceId);



+ 31
- 11
pmapi/src/main/java/com/ningdatech/pmapi/projectdeclared/manage/ReviewByProvincialDeptManage.java Целия файл

@@ -1,18 +1,30 @@
package com.ningdatech.pmapi.projectdeclared.manage;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ningdatech.basic.function.VUtils;
import com.ningdatech.pmapi.common.statemachine.util.StateMachineUtils;
import com.ningdatech.pmapi.projectdeclared.converter.ApplicationConverter;
import com.ningdatech.pmapi.projectdeclared.model.dto.DefaultDeclaredDTO;
import com.ningdatech.pmapi.projectlib.enumeration.ProjectStatusEnum;
import com.ningdatech.pmapi.projectlib.model.dto.ProjectDTO;
import com.ningdatech.pmapi.projectlib.model.entity.Project;
import com.ningdatech.pmapi.projectlib.model.entity.ProjectApplication;
import com.ningdatech.pmapi.projectlib.service.IProjectApplicationService;
import com.ningdatech.pmapi.projectlib.service.IProjectService;
import com.ningdatech.pmapi.provincial.model.dto.ProvincialApplicationDTO;
import com.ningdatech.pmapi.provincial.model.dto.ProvincialProjectDTO;
import com.ningdatech.pmapi.provincial.service.IJoinReviewProvincialBureauService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
* @Classname ReviewByProvincialDeptManage
@@ -31,6 +43,10 @@ public class ReviewByProvincialDeptManage {

private final DefaultDeclaredProjectManage defaultProjectManage;

private final IJoinReviewProvincialBureauService joinReviewProvincialBureauService;

private final IProjectApplicationService applicationService;

/**
* 省级部门联审
* @param project
@@ -47,17 +63,21 @@ public class ReviewByProvincialDeptManage {
VUtils.isTrue(!ProjectStatusEnum.JOINT_REVIEW_BY_PROVINCIAL_DEPARTMENTS.getCode().equals(projectInfo.getStatus()) ||
!ProjectStatusEnum.NOT_APPROVED.getCode().equals(projectInfo.getStage()))
.throwMessage("提交失败 该项目不是 省级部门联审状态状态或者未立项阶段");
// TODO 对接省级联审的接口
Boolean sucessProvince = Boolean.TRUE;
if(sucessProvince){
//测试先成功
stateMachineUtils.pass(project);
projectService.updateById(project);
//直接去预审
if(StringUtils.isNotBlank(defaultProjectManage
.directStartProcess(project,project.getPreStartUserId()))){
return Boolean.TRUE;
}

// 对接省级联审的接口
List<ProjectApplication> applications = applicationService.list(Wrappers.lambdaQuery(ProjectApplication.class)
.eq(ProjectApplication::getProjectId, projectInfo.getId()));
if(joinReviewProvincialBureauService.pushImportProject(
ApplicationConverter.convertProject(projectInfo,applications))){
return Boolean.TRUE;
// //测试先成功
// stateMachineUtils.pass(project);
// projectService.updateById(project);
// //直接去预审
// if(StringUtils.isNotBlank(defaultProjectManage
// .directStartProcess(project,project.getPreStartUserId()))){
// return Boolean.TRUE;
// }
}

return Boolean.FALSE;


+ 1
- 1
pmapi/src/main/java/com/ningdatech/pmapi/projectlib/model/entity/ProjectApplication.java Целия файл

@@ -67,7 +67,7 @@ public class ProjectApplication implements Serializable {
@ApiModelProperty("是否数改系统 0:否 1:是")
private Integer isDigitalModification;

@ApiModelProperty("数改系统")
@ApiModelProperty("数改系统 1: '党政机关整体智治',2: '数字政府',3: '数字经济',4: '数字社会',7: '数字文化',5: '数字法治',6: '一体化智能化公共数据平台', 8: '基层智治' 多个用英文,分隔")
private String digitalModification;

@ApiModelProperty("业务领域")


+ 9
- 2
pmapi/src/main/java/com/ningdatech/pmapi/projectlib/model/vo/ProjectLibListItemVO.java Целия файл

@@ -4,7 +4,9 @@ import com.fasterxml.jackson.annotation.JsonFormat;
import com.ningdatech.pmapi.projectlib.enumeration.ProjectTypeEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Tolerate;

import java.math.BigDecimal;
import java.time.LocalDateTime;
@@ -20,9 +22,14 @@ import java.util.Optional;
* @since 15:13 2023/2/1
*/
@Data
@Builder
@ApiModel("项目库列表视图")
public class ProjectLibListItemVO {

@Tolerate
public ProjectLibListItemVO() {
}

@ApiModelProperty("项目ID")
private Long id;

@@ -67,8 +74,8 @@ public class ProjectLibListItemVO {

private String projectTypeName;

public String getProjectTypeName(){
if(Objects.nonNull(this.projectType)){
public String getProjectTypeName() {
if (Objects.nonNull(this.projectType)) {
Optional.ofNullable(ProjectTypeEnum.getDesc(this.projectType))
.ifPresent(desc -> this.projectTypeName = desc);
}


+ 13
- 5
pmapi/src/main/java/com/ningdatech/pmapi/provincial/controller/TestController.java Целия файл

@@ -1,5 +1,8 @@
package com.ningdatech.pmapi.provincial.controller;

import com.ningdatech.basic.model.ApiResponse;
import com.ningdatech.pmapi.projectdeclared.manage.ReviewByProvincialDeptManage;
import com.ningdatech.pmapi.projectlib.model.entity.Project;
import com.ningdatech.pmapi.provincial.model.dto.ProvincialProjectDTO;
import com.ningdatech.pmapi.provincial.service.IJoinReviewProvincialBureauService;
import io.swagger.annotations.Api;
@@ -24,15 +27,20 @@ public class TestController {
@Autowired
private IJoinReviewProvincialBureauService joinReviewProvincialBureauService;

@PostMapping("/push")
@Autowired
private ReviewByProvincialDeptManage provincialDeptManage;

@GetMapping("/push")
@ApiOperation("测试推送")
private String push(@Valid @RequestBody ProvincialProjectDTO project){
return joinReviewProvincialBureauService.pushImportProject(project);
private Boolean push(@Valid @RequestParam Long projectId){
Project project = new Project();
project.setId(projectId);
return provincialDeptManage.startTheProcess(project);
}

@GetMapping("/detail")
@ApiOperation("测试推送")
private String detail(@RequestParam String projectId){
@ApiOperation("测试获取详情")
private ApiResponse detail(@RequestParam String projectId){
return joinReviewProvincialBureauService.processInfo(projectId);
}
}

+ 27
- 0
pmapi/src/main/java/com/ningdatech/pmapi/provincial/enumeration/ProjectProvincialAuditStatusEnum.java Целия файл

@@ -0,0 +1,27 @@
package com.ningdatech.pmapi.provincial.enumeration;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;

/**
* @Classname ProjectProvincialAuditStatusEnum
* @Description
* @Date 2023/3/16 11:04
* @Author PoffyZhang
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
public enum ProjectProvincialAuditStatusEnum {
/**
* 省级联审的状态
*/
AUDITING(1,"审核中"),
SUCCESS(2,"审核通过"),
FAIL(3,"审核不通过");

private Integer code;
private String desc;
}

+ 8
- 2
pmapi/src/main/java/com/ningdatech/pmapi/provincial/model/dto/ProvincialApplicationDTO.java Целия файл

@@ -1,11 +1,13 @@
package com.ningdatech.pmapi.provincial.model.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.List;

/**
@@ -17,7 +19,9 @@ import java.util.List;
@Data
@Builder
@ApiModel(value = "ProvincialApplicationDTO", description = "")
public class ProvincialApplicationDTO {
public class ProvincialApplicationDTO implements Serializable {


//云 信息
private List<Cloud> clouds;
//是否初次建设 1是 2不是
@@ -85,7 +89,9 @@ public class ProvincialApplicationDTO {
//发布端 '浙里办','浙政钉','数字化改革门户','支付宝','微信','网页','PC客户端','APP端'
private String publishSide;

public static class Cloud {
@Builder
@JsonIgnoreProperties(value = { "handler"})
public static class Cloud implements Serializable {
//云资源台数 11
private Integer cloudNums;
//云资源类型 云服务器(ECS)


+ 4
- 3
pmapi/src/main/java/com/ningdatech/pmapi/provincial/model/dto/ProvincialProjectDTO.java Целия файл

@@ -7,6 +7,7 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;

@@ -21,7 +22,7 @@ import java.util.List;
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "ProvincialProjectDTO", description = "")
public class ProvincialProjectDTO {
public class ProvincialProjectDTO implements Serializable {

@ApiModelProperty("区域code")
private String regionCode;
@@ -114,10 +115,10 @@ public class ProvincialProjectDTO {
private String projectRemark;

@ApiModelProperty("是否有效 1有效 2无效 3撤回")
private String isEffective;
private Integer isEffective;

@ApiModelProperty("是否包含应用 1包含")
private String includeApplication;
private Integer includeApplication;

@ApiModelProperty("app信息")
private List<ProvincialApplicationDTO> applicationInfo;


+ 36
- 0
pmapi/src/main/java/com/ningdatech/pmapi/provincial/model/res/ProcessCommentRes.java Целия файл

@@ -0,0 +1,36 @@
package com.ningdatech.pmapi.provincial.model.res;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;

/**
* @Classname ProcessCommentDTO
* @Description
* @Date 2023/3/2 15:25
* @Author PoffyZhang
*/
@Data
@Builder
@ApiModel(value = "ProcessCommentRes", description = "省局返回流程审核详情")
public class ProcessCommentRes {

@ApiModelProperty("任务id")
private String taskId;

@ApiModelProperty("comment")
private String comment;

@ApiModelProperty("流程步骤")
private String stepName;

@ApiModelProperty("审批状态")
private String status;

@ApiModelProperty("审批人")
private String label;

@ApiModelProperty("时间")
private String approverTime;
}

+ 102
- 0
pmapi/src/main/java/com/ningdatech/pmapi/provincial/model/res/ProvincialApplicationRes.java Целия файл

@@ -0,0 +1,102 @@
package com.ningdatech.pmapi.provincial.model.res;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import lombok.Builder;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

/**
* @Classname ProvincialApplicationDTO
* @Description
* @Date 2023/3/2 10:06
* @Author PoffyZhang
*/
@Data
@Builder
@ApiModel(value = "ProvincialApplicationRes", description = "")
public class ProvincialApplicationRes implements Serializable {


//云 信息
private List<Cloud> clouds;
//是否初次建设 1是 2不是
private Integer isFirst;
//应用名称
private String applicationName;
//关联 关联的IRS应用code
private String applicationCode;
//关联的IRS应用name
private String relatedExistsApplication;
//1: '办公类系统',2: '业务应用类系统',3: '门户网站',4: '宣传微博/微信公众号',5: '硬件类系统',6: '工具类系统',99: '其他'
private Integer applicationType;
//建设层级 1:国家 2:省级 3:市级 4:县(市、区)
private Integer buildLevel;
//是否统建 0:否 1:是
private Integer isUniteBuild;
//统建类型 1:全省统建 2:全市统建
private Integer unionBuildKind;
//应用简介
private String applicationSummary;
//应用备注
private String applicationRemark;
//应用总投资测算明细
private String applicationEstimateFile;
//是否数改系统 0:否 1:是
private Integer isFiveDomain;
//1: '党政机关整体智治',2: '数字政府',3: '数字经济',4: '数字社会',7: '数字文化',5: '数字法治',6: '一体化智能化公共数据平台', 8: '基层智治' 多个用英文,分隔
private String fiveDomain;
//业务领域
private String bizDomain;
//否涉及业务协同 0:否 1:是
private Integer isBizCooperate;
//协同单位111111
private String cooperativeUnit;
//用户范围 0: '机关事业单位人员','0-1': '跨部门跨系统','0-2': '系统内地方各级','0-3': '本部门本级','0-4': '处室内部','0-6': '主管处室内部','0-5': '其他',1: '企业', 2: '社会公众',3: '其他' 多个用英文,分隔
private String userRange;
//是否使用政务云资源 1使用
private Integer useGovCloud;
//是否符合国家信息技术应用创新相关规范 0:否 1:是
private Integer nationalITSpec;
//网络环境 1:政务内网 2:政务外网 3:互联网 4:业务专网 5:单机
private String netEnv;
//等保级别 1:一级 2:二级 3:三级 4:四级 5:五级
private Integer secrecyGrade;
//密码测评级别 1:一级 2:二级 3:三级 4:四级 5:五级
private Integer passwordGrade;
//是否是S2 0:否 1:是
private Integer isS2;
//一本账应用名称
private String accountAppName;
//领域”大脑”一本帐名称
private String brainAccountAppName;
//是否使用公共数据
private Integer useCommonData;
//使用的公共数据名称
private String dataName;
//使用公共组件的名称
private String commonComponents;
//是否使用公共组件
private Integer useCommonComponent;
//是否产生公共组件
private Integer isProduceCommonComponent;
//产生的组件名称
private String produceCommonComponent;
//发布端 '浙里办','浙政钉','数字化改革门户','支付宝','微信','网页','PC客户端','APP端'
private String publishSide;

@Builder
@JsonIgnoreProperties(value = { "handler"})
public static class Cloud implements Serializable {
//云资源台数 11
private Integer cloudNums;
//云资源类型 云服务器(ECS)
private String cloudType;
//云资源规格 1核8G
private String cloudBasicSpec;
//云资源描述
private String cloudUseDescription;
}
}

+ 132
- 0
pmapi/src/main/java/com/ningdatech/pmapi/provincial/model/res/ProvincialProjectRes.java Целия файл

@@ -0,0 +1,132 @@
package com.ningdatech.pmapi.provincial.model.res;

import com.ningdatech.pmapi.provincial.model.dto.ProvincialApplicationDTO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;

/**
* @Classname ProvincialProjectDTO
* @Description
* @Date 2023/3/2 10:06
* @Author PoffyZhang
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "ProvincialProjectDTO", description = "")
public class ProvincialProjectRes implements Serializable {

@ApiModelProperty("项目审核结果 1审核中 2审核通过 3审核不通过")
private Integer projectStatus;

@ApiModelProperty("区域code")
private String regionCode;

@ApiModelProperty("区域名称")
private String regionName;

@ApiModelProperty("重大项目名称")
private String projectName;

@ApiModelProperty("重大项目code 21位")
private String projectId;

@ApiModelProperty("项目类型 1新建 2续建")
private Integer projectType;

@ApiModelProperty("项目总投资(万元)")
private BigDecimal totalMoney;

@ApiModelProperty("项目年度预算(万元)")
private BigDecimal yearBudget;

@ApiModelProperty("自有资金,政府投资-本级财政资金,政府投资-上级补助资金")
private String budgetFrom;

@ApiModelProperty("预算年度 2023")
private String year;

@ApiModelProperty("财政code 32")
private String financialCode;

@ApiModelProperty("发改code 23")
private String developCode;

@ApiModelProperty("开始时间 比如2022-11-18")
private String beginTime;

@ApiModelProperty("结束时间 比如2022-12-13")
private String endTime;

@ApiModelProperty("立项依据1111")
private String buildBasis;

@ApiModelProperty("立项依据材料 [{\"fileId\":\"\"}]")
private String buildBasisFile;

@ApiModelProperty("项目概述")
private String projectSummary;

@ApiModelProperty("负责人")
private String responsibleMan;

@ApiModelProperty("联系人联系方式")
private String responsibleManPhone;

@ApiModelProperty("联系人")
private String contactName;

@ApiModelProperty("联系人联系方式")
private String contactPhone;

@ApiModelProperty("建设单位 比如财政局")
private String buildUnit;

@ApiModelProperty("建设单位浙政钉code")
private String buildUnitCode;

@ApiModelProperty("主管单位")
private String superUnit;

@ApiModelProperty("主管单位浙政钉code")
private String superUnitCode;

@ApiModelProperty("可研报告文件")
private String researchReport;

@ApiModelProperty("项目申报书")
private String projectApplyFile;

@ApiModelProperty("项目总投资测算明细")
private String projectEstimateFile;

@ApiModelProperty("申报单位主要职责")
private String unitThreePlan;

@ApiModelProperty("其他附件")
private String otherFile;

@ApiModelProperty("项目备注111")
private String projectRemark;

@ApiModelProperty("是否有效 1有效 2无效 3撤回")
private Integer isEffective;

@ApiModelProperty("是否包含应用 1包含")
private Integer includeApplication;

@ApiModelProperty("app信息")
private List<ProvincialApplicationRes> applicationInfo;

@ApiModelProperty("审核信息")
private List<ProcessCommentRes> processComment;
}

+ 3
- 2
pmapi/src/main/java/com/ningdatech/pmapi/provincial/service/IJoinReviewProvincialBureauService.java Целия файл

@@ -1,5 +1,6 @@
package com.ningdatech.pmapi.provincial.service;

import com.ningdatech.basic.model.ApiResponse;
import com.ningdatech.pmapi.provincial.model.dto.ProvincialProjectDTO;

/**
@@ -14,12 +15,12 @@ public interface IJoinReviewProvincialBureauService {
* 推送/保存 重大接口到 省局联审
* @return
*/
String pushImportProject(ProvincialProjectDTO project);
Boolean pushImportProject(ProvincialProjectDTO project);


/**
* 查看 本区域 省局联审 的项目审核详情
* @return
*/
String processInfo(String projectId);
ApiResponse processInfo(String projectId);
}

+ 20
- 9
pmapi/src/main/java/com/ningdatech/pmapi/provincial/service/impl/JoinReviewProvincialBureauServiceImpl.java Целия файл

@@ -2,8 +2,12 @@ package com.ningdatech.pmapi.provincial.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ningdatech.basic.model.ApiResponse;
import com.ningdatech.pmapi.common.config.ProvincialProperties;
import com.ningdatech.pmapi.provincial.model.dto.ProvincialProjectDTO;
import com.ningdatech.pmapi.provincial.model.res.ProvincialProjectRes;
import com.ningdatech.pmapi.provincial.service.IJoinReviewProvincialBureauService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -35,12 +39,12 @@ public class JoinReviewProvincialBureauServiceImpl implements IJoinReviewProvinc
* @return
*/
@Override
public String pushImportProject(ProvincialProjectDTO project){
public Boolean pushImportProject(ProvincialProjectDTO project){
Long timeStamp = System.currentTimeMillis()/1000;
String url = provincialProperties.getHost() + provincialProperties.getPushUrl()
+ "?timestamp=" + timeStamp;
log.info("省局推送联审url {}",url);
ResponseEntity<String> responseEntity = null;
ResponseEntity<ApiResponse> responseEntity = null;

String signature = getSha256(timeStamp,provincialProperties.getPushUrl(),
HttpMethod.POST.name());
@@ -55,14 +59,19 @@ public class JoinReviewProvincialBureauServiceImpl implements IJoinReviewProvinc
.accept(MediaType.APPLICATION_JSON)
.body(project);

log.info("省局联审 提交 :{}", requestEntity);
log.info("省局联审 提交body :{}", JSON.toJSONString(requestEntity.getBody()));
try {
responseEntity = restTemplate.exchange(requestEntity,String.class);
responseEntity = restTemplate.exchange(requestEntity, ApiResponse.class);
log.info("省局联审 响应 :{}",responseEntity);
if(responseEntity.getBody().getCode().equals(200)){
return Boolean.TRUE;
}
} catch (Exception e) {
log.error("[省局联审] http request error", e);
}

return responseEntity.getBody();
return Boolean.FALSE;
}

/**
@@ -71,29 +80,31 @@ public class JoinReviewProvincialBureauServiceImpl implements IJoinReviewProvinc
* @return
*/
@Override
public String processInfo(String projectId) {
public ApiResponse processInfo(String projectId) {
Long timeStamp = System.currentTimeMillis()/1000;
String url = provincialProperties.getHost() + provincialProperties.getDetailUrl()
+ "?timestamp=" + timeStamp;

log.info("省局获取审核详情 url {}",url);
ResponseEntity<String> responseEntity = null;
ResponseEntity<ApiResponse> responseEntity = null;

String signature = getSha256(timeStamp,provincialProperties.getDetailUrl(),
HttpMethod.POST.name());

JSONObject jsonBaby = new JSONObject();
jsonBaby.put("projectId",projectId);
//发送post请求
RequestEntity<String> requestEntity = RequestEntity
RequestEntity<JSONObject> requestEntity = RequestEntity
.post(url)
.header("Accept", MediaType.APPLICATION_JSON.toString())
.header("X-Hmac-Auth-Key",provincialProperties.getKey())
.header("X-Hmac-Auth-Signature",signature)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(projectId); //也可以是DTO
.body(jsonBaby); //也可以是DTO

try {
responseEntity = restTemplate.exchange(requestEntity,String.class);
responseEntity = restTemplate.exchange(requestEntity,ApiResponse.class);
log.info("获取审批详情 响应 :{}",responseEntity);
} catch (Exception e) {
log.error("[省局获取审核详情] http request error", e);


+ 91
- 0
pmapi/src/main/java/com/ningdatech/pmapi/scheduler/task/CheckProvincialReviewResultTask.java Целия файл

@@ -0,0 +1,91 @@
package com.ningdatech.pmapi.scheduler.task;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.http.HttpStatus;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ningdatech.basic.model.ApiResponse;
import com.ningdatech.pmapi.common.statemachine.util.StateMachineUtils;
import com.ningdatech.pmapi.projectlib.enumeration.ProjectStatusEnum;
import com.ningdatech.pmapi.projectlib.model.entity.Project;
import com.ningdatech.pmapi.projectlib.service.IProjectService;
import com.ningdatech.pmapi.provincial.enumeration.ProjectProvincialAuditStatusEnum;
import com.ningdatech.pmapi.provincial.model.res.ProvincialProjectRes;
import com.ningdatech.pmapi.provincial.service.IJoinReviewProvincialBureauService;
import com.ningdatech.pmapi.scheduler.contants.TaskContant;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;

/**
* @Classname CheckProvincialReviewResultTask
* @Description 去获取省级联审结果任务
* @Date 2023/3/16 10:12
* @Author PoffyZhang
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class CheckProvincialReviewResultTask {

private final IJoinReviewProvincialBureauService reviewProvincialBureauService;

private final IProjectService projectService;
private final StateMachineUtils stateMachineUtils;

@Scheduled(cron = "0 */5 * * * ?")
public void statusFlow() throws UnknownHostException {
//测试暂时用自己电脑HOST
if (TaskContant.Host.HOST_ZPF.equals(InetAddress.getLocalHost().getHostName())) {
//1. 定时取 省级部门联审中的项目 去取项目
List<Project> projectList = projectService.list(Wrappers.lambdaQuery(Project.class)
.eq(Project::getStage, ProjectStatusEnum.NOT_APPROVED)
.eq(Project::getStatus, ProjectStatusEnum.JOINT_REVIEW_BY_PROVINCIAL_DEPARTMENTS)
.orderByAsc(Project::getCreateOn));

log.info("需要去查询省级联审结果的项目 size:{}",projectList.size());
if(CollUtil.isEmpty(projectList)){
log.info("没有正在省级联审中的项目!");
return;
}

//遍历
for(Project project: projectList){
try{
ApiResponse apiResponse = reviewProvincialBureauService.processInfo(String.valueOf(project.getId()));
log.info("项目 【{}】 去获取省局联审结果 :{}",project.getId(),apiResponse);
if(Objects.isNull(apiResponse) || !Integer.valueOf(HttpStatus.HTTP_OK).equals(apiResponse.getCode())){
log.info("项目 【{}】 去获取省局联审结果失败",project.getId());
continue;
}
ProvincialProjectRes projectRes = JSON.parseObject(JSON.toJSONString(apiResponse.getData()),
ProvincialProjectRes.class);

if(ProjectProvincialAuditStatusEnum.AUDITING.getCode().equals(projectRes.getProjectStatus())){
log.info("此项目 【{}】 还在审核中",projectRes.getProjectId());
}else if(ProjectProvincialAuditStatusEnum.SUCCESS.getCode().equals(projectRes.getProjectStatus())){
log.info("此项目 【{}】 审核通过",projectRes.getProjectId());
stateMachineUtils.pass(project);
project.setUpdateOn(LocalDateTime.now());
projectService.updateById(project);
}else if(ProjectProvincialAuditStatusEnum.SUCCESS.getCode().equals(projectRes.getProjectStatus())){
log.info("此项目 【{}】 审核不通过",projectRes.getProjectId());
stateMachineUtils.reject(project);
project.setUpdateOn(LocalDateTime.now());
projectService.updateById(project);
}else{
log.info("此项目 【{}】 审核结果错误",projectRes.getProjectId());
}
}catch (Exception e){
log.error("项目审核信息获取异常 projectId:【" + project.getId() + "】 异常内容:" + e.getMessage());
}
}
}
}
}

+ 2
- 2
pmapi/src/main/java/com/ningdatech/pmapi/scheduler/task/ProjectStatusFlowTask.java Целия файл

@@ -42,8 +42,8 @@ public class ProjectStatusFlowTask {

@Scheduled(cron = "0 */1 * * * ?")
public void statusFlow() throws UnknownHostException {
//测试暂时用自己电脑HOST
if (TaskContant.Host.HOST_ZPF.equals(InetAddress.getLocalHost().getHostName())) {
//测试暂时用自己207
if (TaskContant.Host.HOST_207.equals(InetAddress.getLocalHost().getHostName())) {
//1. 定时取 项目暂存表的数据 去进行状态继续流转
List<ProjectStaging> stagingList = projectStagingService.list(Wrappers.lambdaQuery(ProjectStaging.class)
.eq(ProjectStaging::getDead,Boolean.FALSE)


+ 2
- 2
pmapi/src/main/java/com/ningdatech/pmapi/scheduler/task/WorkNoticeFlowTask.java Целия файл

@@ -46,10 +46,10 @@ public class WorkNoticeFlowTask {
private final INdWorkNoticeStagingService workNoticeStagingService;
private final ZwddClient zwddClient;

// @Scheduled(cron = "0 */1 * * * ?")
@Scheduled(cron = "0 */1 * * * ?")
public void statusFlow() throws UnknownHostException {
//测试暂时用自己电脑HOST
if (TaskContant.Host.HOST_CMM.equals(InetAddress.getLocalHost().getHostName())) {
if (TaskContant.Host.HOST_207.equals(InetAddress.getLocalHost().getHostName())) {
//1. 定时取 工作通知暂存表的数据进行发送
List<WorkNoticeStaging> stagingList = workNoticeStagingService.list(Wrappers.lambdaQuery(WorkNoticeStaging.class)
.eq(WorkNoticeStaging::getDead, Boolean.FALSE)


+ 1
- 1
pmapi/src/main/java/com/ningdatech/pmapi/sms/controller/VerificationCodeController.java Целия файл

@@ -35,7 +35,7 @@ public class VerificationCodeController {
@ApiOperation(value = "发送验证码", notes = "发送验证码")
@PostMapping(value = "/send")
public void send(@Validated @RequestBody ReqVerificationCodePO request) {
// smsManage.sendVerificationCode(request);
smsManage.sendVerificationCode(request);
}

}

+ 64
- 59
pmapi/src/main/java/com/ningdatech/pmapi/sms/manage/SmsManage.java Целия файл

@@ -11,6 +11,9 @@ import com.ningdatech.pmapi.sms.model.dto.VerifyCodeCacheDTO;
import com.ningdatech.pmapi.sms.model.po.ReqVerificationCodePO;
import com.ningdatech.pmapi.sms.utils.DateUtil;
import com.ningdatech.pmapi.sms.utils.SmsRedisKeyUtils;
import com.ningdatech.yxt.client.YxtClient;
import com.ningdatech.yxt.constants.YxtSmsSignEnum;
import com.ningdatech.yxt.model.cmd.SendSmsCmd;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@@ -20,6 +23,7 @@ import org.springframework.util.Assert;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Objects;

/**
* @author liuxinxin
@@ -29,65 +33,66 @@ import java.util.Collections;
@Component
@RequiredArgsConstructor
public class SmsManage {
//
// private final YxtClient yxtClient;
// private final CachePlusOps cachePlusOps;
//
// public void sendVerificationCode(ReqVerificationCodePO request) {
// Assert.isTrue(PhoneUtil.isMobile(request.getMobile()), "手机号码格式不正确");
// String verificationType = request.getVerificationType();
// VerificationCodeType verificationCodeTypeEnum = VerificationCodeType.of(verificationType);
//
// // 验证是否被锁定
// String lockKey = SmsRedisKeyUtils.smsSendLockKey(verificationCodeTypeEnum, request.getMobile());
// if (StringUtils.isNotBlank(cachePlusOps.get(lockKey))) {
// throw BizException.wrap("今日" + verificationCodeTypeEnum.getDesc() + "的验证码发送次数过多,已被锁定");
// }
// // 验证发送间隔
// String cacheKey = SmsRedisKeyUtils.smsCodeVerifyKey(verificationCodeTypeEnum, request.getMobile());
// VerifyCodeCacheDTO preCache = (VerifyCodeCacheDTO) cachePlusOps.get(cacheKey);
// if (preCache != null) {
// if (LocalDateTime.now().minusMinutes(verificationCodeTypeEnum.getSendInterval())
// .isBefore(preCache.getSendTime())) {
// throw BizException.wrap(verificationCodeTypeEnum.getSendInterval() + "分钟之内已发送过验证码,请稍后重试");
// }
// }
// String code = RandomUtil.randomNumbers(6);
// VerifyCodeCacheDTO cache = VerifyCodeCacheDTO.builder()
// .code(code)
// .sendTime(LocalDateTime.now())
// .mobile(request.getMobile())
// .build();
//
// // 创建短信内容
// SendSmsCmd sendSmsCmd = new SendSmsCmd();
// switch (verificationCodeTypeEnum) {
// case LOGIN:
// SendSmsCmd.SendSmsContext sendSmsContext = new SendSmsCmd.SendSmsContext();
// sendSmsContext.setReceiveNumber(request.getMobile());
// sendSmsContext.setContent(String.format(YxtSmsTemplateConst.SMS_LOGIN_TEMPLATE, code, verificationCodeTypeEnum.getExpireTime()));
// sendSmsCmd.setContextList(Collections.singletonList(sendSmsContext));
// sendSmsCmd.setSmsSignEnum(YxtSmsSignEnum.ZJS_ELECTRONIC_EXPERT_LIB);
// break;
// default:
// throw new IllegalArgumentException("非法的短信发送类型");
// }
// // 发送 短信
// yxtClient.submitSmsTask(sendSmsCmd);
// log.info("send verificationCode mobile = {},code = {}", request.getMobile(), code);
//
// cachePlusOps.set(new CacheKey(cacheKey, Duration.ofMinutes(verificationCodeTypeEnum.getExpireTime())), cache);
// String limitKey = SmsRedisKeyUtils.smsSendLimitKey(verificationCodeTypeEnum, request.getMobile());
// if (StringUtils.isNotBlank(cachePlusOps.get(limitKey))) {
// long limitCount = cachePlusOps.incr(new CacheKey(limitKey, Duration.ofSeconds(DateUtil.restSecondsFromNowToNoon())));
// // 超出单日发送次数之后直接锁定
// if (limitCount >= verificationCodeTypeEnum.getSendTimesByDay().longValue()) {
// cachePlusOps.set(new CacheKey(lockKey, Duration.ofSeconds(DateUtil.restSecondsFromNowToNoon())), request.getMobile());
// }
// } else {
// cachePlusOps.set(new CacheKey(limitKey, Duration.ofSeconds(DateUtil.restSecondsFromNowToNoon())), 1);
// }
// }

private final YxtClient yxtClient;
private final CachePlusOps cachePlusOps;

public void sendVerificationCode(ReqVerificationCodePO request) {
Assert.isTrue(PhoneUtil.isMobile(request.getMobile()), "手机号码格式不正确");
String verificationType = request.getVerificationType();
VerificationCodeType verificationCodeTypeEnum = VerificationCodeType.of(verificationType);

// 验证是否被锁定
String lockKey = SmsRedisKeyUtils.smsSendLockKey(verificationCodeTypeEnum, request.getMobile());
if (StringUtils.isNotBlank(cachePlusOps.get(lockKey))) {
throw BizException.wrap("今日" + verificationCodeTypeEnum.getDesc() + "的验证码发送次数过多,已被锁定");
}
// 验证发送间隔
String cacheKey = SmsRedisKeyUtils.smsCodeVerifyKey(verificationCodeTypeEnum, request.getMobile());
VerifyCodeCacheDTO preCache = (VerifyCodeCacheDTO) cachePlusOps.get(cacheKey);
if (preCache != null) {
if (LocalDateTime.now().minusMinutes(verificationCodeTypeEnum.getSendInterval())
.isBefore(preCache.getSendTime())) {
throw BizException.wrap(verificationCodeTypeEnum.getSendInterval() + "分钟之内已发送过验证码,请稍后重试");
}
}
String code = RandomUtil.randomNumbers(6);
VerifyCodeCacheDTO cache = VerifyCodeCacheDTO.builder()
.code(code)
.sendTime(LocalDateTime.now())
.mobile(request.getMobile())
.build();

// 创建短信内容
SendSmsCmd sendSmsCmd = new SendSmsCmd();
switch (verificationCodeTypeEnum) {
case LOGIN:
SendSmsCmd.SendSmsContext sendSmsContext = new SendSmsCmd.SendSmsContext();
sendSmsContext.setReceiveNumber(request.getMobile());
sendSmsContext.setContent(String.format(YxtSmsTemplateConst.SMS_LOGIN_TEMPLATE, code, verificationCodeTypeEnum.getExpireTime()));
sendSmsCmd.setContextList(Collections.singletonList(sendSmsContext));
sendSmsCmd.setSmsSignEnum(YxtSmsSignEnum.ZJS_ELECTRONIC_EXPERT_LIB);
break;
default:
throw new IllegalArgumentException("非法的短信发送类型");
}
// 发送 短信
yxtClient.submitSmsTask(sendSmsCmd);
log.info("send verificationCode mobile = {},code = {}", request.getMobile(), code);

cachePlusOps.set(new CacheKey(cacheKey, Duration.ofMinutes(verificationCodeTypeEnum.getExpireTime())), cache);
String limitKey = SmsRedisKeyUtils.smsSendLimitKey(verificationCodeTypeEnum, request.getMobile());
if (Objects.nonNull(cachePlusOps.get(limitKey))) {
Integer limitCount = cachePlusOps.get(limitKey);
cachePlusOps.set(new CacheKey(limitKey, Duration.ofSeconds(DateUtil.restSecondsFromNowToNoon())), limitCount++);
// 超出单日发送次数之后直接锁定
if (limitCount >= verificationCodeTypeEnum.getSendTimesByDay().longValue()) {
cachePlusOps.set(new CacheKey(lockKey, Duration.ofSeconds(DateUtil.restSecondsFromNowToNoon())), request.getMobile());
}
} else {
cachePlusOps.set(new CacheKey(limitKey, Duration.ofSeconds(DateUtil.restSecondsFromNowToNoon())), 1);
}
}


}

+ 2
- 2
pmapi/src/main/java/com/ningdatech/pmapi/sms/model/po/ReqVerificationCodePO.java Целия файл

@@ -19,8 +19,8 @@ public class ReqVerificationCodePO implements Serializable {
@NotBlank(message = "手机号不能为空")
private String mobile;

@ApiModelProperty(value = "短信类型", allowableValues = "LOGIN,RECOMMENDATION_PROOF_FILE_SUBMIT")
@ApiModelProperty(value = "短信类型", allowableValues = "LOGIN")
@NotBlank(message = "短信类型不能为空")
private String verificationType;

}
}

+ 29
- 0
pmapi/src/main/java/com/ningdatech/pmapi/sms/task/YxtPollingTask.java Целия файл

@@ -0,0 +1,29 @@
package com.ningdatech.pmapi.sms.task;

import com.ningdatech.yxt.client.YxtContext;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
* @author liuxinxin
* @date 2022/8/9 下午3:58
* 音信通定时检查电话结果
*/
@Component
@RequiredArgsConstructor
public class YxtPollingTask {


private final YxtContext yxtContext;

/**
* 校验音信通结果数据
* 每5分钟执行一次
*/
@Scheduled(cron = "0 */1 * * * ?")
// @Scheduled(cron = "${cron-expression.sms-msg-result-check-cron}")
public void smsMsgResultCheck() {
yxtContext.smsMsgResultCheck();
}
}

+ 5
- 3
pmapi/src/main/java/com/ningdatech/pmapi/sms/utils/SmsRedisKeyUtils.java Целия файл

@@ -16,20 +16,22 @@ public class SmsRedisKeyUtils {
private SmsRedisKeyUtils() {
}

private static final String PROJECT_NAME = "ls_pm:";

private static final String SMS_CODE_VERIFY_PREFIX = "sms:verify:";
private static final String SMS_SEND_LIMIT = "sms:limit:";
private static final String SMS_SEND_LOCK = "sms:lock:";

public static String smsCodeVerifyKey(VerificationCodeType type, String mobile) {
return SMS_CODE_VERIFY_PREFIX + StrPool.COLON + type.name() + StrPool.COLON + mobile;
return PROJECT_NAME + SMS_CODE_VERIFY_PREFIX + StrPool.COLON + type.name() + StrPool.COLON + mobile;
}

public static String smsSendLimitKey(VerificationCodeType type, String mobile) {
return SMS_SEND_LIMIT + StrPool.COLON + type.name() + StrPool.COLON + mobile;
return PROJECT_NAME + SMS_SEND_LIMIT + StrPool.COLON + type.name() + StrPool.COLON + mobile;
}

public static String smsSendLockKey(VerificationCodeType type, String mobile) {
return SMS_SEND_LOCK + StrPool.COLON + type.name() + StrPool.COLON + mobile;
return PROJECT_NAME + SMS_SEND_LOCK + StrPool.COLON + type.name() + StrPool.COLON + mobile;
}

}

+ 1
- 1
pmapi/src/main/java/com/ningdatech/pmapi/sys/service/IRegionService.java Целия файл

@@ -1,8 +1,8 @@
package com.ningdatech.pmapi.sys.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.ningdatech.pmapi.sys.model.entity.Region;
import com.ningdatech.pmapi.sys.model.dto.RegionDTO;
import com.ningdatech.pmapi.sys.model.entity.Region;

import java.util.List;



+ 3
- 3
pmapi/src/main/java/com/ningdatech/pmapi/sys/service/impl/RegionServiceImpl.java Целия файл

@@ -52,9 +52,9 @@ public class RegionServiceImpl extends ServiceImpl<RegionMapper, Region> impleme
@Override
public Long getRegionIdByParentIdWithSameRegionCode(Long parentId, String regionCode) {
return baseMapper.selectOne(Wrappers.lambdaQuery(Region.class)
.eq(Region::getParentId, parentId)
.eq(Region::getRegionCode, regionCode)
.eq(Region::getDeleted, false))
.eq(Region::getParentId, parentId)
.eq(Region::getRegionCode, regionCode)
.eq(Region::getDeleted, false))
.getId();
}



+ 7
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/controller/UserAuthController.java Целия файл

@@ -81,4 +81,11 @@ public class UserAuthController {
response.getWriter().write(objectMapper.writeValueAsString(BizConst.UNAUTHENTICATED));
}

@PostMapping(value = "/agent-login", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
@ApiOperation(value = "代登陆")
@ApiImplicitParams({
@ApiImplicitParam(name = "userId", value = "账号", required = true, paramType = "form", dataType = "String")})
public void agentLogin(@RequestParam(value = "userId", required = true) String userId) {
// 不实现任何内容,只是为了出api文档
}
}

+ 0
- 6
pmapi/src/main/java/com/ningdatech/pmapi/user/controller/UserInfoController.java Целия файл

@@ -59,10 +59,4 @@ public class UserInfoController {
return userInfoManage.currentUserInfo();
}

@ApiOperation(value = "代登陆", notes = "代登陆")
@PostMapping("/generation-login")
public void generationLogin(@Valid @RequestBody ReqGenerationLoginPO reqGenerationLoginPO, HttpServletRequest httpServletRequest) {
userInfoManage.generationLogin(reqGenerationLoginPO);
}

}

+ 5
- 38
pmapi/src/main/java/com/ningdatech/pmapi/user/manage/UserInfoManage.java Целия файл

@@ -17,7 +17,10 @@ import com.ningdatech.pmapi.sys.service.IRoleService;
import com.ningdatech.pmapi.sys.service.IUserRoleService;
import com.ningdatech.pmapi.user.constant.UserAvailableEnum;
import com.ningdatech.pmapi.user.entity.UserInfo;
import com.ningdatech.pmapi.user.model.po.*;
import com.ningdatech.pmapi.user.model.po.ReqUserDetailEditPO;
import com.ningdatech.pmapi.user.model.po.ReqUserDetailPO;
import com.ningdatech.pmapi.user.model.po.ReqUserDisableOrEnablePO;
import com.ningdatech.pmapi.user.model.po.ReqUserInfoListPO;
import com.ningdatech.pmapi.user.model.vo.ResUserDetailVO;
import com.ningdatech.pmapi.user.model.vo.ResUserInfoListVO;
import com.ningdatech.pmapi.user.model.vo.UserRoleVO;
@@ -25,7 +28,6 @@ import com.ningdatech.pmapi.user.security.auth.model.UserFullInfoDTO;
import com.ningdatech.pmapi.user.service.IUserInfoService;
import com.ningdatech.pmapi.user.util.LoginUserUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@@ -250,7 +252,7 @@ public class UserInfoManage {
}
resUserDetailVO.setUserRoleInfoList(userRoleInfoList);
UserFullInfoDTO userFullInfo = userInfoHelper.getUserFullInfo(userId);
if(Objects.nonNull(userFullInfo)){
if (Objects.nonNull(userFullInfo)) {
resUserDetailVO.setOrgCode(userFullInfo.getOrganizationCode());
resUserDetailVO.setOrgName(userFullInfo.getOrganizationName());
resUserDetailVO.setRegionCode(userFullInfo.getRegionCode());
@@ -258,39 +260,4 @@ public class UserInfoManage {
return resUserDetailVO;
}

public void generationLogin(ReqGenerationLoginPO reqGenerationLoginPO) {
Long userId = reqGenerationLoginPO.getUserId();
UserInfo userInfo = iUserInfoService.getById(userId);
if (Objects.isNull(userInfo)) {
throw new BizException("该员工账号处于禁用状态中,无法使用");
}
if (!UserAvailableEnum.ENABLE.name().equals(userInfo.getAvailable())) {
throw new BizException("该员工账号处于禁用状态中,无法使用");
}
UserFullInfoDTO userFullInfo = userInfoHelper.getUserFullInfo(userId);


// ReqGenerationLoginPO reqGenerationLoginPO
}

// public void autoLogin(Long userId){
// userDetailsService.loadUserByUsername(userId + UserDeatilsServiceConstant.USER_DETAILS_SERVICE_SEPARATOR + LoginTypeEnum.USERNAME_PASSWORD_LOGIN.name());
//
// CredentialAuthToken token = new CredentialAuthToken(email, password);
// try {
// token.setDetails(new WebAuthenticationDetails(httpServletRequest));
// UsernamePasswordAuthToken authenticatedUser = (UsernamePasswordAuthToken)usernamePasswordAuthSecurityConfig
// .getAuthenticationManager().authenticate(token);
// SecurityContextHolder.getContext().setAuthentication(authenticatedUser);
// httpServletRequest.getSession().setAttribute(
// HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
// String sessionId = httpServletRequest.getSession().getId();
// putSessionIdToCache(LoginUserUtil.getUserId(), sessionId);
// } catch (
// AuthenticationException e) {
// throw new RuntimeException("autoLogIn Authentication failed!", e);
// }
// }


}

+ 5
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/AuthProperties.java Целия файл

@@ -29,6 +29,11 @@ public class AuthProperties {

private String passwordLoginUrl;

/**
* 代登陆接口
*/
private String agentLoginUrl;

private String logoutUrl;

private List<String> ignoreAuthUrls;


+ 6
- 2
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/WebSecurityConfig.java Целия файл

@@ -4,8 +4,9 @@ import com.ningdatech.basic.util.NdJsonUtil;
import com.ningdatech.basic.util.StrPool;
import com.ningdatech.pmapi.common.constant.BizConst;
import com.ningdatech.pmapi.common.constant.CommonConst;
import com.ningdatech.pmapi.user.security.auth.handler.DefaultExpiredSessionStrategy;
import com.ningdatech.pmapi.user.security.auth.agent.AgentAuthSecurityConfig;
import com.ningdatech.pmapi.user.security.auth.credential.CredentialAuthSecurityConfig;
import com.ningdatech.pmapi.user.security.auth.handler.DefaultExpiredSessionStrategy;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
@@ -31,13 +32,16 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final CredentialAuthSecurityConfig credentialAuthSecurityConfig;
private final LogoutSuccessHandler logoutSuccessHandler;
private final DefaultExpiredSessionStrategy defaultExpiredSessionStrategy;
private final AgentAuthSecurityConfig agentAuthSecurityConfig;

public WebSecurityConfig(AuthProperties authProperties,
CredentialAuthSecurityConfig credentialAuthSecurityConfig,
AgentAuthSecurityConfig agentAuthSecurityConfig,
@Qualifier(value = "defaultLogoutSuccessHandler") LogoutSuccessHandler logoutSuccessHandler,
DefaultExpiredSessionStrategy defaultExpiredSessionStrategy) {
this.authProperties = authProperties;
this.credentialAuthSecurityConfig = credentialAuthSecurityConfig;
this.agentAuthSecurityConfig = agentAuthSecurityConfig;
this.logoutSuccessHandler = logoutSuccessHandler;
this.defaultExpiredSessionStrategy = defaultExpiredSessionStrategy;
}
@@ -47,7 +51,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
assemblerPreAuthUrls(http);
http.formLogin()
.loginPage(authProperties.getAuthRequireUrl())
.and().apply(credentialAuthSecurityConfig)
.and().apply(credentialAuthSecurityConfig).and().apply(agentAuthSecurityConfig)
.and()
.authorizeRequests().antMatchers(authProperties.getIgnoreAuthUrlsArray()).permitAll().anyRequest()
.authenticated().and()


+ 74
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/agent/AgentAuthFilter.java Целия файл

@@ -0,0 +1,74 @@
package com.ningdatech.pmapi.user.security.auth.agent;

import com.ningdatech.basic.exception.BizException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* @Author LiuXinXin
* @Date 2020/8/3 8:46 下午
* @Version 1.0
**/
public class AgentAuthFilter extends AbstractAuthenticationProcessingFilter {

private boolean postOnly = true;

private static final String USER_ID_PARAMETER = "userId";

// ~ Constructors
// ===================================================================================================

public AgentAuthFilter(String processingUrl) {
super(new AntPathRequestMatcher(processingUrl, HttpMethod.POST.name()));
}

// ~ Methods
// ========================================================================================================

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (postOnly && !request.getMethod().equals(HttpMethod.POST.name())) {
throw new AuthenticationServiceException("请求方法错误");
}
String userId = request.getParameter(USER_ID_PARAMETER);
if (StringUtils.isBlank(userId)) {
throw new BadCredentialsException("用户id 不能为空");
}

userId = trim(userId);
try {
AgentAuthToken authRequest = new AgentAuthToken(userId, userId);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
} catch (AuthenticationException e) {
throw new BadCredentialsException("账号或密码错误");
} catch (BizException e) {
throw new BadCredentialsException(e.getMessage());
} catch (Exception e) {
throw new InternalAuthenticationServiceException("授权失败:", e);
}
}

protected void setDetails(HttpServletRequest request, AgentAuthToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}

private String trim(String trimStr) {
if (StringUtils.isNotBlank(trimStr)) {
return trimStr.trim();
}
return null;
}
}

+ 40
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/agent/AgentAuthProvider.java Целия файл

@@ -0,0 +1,40 @@
package com.ningdatech.pmapi.user.security.auth.agent;

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;

/**
* @Author LiuXinXin
* @Date 2020/8/3 8:55 下午
* @Version 1.0
**/
public class AgentAuthProvider implements AuthenticationProvider {

private UserDetailsService userDetailsService;

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!(authentication instanceof AgentAuthToken)) {
throw new RuntimeException("CustomAuthProvider 只支持 CustomAuthToken");
}
AgentAuthToken authenticationToken = (AgentAuthToken) authentication;
String principal = (String) authenticationToken.getPrincipal();

UserDetails user = userDetailsService.loadUserByUsername(principal);
// 将用户定义的user放入token中,这样可以在session中查询到所有自定义的用户信息
return new AgentAuthToken(user, user.getPassword(), user.getAuthorities());
}

@Override
public boolean supports(Class<?> authentication) {
return AgentAuthToken.class.isAssignableFrom(authentication);
}

public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}

}

+ 55
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/agent/AgentAuthSecurityConfig.java Целия файл

@@ -0,0 +1,55 @@
package com.ningdatech.pmapi.user.security.auth.agent;

import com.ningdatech.pmapi.user.security.auth.AuthProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;

/**
* 账号密码登陆的认证配置
*/
@Component
public class AgentAuthSecurityConfig
extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

@Autowired
@Qualifier(value = "defaultLoginSuccessHandler")
protected AuthenticationSuccessHandler defaultLoginSuccessHandler;

@Autowired
@Qualifier(value = "defaultLoginFailureHandler")
protected AuthenticationFailureHandler defaultLoginFailureHandler;

@Autowired
@Qualifier(value = "agentLoginUserDetailService")
private UserDetailsService agentLoginUserDetailService;

@Autowired
private AuthProperties authProperties;

private AuthenticationManager authenticationManager;

@Override
public void configure(HttpSecurity http) throws Exception {
AgentAuthFilter agentAuthFilter =
new AgentAuthFilter(authProperties.getAgentLoginUrl());
authenticationManager = http.getSharedObject(AuthenticationManager.class);
agentAuthFilter.setAuthenticationManager(authenticationManager);
agentAuthFilter.setAuthenticationSuccessHandler(defaultLoginSuccessHandler);
agentAuthFilter.setAuthenticationFailureHandler(defaultLoginFailureHandler);

AgentAuthProvider authenticationProvider = new AgentAuthProvider();
authenticationProvider.setUserDetailsService(agentLoginUserDetailService);

http.authenticationProvider(authenticationProvider).addFilterAfter(agentAuthFilter,
UsernamePasswordAuthenticationFilter.class);
}
}

+ 76
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/agent/AgentAuthToken.java Целия файл

@@ -0,0 +1,76 @@
package com.ningdatech.pmapi.user.security.auth.agent;

import com.ningdatech.pmapi.user.constant.LoginTypeEnum;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;

import java.util.Collection;

/**
* @Author LiuXinXin
* @Date 2020/8/3 8:52 下午
* @Version 1.0
**/
public class AgentAuthToken extends AbstractAuthenticationToken {

private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

private final Object principal;

private final Object credentials;

/**
* This constructor can be safely used by any code that wishes to create a
* <code>UsernamePasswordAuthenticationToken</code>, as the {@link #isAuthenticated()} will return
* <code>false</code>.
*/
public AgentAuthToken(String principal, String credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}

/**
* This constructor should only be used by <code>AuthenticationManager</code> or <code>AuthenticationProvider</code>
* implementations that are satisfied with producing a trusted (i.e. {@link #isAuthenticated()} = <code>true</code>)
* authentication token.
*
* @param principal
* @param authorities
*/
public AgentAuthToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
// must use super, as we override
super.setAuthenticated(true);
}

@Override
public Object getCredentials() {
return this.credentials;
}

@Override
public Object getPrincipal() {
return this.principal;
}

@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}

@Override
public void eraseCredentials() {
super.eraseCredentials();
}

}

+ 48
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/agent/AgentLoginUserDetailService.java Целия файл

@@ -0,0 +1,48 @@
package com.ningdatech.pmapi.user.security.auth.agent;


import com.ningdatech.pmapi.user.manage.UserAuthLoginManage;
import com.ningdatech.pmapi.user.security.auth.model.UserFullInfoDTO;
import com.ningdatech.pmapi.user.security.auth.model.UserInfoDetails;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.Objects;

/**
* @author LiuXinXin
* @date 2022/9/30 上午9:49
*/

@Service("agentLoginUserDetailService")
@RequiredArgsConstructor
public class AgentLoginUserDetailService implements UserDetailsService {

private final UserAuthLoginManage userAuthLoginManage;

@Override
public UserInfoDetails loadUserByUsername(String username) throws UsernameNotFoundException {

final Long userId = Long.parseLong(username);

UserFullInfoDTO userFullInfoDTO = userAuthLoginManage.getUserFullInfo(userId);

if (Objects.isNull(userFullInfoDTO)) {
throw new UsernameNotFoundException(String.format("%s user not exist", username));
}
UserInfoDetails userInfoDetails = new UserInfoDetails();
userInfoDetails.setUserId(userFullInfoDTO.getUserId());
userInfoDetails.setUsername(userFullInfoDTO.getUsername());
userInfoDetails.setRealName(userFullInfoDTO.getRealName());
userInfoDetails.setUserRoleList(userFullInfoDTO.getUserRoleList());
userInfoDetails.setRegionCode(userFullInfoDTO.getRegionCode());
userInfoDetails.setIdentifier(userFullInfoDTO.getIdentifier());
userInfoDetails.setPassword(userFullInfoDTO.getCredential());

userInfoDetails.setOrganizationCode(userFullInfoDTO.getOrganizationCode());
userInfoDetails.setOrganizationName(userFullInfoDTO.getOrganizationName());
return userInfoDetails;
}
}

+ 1
- 1
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultLoginSuccessHandler.java Целия файл

@@ -29,7 +29,7 @@ public class DefaultLoginSuccessHandler extends SavedRequestAwareAuthenticationS
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().write(objectMapper.writeValueAsString(ApiResponse.ofSuccess()));
response.getWriter().write(objectMapper.writeValueAsString(ApiResponse.ofMessage(ApiResponse.SUCCESS_MSG)));
}

}

+ 5
- 2
pmapi/src/main/java/com/ningdatech/pmapi/user/service/IUserInfoService.java Целия файл

@@ -1,7 +1,7 @@
package com.ningdatech.pmapi.user.service;

import com.ningdatech.pmapi.user.entity.UserInfo;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ningdatech.pmapi.user.entity.UserInfo;
import com.wflow.workflow.bean.dto.ProcessInstanceUserDto;

import java.util.Map;
@@ -9,7 +9,7 @@ import java.util.Set;

/**
* <p>
* 服务类
* 服务类
* </p>
*
* @author Lierbao
@@ -19,4 +19,7 @@ public interface IUserInfoService extends IService<UserInfo> {
Map<String, ProcessInstanceUserDto> getUserMapByIds(Set<String> staterUsers);

ProcessInstanceUserDto getUserInfo(String userId);

UserInfo getUserInfoByPhoneNo(String phoneNo);

}

+ 17
- 4
pmapi/src/main/java/com/ningdatech/pmapi/user/service/impl/UserInfoServiceImpl.java Целия файл

@@ -12,6 +12,7 @@ import com.ningdatech.pmapi.user.mapper.NdUserInfoMapper;
import com.ningdatech.pmapi.user.service.IUserInfoService;
import com.wflow.workflow.bean.dto.ProcessInstanceUserDto;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.util.List;
@@ -22,7 +23,7 @@ import java.util.stream.Collectors;

/**
* <p>
* 服务实现类
* 服务实现类
* </p>
*
* @author Lierbao
@@ -36,6 +37,7 @@ public class UserInfoServiceImpl extends ServiceImpl<NdUserInfoMapper, UserInfo>
private final IDingEmployeeInfoService dingEmployeeInfoService;

private final IDingOrganizationService dingOrganizationService;

@Override
public Map<String, ProcessInstanceUserDto> getUserMapByIds(Set<String> staterUsers) {
List<UserInfo> userInfos = userInfoMapper.selectBatchIds(staterUsers);
@@ -44,13 +46,13 @@ public class UserInfoServiceImpl extends ServiceImpl<NdUserInfoMapper, UserInfo>
userInfo.setUserId(String.valueOf(u.getId()));
userInfo.setUserName(u.getRealName());
Long accountId = u.getAccountId();
if (Objects.isNull(accountId)){
if (Objects.isNull(accountId)) {
throw new BizException("该用户没有录入浙政钉用户信息!");
}
// 根据浙政钉用户ID获取单位code
DingEmployeeInfo employeeInfo = dingEmployeeInfoService.getOne(Wrappers.lambdaQuery(DingEmployeeInfo.class)
.eq(DingEmployeeInfo::getAccountId, accountId)
.eq(DingEmployeeInfo::getMainJob,String.valueOf(Boolean.TRUE))
.eq(DingEmployeeInfo::getMainJob, String.valueOf(Boolean.TRUE))
.last("limit 1"));
String organizationCode = employeeInfo.getOrganizationCode();
// 根据 单位code获取单位名称
@@ -70,7 +72,7 @@ public class UserInfoServiceImpl extends ServiceImpl<NdUserInfoMapper, UserInfo>
processInstanceUserDto.setUserId(String.valueOf(userInfo.getId()));
processInstanceUserDto.setUserName(userInfo.getRealName());
Long accountId = userInfo.getAccountId();
if (Objects.isNull(accountId)){
if (Objects.isNull(accountId)) {
throw new BizException("该用户没有录入浙政钉用户信息!");
}
// 根据浙政钉用户ID获取部门code
@@ -85,4 +87,15 @@ public class UserInfoServiceImpl extends ServiceImpl<NdUserInfoMapper, UserInfo>
processInstanceUserDto.setOrgName(organizationName);
return processInstanceUserDto;
}

@Override
public UserInfo getUserInfoByPhoneNo(String phoneNo) {
if (StringUtils.isEmpty(phoneNo)) {
return null;
}
UserInfo userInfo = userInfoMapper
.selectOne(Wrappers.lambdaQuery(UserInfo.class)
.eq(UserInfo::getMobile, phoneNo));
return userInfo;
}
}

+ 9
- 0
pmapi/src/main/resources/application-dev.yml Целия файл

@@ -193,3 +193,12 @@ provincial:
detailUrl: /api/v1/foreign/importantProView
key: 7196317343a64e67895dc0375c098fe7
secret: 75152a97f20e4c4c854dc6301cf72ad4


irs:
seal-platform:
project-id: 1
project-secret: 2
access-key: 3
secret-key: 4
api-url: https://ibcdsg.zj.gov.cn:8443/restapi/prod/IC33000020220309000004/seal-platform/seal/v1/rest/sign/signPdf

+ 3
- 0
pmapi/src/main/resources/security/auth-dev.yml Целия файл

@@ -3,6 +3,7 @@ security:
auth-require-url: /api/v1/user/auth/auth-require
invalid-session-url: /api/v1/user/auth/invalid-session
password-login-url: /api/v1/user/auth/login
agent-login-url: /api/v1/user/auth/agent-login
logout-url: /api/v1/user/auth/logout
ignore-auth-urls:
- /v2/api-docs
@@ -21,6 +22,7 @@ security:
- /oa/**
- /wflow/**
- /sys/**
- /api/v1/verification/**
ignore-csrf-urls:
- /api/v1/user/auth/**
- /v2/api-docs
@@ -37,6 +39,7 @@ security:
- /oa/**
- /wflow/**
- /sys/**
- /api/v1/verification/**
role-map:
"engineer":
"project_manager":


Loading…
Отказ
Запис