diff --git a/safety-eval-adapter/src/main/java/org/qinan/safetyeval/adapter/web/OrgPersonnelController.java b/safety-eval-adapter/src/main/java/org/qinan/safetyeval/adapter/web/OrgPersonnelController.java index 0c6ad0b2..21705fc8 100644 --- a/safety-eval-adapter/src/main/java/org/qinan/safetyeval/adapter/web/OrgPersonnelController.java +++ b/safety-eval-adapter/src/main/java/org/qinan/safetyeval/adapter/web/OrgPersonnelController.java @@ -61,6 +61,13 @@ public class OrgPersonnelController { return orgPersonnelApi.page(query); } + @ApiOperation("监管端评价师查询人员信息") + @GetMapping("/regulationPage") + public PageResponse regulationPage(@Validated OrgPersonnelPageQuery query) { + query.setEvaluatorList(true); + return orgPersonnelApi.page(query); + } + @ApiOperation("班级选择人员分页查询") @GetMapping("/classSelect/page") public PageResponse classSelectPage(@Validated ClassSelectPersonnelPageQuery query) { diff --git a/safety-eval-app/src/main/java/org/qinan/safetyeval/app/executor/OrgPersonnelExecutor.java b/safety-eval-app/src/main/java/org/qinan/safetyeval/app/executor/OrgPersonnelExecutor.java index b4185e64..d8852181 100644 --- a/safety-eval-app/src/main/java/org/qinan/safetyeval/app/executor/OrgPersonnelExecutor.java +++ b/safety-eval-app/src/main/java/org/qinan/safetyeval/app/executor/OrgPersonnelExecutor.java @@ -34,8 +34,10 @@ import org.qinan.safetyeval.infrastructure.adapter.ThreadLocalUserInfoAdapter; import org.qinan.safetyeval.infrastructure.adapter.auth.AuthUserContextAdapter; import org.qinan.safetyeval.infrastructure.adapter.gbs.GbsUserFacadeClient; import org.qinan.safetyeval.infrastructure.convertor.OrgPersonnelCertConvertor; +import org.qinan.safetyeval.infrastructure.dataobject.OrgInfoDO; import org.qinan.safetyeval.infrastructure.dataobject.OrgPersonnelDO; import org.qinan.safetyeval.infrastructure.dataobject.OrgPersonnelDeptCountDO; +import org.qinan.safetyeval.infrastructure.mapper.OrgInfoMapper; import org.qinan.safetyeval.infrastructure.mapper.OrgPersonnelMapper; import org.qinan.safetyeval.infrastructure.mapper.QualFilingPersonnelCertMapper; import org.qinan.safetyeval.infrastructure.support.UserOrgIdResolver; @@ -54,10 +56,7 @@ import org.springframework.web.multipart.MultipartFile; import java.math.BigDecimal; import java.time.LocalDate; import java.time.temporal.ChronoUnit; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.stream.Collectors; /** @@ -122,6 +121,8 @@ public class OrgPersonnelExecutor implements OrgPersonnelApi { private boolean gbsUserSyncFailFast; @Value("${template.personnel.import.url:}") private String personImportTemplateUrl; + @Autowired + private OrgInfoMapper orgInfoMapper; @Override @@ -241,21 +242,35 @@ public class OrgPersonnelExecutor implements OrgPersonnelApi { domainQuery.setPostId(query.getPostId()); domainQuery.setUserName(query.getUserName()); domainQuery.setAccount(query.getAccount()); + domainQuery.setRegisterEngineerFlag(query.getRegisterEngineerFlag()); domainQuery.setEmploymentStatusCode(query.getEmploymentStatusCode()); + domainQuery.setEvaluatorList(query.getEvaluatorList()); if (query.getResignAuditStatus() != null && orgResignApplyGateway != null) { domainQuery.setPersonnelIds(orgResignApplyGateway.listPersonnelIdsByAuditStatus( query.getResignAuditStatus(), query.getOrgId())); } PageResult pageResult = orgPersonnelDomainService.page(domainQuery); - - List records = pageResult.getRecords().stream() - .map(item -> orgPersonnelConvertor.toCO(item)) - .collect(java.util.stream.Collectors.toList()); + Map orgNameMap; + if (pageResult != null && !CollectionUtils.isEmpty(pageResult.getRecords())) { + List orgIds = pageResult.getRecords().stream().map(OrgPersonnelEntity::getOrgId).filter(Objects::nonNull).collect(Collectors.toList()); + orgNameMap = orgInfoMapper.selectBatchIds(CollectionUtils.isEmpty(orgIds) ? Collections.singletonList(0) : orgIds).stream().collect(Collectors.toMap(OrgInfoDO::getId, OrgInfoDO::getUnitName)); + } else { + orgNameMap = new HashMap<>(); + } + List records = null; + if (pageResult != null) { + records = pageResult.getRecords().stream() + .map(item -> { + OrgPersonnelCO co = orgPersonnelConvertor.toCO(item); + co.setOrgName(orgNameMap.getOrDefault(item.getOrgId(), "")); + return co; + }) + .collect(Collectors.toList()); + } if (orgPersonnelViewEnricher != null) { orgPersonnelViewEnricher.enrichPersonnelBatch(records); } - return PageResponse.of(records, pageResult.getTotal()); } diff --git a/safety-eval-app/src/main/java/org/qinan/safetyeval/app/executor/institution/InstitutionDashboardExecutor.java b/safety-eval-app/src/main/java/org/qinan/safetyeval/app/executor/institution/InstitutionDashboardExecutor.java index 68e25c31..112ddddc 100644 --- a/safety-eval-app/src/main/java/org/qinan/safetyeval/app/executor/institution/InstitutionDashboardExecutor.java +++ b/safety-eval-app/src/main/java/org/qinan/safetyeval/app/executor/institution/InstitutionDashboardExecutor.java @@ -1,9 +1,8 @@ package org.qinan.safetyeval.app.executor.institution; -import org.qinan.safetyeval.app.executor.support.EvalProjectNodeOverviewAssembler; +import com.alibaba.fastjson2.JSON; import org.qinan.safetyeval.client.api.institution.InstitutionDashboardApi; -import org.qinan.safetyeval.client.co.institution.EvalProjectNodeItemCO; -import org.qinan.safetyeval.client.co.institution.EvalProjectNodeOverviewCO; +import org.qinan.safetyeval.client.co.institution.ProjectNodeCO; import org.qinan.safetyeval.client.co.institution.dashboard.InstitutionDashboardEvalTypeRatioCO; import org.qinan.safetyeval.client.co.institution.dashboard.InstitutionDashboardIndustryStatCO; import org.qinan.safetyeval.client.co.institution.dashboard.InstitutionDashboardNoticeCO; @@ -11,6 +10,7 @@ import org.qinan.safetyeval.client.co.institution.dashboard.InstitutionDashboard import org.qinan.safetyeval.client.co.institution.dashboard.InstitutionDashboardProjectNodeStatsCO; import org.qinan.safetyeval.client.dto.SingleResponse; import org.qinan.safetyeval.domain.constant.EvalTypeEnum; +import org.qinan.safetyeval.domain.constant.ProjectStageEnum; import org.qinan.safetyeval.domain.constant.basic.IndustryEnum; import org.qinan.safetyeval.domain.entity.EvalCustomerEntity; import org.qinan.safetyeval.domain.entity.EvalProjectEntity; @@ -27,11 +27,13 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import javax.annotation.Resource; +import java.time.LocalDate; import java.util.ArrayList; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; /** @@ -47,9 +49,6 @@ public class InstitutionDashboardExecutor implements InstitutionDashboardApi { @Resource private EvalProjectGateway evalProjectGateway; - @Resource - private EvalProjectNodeOverviewAssembler evalProjectNodeOverviewAssembler; - @Resource private SafetyMessageGateway safetyMessageGateway; @@ -81,52 +80,19 @@ public class InstitutionDashboardExecutor implements InstitutionDashboardApi { @Override public SingleResponse projectNodeStats() { - Long orgId = ThreadLocalUserInfoAdapter.getOrgId(); - List projects = listOrgProjects(orgId); - - int total = projects.size(); - int statutory = (int) projects.stream().filter(p -> Boolean.TRUE.equals(p.getIsStatutory())).count(); - - // 节点待办数(待办=节点状态为「未开始」) - LinkedHashMap pending = new LinkedHashMap<>(); - for (String code : NODE_DISPLAY.keySet()) { - pending.put(code, 0); + Long orgId = institutionOrgSupport.resolveCurrentOrgIdByUserId(); + if (orgId == null) { + return SingleResponse.success(new InstitutionDashboardProjectNodeStatsCO()); } - for (EvalProjectEntity p : projects) { - try { - EvalProjectNodeOverviewCO ov = evalProjectNodeOverviewAssembler.buildForInstitution(p.getId()); - if (ov == null || ov.getNodes() == null) { - continue; - } - for (EvalProjectNodeItemCO n : ov.getNodes()) { - String code = n.getNodeCode(); - if (code != null && pending.containsKey(code) && "未开始".equals(n.getStatusName())) { - pending.put(code, pending.get(code) + 1); - } - } - } catch (Exception e) { - // 单项目装配异常忽略,保证其余节点统计可用 - } - } - - List nodeStats = new ArrayList<>(); - for (Map.Entry entry : NODE_DISPLAY.entrySet()) { - InstitutionDashboardProjectNodeStatsCO.NodeStat ns = new InstitutionDashboardProjectNodeStatsCO.NodeStat(); - ns.setNodeCode(entry.getKey()); - ns.setNodeName(entry.getValue()); - ns.setPendingCount(pending.getOrDefault(entry.getKey(), 0)); - nodeStats.add(ns); - } - - int archived = (int) projects.stream().filter(p -> Boolean.TRUE.equals(p.getArchiveFlag())).count(); - int delayed = (int) evalProjectGateway.countByOrgIdAndProgressStatus(orgId, "DELAY"); + // 只查询统计所需最小字段:id、project_node、plan_end_date、archive_flag、is_statutory + List projects = evalProjectGateway.listNodeStatFieldsByOrgId(orgId); InstitutionDashboardProjectNodeStatsCO co = new InstitutionDashboardProjectNodeStatsCO(); - co.setTotalProjects(total); - co.setStatutoryProjects(statutory); - co.setNodeStats(nodeStats); - co.setArchivedProjectCount(archived); - co.setDelayedProjectCount(delayed); + co.setTotalProjects(projects.size()); + co.setStatutoryProjects((int) projects.stream().filter(p -> Boolean.TRUE.equals(p.getIsStatutory())).count()); + co.setArchivedProjectCount((int) projects.stream().filter(p -> Objects.equals(p.getArchiveFlag(), 1)).count()); + co.setDelayedProjectCount(countDelayed(projects)); + co.setNodeStats(buildNodeStats(projects)); return SingleResponse.success(co); } @@ -249,6 +215,70 @@ public class InstitutionDashboardExecutor implements InstitutionDashboardApi { // ============================ 通用方法 ============================ + /** + * 统计各流程节点待办数(待办=project_node JSON 中该节点 state=0 未完成) + * + * @param projects 机构项目列表 + * @return 8 个节点的待办统计 + */ + private List buildNodeStats(List projects) { + LinkedHashMap pending = new LinkedHashMap<>(); + for (String code : NODE_DISPLAY.keySet()) { + pending.put(code, 0); + } + for (EvalProjectEntity p : projects) { + for (ProjectNodeCO n : parseNodes(p.getProjectNode())) { + ProjectStageEnum stage = ProjectStageEnum.fromVal(n.getType()); + if (stage != null && NODE_DISPLAY.containsKey(stage.getNode()) + && Integer.valueOf(0).equals(n.getState())) { + pending.put(stage.getNode(), pending.get(stage.getNode()) + 1); + } + } + } + + List nodeStats = new ArrayList<>(); + for (Map.Entry entry : NODE_DISPLAY.entrySet()) { + InstitutionDashboardProjectNodeStatsCO.NodeStat ns = new InstitutionDashboardProjectNodeStatsCO.NodeStat(); + ns.setNodeCode(entry.getKey()); + ns.setNodeName(entry.getValue()); + ns.setPendingCount(pending.getOrDefault(entry.getKey(), 0)); + nodeStats.add(ns); + } + return nodeStats; + } + + /** + * 解析 project_node JSON([{"type":"RISK_ANALYSIS","state":0}]),空值/异常返回空列表保证统计可用 + * + * @param projectNodeJson 节点进度 JSON 字符串 + * @return 节点列表 + */ + private List parseNodes(String projectNodeJson) { + if (projectNodeJson == null || projectNodeJson.isEmpty()) { + return new ArrayList<>(); + } + try { + List nodes = JSON.parseArray(projectNodeJson, ProjectNodeCO.class); + return nodes != null ? nodes : new ArrayList<>(); + } catch (Exception e) { + return new ArrayList<>(); + } + } + + /** + * 统计延期项目数(延期=计划结束日期早于今天且未归档) + * + * @param projects 机构项目列表 + * @return 延期项目数 + */ + private int countDelayed(List projects) { + LocalDate today = LocalDate.now(); + return (int) projects.stream() + .filter(p -> p.getPlanEndDate() != null && p.getPlanEndDate().isBefore(today) + && !Objects.equals(p.getArchiveFlag(), 1)) + .count(); + } + private List listOrgProjects(Long orgId) { EvalProjectQuery q = new EvalProjectQuery(); q.setOrgId(orgId); diff --git a/safety-eval-client/src/main/java/org/qinan/safetyeval/client/co/OrgPersonnelCO.java b/safety-eval-client/src/main/java/org/qinan/safetyeval/client/co/OrgPersonnelCO.java index 7f89602d..2b173199 100644 --- a/safety-eval-client/src/main/java/org/qinan/safetyeval/client/co/OrgPersonnelCO.java +++ b/safety-eval-client/src/main/java/org/qinan/safetyeval/client/co/OrgPersonnelCO.java @@ -1,5 +1,7 @@ package org.qinan.safetyeval.client.co; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import org.qinan.safetyeval.domain.entity.CapabilityAssessmentDTO; @@ -20,6 +22,7 @@ public class OrgPersonnelCO { private Long id; @ApiModelProperty(value = "部门ID") + @JsonSerialize(using = ToStringSerializer.class) private Long deptId; @ApiModelProperty(value = "岗位ID") @@ -168,4 +171,9 @@ public class OrgPersonnelCO { @ApiModelProperty(value = "租户ID") private Long tenantId; + + @ApiModelProperty(value = "人员状态: 1正常,2异常") + private Integer personnelStatus = 1; + @ApiModelProperty(value = "机构名称") + private String orgName; } diff --git a/safety-eval-client/src/main/java/org/qinan/safetyeval/client/dto/OrgPersonnelPageQuery.java b/safety-eval-client/src/main/java/org/qinan/safetyeval/client/dto/OrgPersonnelPageQuery.java index 5d4c4c8d..9279ed28 100644 --- a/safety-eval-client/src/main/java/org/qinan/safetyeval/client/dto/OrgPersonnelPageQuery.java +++ b/safety-eval-client/src/main/java/org/qinan/safetyeval/client/dto/OrgPersonnelPageQuery.java @@ -22,7 +22,7 @@ public class OrgPersonnelPageQuery extends BasePageQuery { @ApiModelProperty(value = "岗位id") private Long postId; - @ApiModelProperty(value = "用户名称/姓名") + @ApiModelProperty(value = "用户名称/姓名/评价师名称") private String userName; @ApiModelProperty(value = "账号") @@ -36,4 +36,10 @@ public class OrgPersonnelPageQuery extends BasePageQuery { @ApiModelProperty(value = "是否员工变更管理页面") private Boolean employeeChange = false; + + @ApiModelProperty(value = "是否注册安全工程师(1是2否)") + private Integer registerEngineerFlag; + + @ApiModelProperty(value = "评价师列表") + private Boolean evaluatorList = false; } diff --git a/safety-eval-domain/src/main/java/org/qinan/safetyeval/domain/gateway/EvalProjectGateway.java b/safety-eval-domain/src/main/java/org/qinan/safetyeval/domain/gateway/EvalProjectGateway.java index 743d45f3..6839318a 100644 --- a/safety-eval-domain/src/main/java/org/qinan/safetyeval/domain/gateway/EvalProjectGateway.java +++ b/safety-eval-domain/src/main/java/org/qinan/safetyeval/domain/gateway/EvalProjectGateway.java @@ -4,6 +4,8 @@ import org.qinan.safetyeval.domain.entity.EvalProjectEntity; import org.qinan.safetyeval.domain.query.EvalProjectQuery; import org.qinan.safetyeval.domain.query.PageResult; +import java.util.List; + public interface EvalProjectGateway { EvalProjectEntity save(EvalProjectEntity entity); @@ -25,4 +27,12 @@ public interface EvalProjectGateway { void editProjectNode(Long projectId, String val); EvalProjectEntity findByControlledNumber(String controlledNumber); + + /** + * 按机构查询节点统计所需的最小字段集合(id、project_node、plan_end_date、archive_flag、is_statutory) + * + * @param orgId 机构id + * @return 项目实体列表(仅统计所需字段有值) + */ + List listNodeStatFieldsByOrgId(Long orgId); } diff --git a/safety-eval-domain/src/main/java/org/qinan/safetyeval/domain/query/OrgPersonnelQuery.java b/safety-eval-domain/src/main/java/org/qinan/safetyeval/domain/query/OrgPersonnelQuery.java index c5bf4342..b949a742 100644 --- a/safety-eval-domain/src/main/java/org/qinan/safetyeval/domain/query/OrgPersonnelQuery.java +++ b/safety-eval-domain/src/main/java/org/qinan/safetyeval/domain/query/OrgPersonnelQuery.java @@ -1,5 +1,6 @@ package org.qinan.safetyeval.domain.query; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** @@ -42,4 +43,9 @@ public class OrgPersonnelQuery { /** 人员 ID 列表(离职审核状态筛选) */ private java.util.List personnelIds; + @ApiModelProperty(value = "是否注册安全工程师(1是2否)") + private Integer registerEngineerFlag; + @ApiModelProperty(value = "评价师列表") + private Boolean evaluatorList = false; + } diff --git a/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/gatewayimpl/EvalProjectGatewayImpl.java b/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/gatewayimpl/EvalProjectGatewayImpl.java index 44ac0783..529da2ff 100644 --- a/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/gatewayimpl/EvalProjectGatewayImpl.java +++ b/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/gatewayimpl/EvalProjectGatewayImpl.java @@ -142,6 +142,21 @@ public class EvalProjectGatewayImpl implements EvalProjectGateway { return dataObject == null ? null : evalProjectDoConvertor.convertDoToE(dataObject); } + @Override + public List listNodeStatFieldsByOrgId(Long orgId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + // 只取统计所需最小字段,减少查询与转换开销 + wrapper.select(EvalProjectDO::getId, EvalProjectDO::getProjectNode, EvalProjectDO::getPlanEndDate, + EvalProjectDO::getArchiveFlag, EvalProjectDO::getIsStatutory) + .eq(EvalProjectDO::getDeleteEnum, "false"); + if (orgId != null) { + wrapper.eq(EvalProjectDO::getOrgId, orgId); + } + return evalProjectMapper.selectList(wrapper).stream() + .map(evalProjectDoConvertor::convertDoToE) + .collect(Collectors.toList()); + } + @Override public void editProjectNode(Long projectId, String val) { EvalProjectDO evalProjectDO = evalProjectMapper.selectById(projectId); diff --git a/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/gatewayimpl/OrgPersonnelGatewayImpl.java b/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/gatewayimpl/OrgPersonnelGatewayImpl.java index d990d8c6..44649c62 100644 --- a/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/gatewayimpl/OrgPersonnelGatewayImpl.java +++ b/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/gatewayimpl/OrgPersonnelGatewayImpl.java @@ -153,6 +153,13 @@ public class OrgPersonnelGatewayImpl implements OrgPersonnelGateway { if (query.getEmploymentStatusCode() != null) { wrapper.eq(OrgPersonnelDO::getEmploymentStatusCode, query.getEmploymentStatusCode()); } + if (query.getRegisterEngineerFlag() != null) { + wrapper.eq(OrgPersonnelDO::getRegisterEngineerFlag, query.getRegisterEngineerFlag()); + } + if (query.getEvaluatorList()) { + // 查看有能力,专职人员 + wrapper.isNotNull(OrgPersonnelDO::getCapabilityAssessment); + } if (query.getPersonnelIds() != null) { if (query.getPersonnelIds().isEmpty()) { wrapper.eq(OrgPersonnelDO::getId, -1L); diff --git a/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/support/UserOrgIdResolver.java b/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/support/UserOrgIdResolver.java index 97e49aa5..9299e490 100644 --- a/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/support/UserOrgIdResolver.java +++ b/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/support/UserOrgIdResolver.java @@ -1,6 +1,9 @@ package org.qinan.safetyeval.infrastructure.support; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.qinan.safetyeval.infrastructure.dataobject.OrgInfoDO; import org.qinan.safetyeval.infrastructure.dataobject.OrgPersonnelDO; +import org.qinan.safetyeval.infrastructure.mapper.OrgInfoMapper; import org.qinan.safetyeval.infrastructure.mapper.OrgPersonnelMapper; import org.springframework.stereotype.Component; @@ -32,6 +35,9 @@ public class UserOrgIdResolver { @Resource private OrgPersonnelMapper orgPersonnelMapper; + @Resource + private OrgInfoMapper orgInfoMapper; + private final Map cache = new ConcurrentHashMap<>(); /** @@ -72,7 +78,25 @@ public class UserOrgIdResolver { private Long queryOrgIdByUserId(Long userId) { OrgPersonnelDO personnel = orgPersonnelMapper.selectById(userId); - return personnel == null ? null : personnel.getOrgId(); + if (personnel != null) { + return personnel.getOrgId(); + } + // 兜底:用户可能是机构注册人(org_info.create_id = userId),取其创建的机构 + return queryOrgIdByCreator(userId); + } + + /** + * 根据创建人 id 反查其创建的机构 orgId。 + * + * @param userId 创建人 id(org_info.create_id) + * @return 机构 orgId;未创建过机构返回 null + */ + private Long queryOrgIdByCreator(Long userId) { + OrgInfoDO orgInfo = orgInfoMapper.selectOne(new LambdaQueryWrapper() + .select(OrgInfoDO::getId) + .eq(OrgInfoDO::getCreateId, userId) + .last("limit 1")); + return orgInfo == null ? null : orgInfo.getId(); } private static class CacheEntry {