Commit 6e1055f0 by 穆龙飞

Merge branch 'release-260708' into 'master'

分支release-260708已上线,自动合并到master

See merge request !32
parents e3e0dac2 b67d96bd
Pipeline #93889 failed with stage
in 6 seconds
ALTER TABLE `TB_YX_QC_CHANGE_EXEC_RECORD` ADD COLUMN `sub_flow_id` varchar(100) NOT NULL DEFAULT '' COMMENT '子流程工单ID' AFTER `change_exec_finish_desc`,
ADD COLUMN `sub_flow_record_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '变更行动工单记录ID(关联到变更行动工单中间表,新数据使用)' AFTER `sub_flow_id`,
ADD INDEX `idx_sub_flow_record_id` (`sub_flow_record_id`) USING BTREE COMMENT '变更行动工单记录ID索引',
ADD INDEX `idx_change_exec_user_email` (`change_exec_user_email`) USING BTREE COMMENT '变更行动人邮箱索引';
ALTER TABLE `TB_YX_QC_CHANGE_RECORD` ADD COLUMN `approver` varchar(500) NOT NULL DEFAULT '' COMMENT '变更审批人(JSON格式,存储当前节点的审批人列表,用于查询过滤)',
ADD COLUMN `batch_description` VARCHAR(512) NOT NULL DEFAULT '' COMMENT '批次说明' AFTER `change_profit_amount`;
CREATE TABLE `TB_YX_QC_CHANGE_SUB_FLOW_RECORD`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`change_record_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '主单记录ID(变更记录ID)',
`sub_flow_id` varchar(100) NOT NULL DEFAULT '' COMMENT '变更行动工单ID(即topo)',
`sub_flow_node` varchar(20) NOT NULL DEFAULT '' COMMENT '变更行动工单当前节点',
`status` int(2) NOT NULL DEFAULT '0' COMMENT '变更行动工单状态,1待确认行动方案,2待审批行动方案,3待审批变更方案,4待提交执行结果,5已完结,6已取消',
`approver` varchar(500) NOT NULL DEFAULT '' COMMENT '变更行动工单审批人(JSON格式,存储审批人列表,第一次新建时就是行动人)',
`change_exec_user_email` varchar(70) NOT NULL DEFAULT '' COMMENT '变更行动人邮箱(用于查询和展示,新建时从行动项获取)',
`change_exec_department` varchar(50) NOT NULL DEFAULT '' COMMENT '变更行动部门(用于查询和展示,新建时从行动项获取)',
`change_commander` varchar(70) NOT NULL DEFAULT '' COMMENT '变更负责人(用于查询和展示,新建时从主单获取)',
`reject_reason` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '拒绝原因',
`change_result` INT(2) NOT NULL DEFAULT 0 COMMENT '变更结论(0-未设置/1-完成所有/2-部分完成/3-取消/4-延期)',
`remark` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注',
`cancel_reason` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '取消原因',
`change_confirm_result_time` bigint(20) NOT NULL DEFAULT '0' COMMENT '变更确认结果时间(从主单创建时继承,延期时可覆盖)',
`create_time` bigint(20) NOT NULL DEFAULT '0' COMMENT '创建时间',
`update_time` bigint(20) NOT NULL DEFAULT '0' COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_change_record_id` (`change_record_id`) USING BTREE COMMENT '主单记录ID索引',
KEY `idx_sub_flow_id` (`sub_flow_id`) USING BTREE COMMENT '变更行动工单ID索引',
KEY `idx_status` (`status`) USING BTREE COMMENT '状态索引',
KEY `idx_change_exec_user_email` (`change_exec_user_email`) USING BTREE COMMENT '变更行动人邮箱索引',
KEY `idx_change_commander` (`change_commander`) USING BTREE COMMENT '变更负责人索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='变更行动工单记录表';
CREATE TABLE `TB_YX_QC_CHANGE_SUB_FLOW_FILE`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`sub_flow_record_id` bigint(20) NOT NULL COMMENT '子单记录ID',
`file_name` varchar(255) NOT NULL DEFAULT '' COMMENT '文件名',
`file_url` varchar(500) NOT NULL DEFAULT '' COMMENT '文件URL',
`file_type` int(11) NOT NULL DEFAULT '0' COMMENT '文件类型(参考 FileTypeEnum)',
`create_time` bigint(20) NOT NULL COMMENT '创建时间',
`update_time` bigint(20) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_sub_flow_record_id` (`sub_flow_record_id`),
KEY `idx_file_type` (`file_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='变更行动工单附件表';
\ No newline at end of file
package com.netease.mail.yanxuan.change.biz.biz;
import com.netease.mail.yanxuan.change.biz.meta.exception.ExceptionFactory;
import com.netease.mail.yanxuan.change.common.bean.ResponseCode;
import com.netease.mail.yanxuan.change.common.util.DateUtils;
import com.netease.mail.yanxuan.change.dal.entity.ChangeExecRecord;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeExecConfigReq;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.stream.Collectors;
/**
* 变更行动项业务逻辑
* @author system
*/
@Component
@Slf4j
public class ChangeExecRecordBiz {
/**
* 根据 ChangeExecConfigReq 列表构建 ChangeExecRecord 列表
*
* @param changeRecordId 变更记录ID
* @param changeExecProjectList 变更行动方案列表
* @param subFlowId 子流程工单ID(可选,主单场景传 null)
* @param subFlowRecordId 变更行动工单记录ID(可选,主单场景传 null)
* @return 变更行动项列表
*/
public List<ChangeExecRecord> buildChangeExecRecord(Long changeRecordId,
List<ChangeExecConfigReq> changeExecProjectList,
String subFlowId,
Long subFlowRecordId) {
return changeExecProjectList.stream().map(c -> {
ChangeExecRecord changeExecRecord = new ChangeExecRecord();
changeExecRecord.setChangeRecordId(changeRecordId);
changeExecRecord.setChangeExecDepartment(c.getChangeExecDepartment());
changeExecRecord.setChangeExecUserType(c.getChangeExecUserType());
changeExecRecord.setChangeExecUser(c.getChangeExecUser());
changeExecRecord.setChangeExecUserEmail(c.getChangeExecUserEmail());
changeExecRecord.setChangeRiskDesc(c.getChangeRiskDesc());
changeExecRecord.setChangeExecProject(c.getChangeExecProject());
changeExecRecord.setChangeChecking(c.getChangeChecking());
changeExecRecord.setChangeExecFinishTime(c.getChangeExecFinishTime());
changeExecRecord.setChangeExecFinishDesc(c.getChangeExecFinishDesc());
changeExecRecord.setSubFlowId(subFlowId);
changeExecRecord.setSubFlowRecordId(subFlowRecordId);
changeExecRecord.setCreateTime(DateUtils.getCurrentTime());
changeExecRecord.setUpdateTime(DateUtils.getCurrentTime());
return changeExecRecord;
}).collect(Collectors.toList());
}
/**
* 校验行动项的必填字段
*
* @param changeExecProjectList 行动项列表
* @param checkTime
*/
public void validateChangeExecProjectRequiredFields(List<ChangeExecConfigReq> changeExecProjectList, boolean checkTime) {
if (CollectionUtils.isEmpty(changeExecProjectList)) {
return;
}
changeExecProjectList.forEach(c -> {
if (StringUtils.isBlank(c.getChangeExecUserEmail())) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "行动项中变更行动人邮箱不能为空");
}
if (StringUtils.isBlank(c.getChangeRiskDesc())) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "行动项中变更风险描述不能为空");
}
if (StringUtils.isBlank(c.getChangeExecProject())) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "行动项中行动项内容不能为空");
}
if (checkTime && (c.getChangeExecFinishTime() == null || c.getChangeExecFinishTime() <= 0)) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "行动项中变更完成时间不能为空");
}
});
}
}
package com.netease.mail.yanxuan.change.biz.biz;
import com.alibaba.fastjson.JSON;
import com.netease.mail.yanxuan.change.biz.config.AppConfig;
import com.netease.mail.yanxuan.change.biz.meta.exception.ExceptionFactory;
import com.netease.mail.yanxuan.change.biz.service.ChangeFlowExecService;
import com.netease.mail.yanxuan.change.biz.service.ChangeFlowService;
import com.netease.mail.yanxuan.change.biz.service.ChangeSubFlowFileService;
import com.netease.mail.yanxuan.change.biz.service.ChangeSubFlowRecordService;
import com.netease.mail.yanxuan.change.biz.service.ChangeSubFlowService;
import com.netease.mail.yanxuan.change.biz.service.rpc.FlowService;
import com.netease.mail.yanxuan.change.biz.service.rpc.IusService;
import com.netease.mail.yanxuan.change.biz.service.rpc.SupplierSendService;
import com.netease.mail.yanxuan.change.integration.flow.ius.IusRpcService;
import com.netease.mail.yanxuan.change.common.bean.CommonConstants;
import com.netease.mail.yanxuan.change.biz.util.PageUtils;
import com.netease.mail.yanxuan.change.common.bean.RequestLocalBean;
import com.netease.mail.yanxuan.change.common.bean.ResponseCode;
import com.netease.mail.yanxuan.change.common.constants.FlowTransitionType;
import com.netease.mail.yanxuan.change.common.enums.ChangeFlowEnum;
import com.netease.mail.yanxuan.change.common.enums.ChangeResultEnum;
import com.netease.mail.yanxuan.change.common.enums.ChangeStatusEnum;
import com.netease.mail.yanxuan.change.common.enums.ChangeSubFlowStatusEnum;
import com.netease.mail.yanxuan.change.common.enums.ChangeSubjectEnum;
import com.netease.mail.yanxuan.change.common.enums.CreateSourceEnum;
import com.netease.mail.yanxuan.change.common.enums.FileTypeEnum;
import com.netease.mail.yanxuan.change.common.enums.FlowOperationTypeEnum;
import com.netease.mail.yanxuan.change.common.enums.FlowxOperationEnum;
import com.netease.mail.yanxuan.change.integration.email.enums.EmailTemplateEnum;
import com.netease.mail.yanxuan.change.common.util.DateUtils;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.netease.mail.yanxuan.change.dal.entity.ChangeExecRecord;
import com.netease.mail.yanxuan.change.dal.entity.ChangeRecord;
import com.netease.mail.yanxuan.change.dal.entity.ChangeSubFlowFile;
import com.netease.mail.yanxuan.change.dal.entity.ChangeSubFlowRecord;
import com.netease.mail.yanxuan.change.dal.mapper.ChangeRecordMapper;
import com.netease.mail.yanxuan.change.dal.mapper.ChangeSubFlowRecordMapper;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeExecConfigReq;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeFlowFile;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeFlowListQueryReq;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeSubFlowListQueryReq;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeSubFlowSubmitReq;
import com.netease.mail.yanxuan.change.dal.meta.model.vo.ChangeFlowExecVO;
import com.netease.mail.yanxuan.change.dal.meta.model.vo.ChangeFlowVO;
import com.netease.mail.yanxuan.change.dal.meta.model.vo.ChangeSubFlowListVO;
import com.netease.mail.yanxuan.change.dal.meta.model.vo.ChangeSubFlowOwnershipVO;
import com.netease.mail.yanxuan.change.dal.meta.model.vo.ChangeSubFlowVO;
import com.netease.mail.yanxuan.change.dal.meta.model.vo.PageVO;
import com.netease.mail.yanxuan.change.integration.flow.UserQueryDTO;
import com.netease.mail.yanxuan.change.integration.flow.ius.rsp.IusUserInfoRsp;
import com.netease.yanxuan.flowx.sdk.meta.controller.communal.AjaxResponse;
import com.netease.yanxuan.flowx.sdk.meta.dto.base.FlowDataDTO;
import com.netease.yanxuan.flowx.sdk.meta.dto.base.UserReachDTO;
import com.netease.yanxuan.flowx.sdk.meta.dto.exec.InterfaceInputDTO;
import com.netease.yanxuan.flowx.sdk.meta.dto.exec.UserBaseContainerDTO;
import com.netease.yanxuan.flowx.sdk.meta.dto.flow.FlowCreateReqDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author TmindA
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class ChangeSubFlowBiz {
private final ChangeSubFlowService changeSubFlowService;
private final FlowService flowService;
private final ChangeFlowExecService changeFlowExecService;
private final ChangeFlowService changeFlowService;
private final ChangeSubFlowRecordService changeSubFlowRecordService;
private final ChangeSubFlowRecordMapper changeSubFlowRecordMapper;
private final ChangeRecordMapper changeRecordMapper;
private final ChangeFlowBiz changeFlowBiz;
private final ChangeExecRecordBiz changeExecRecordBiz;
private final DepartmentLeaderBiz departmentLeaderBiz;
private final IusRpcService iusRpcService;
private final IusService iusService;
private final AppConfig appConfig;
private final ChangeSubFlowFileService changeSubFlowFileService;
private final SupplierSendService sendSupplierEmail;
public UserBaseContainerDTO getOperator(InterfaceInputDTO interfaceInput) {
log.info("[getOperator] interfaceInput:{}", JSON.toJSONString(interfaceInput));
// 根据工单subFlowId获取审批人,创建工单时设置,唯一
String subFlowId = interfaceInput.getPublicFieldDTO().getFlowMeta().getFlowId();
String nodeId = interfaceInput.getPublicFieldDTO().getFlowMeta().getCurrNodeDataList().get(0).getNodeId();
log.info("[getOperator] subFlowId:{}, nodeId:{}", subFlowId, nodeId);
ChangeSubFlowRecord changeSubFlowRecord;
changeSubFlowRecord = changeSubFlowRecordService.getBySubFlowId(subFlowId);
log.info("[getOperator] first time get changeSubFlowRecord:{}", changeSubFlowRecord);
// 创建工单后落库,此时审批人还未落库,工单平台无法查询到审批人,方法休眠500ms
if (changeSubFlowRecord == null) {
try {
Thread.sleep(500);
changeSubFlowRecord = changeSubFlowRecordService.getBySubFlowId(subFlowId);
log.info("[getOperator] second time get changeSubFlowRecord:{}", changeSubFlowRecord);
} catch (Exception e) {
log.info("[getOperator] subFlowId:{}, nodeId:{}, e={}", subFlowId, nodeId, e);
}
}
// 校验节点是否匹配,避免节点已变更但approver还未更新的情况
int maxRetries = 2;
int retryCount = 0;
while (changeSubFlowRecord != null && !nodeId.equals(changeSubFlowRecord.getSubFlowNode()) && retryCount < maxRetries) {
log.warn("[getOperator] subFlowId:{}, nodeId:{}, subFlowNode:{}, 节点不匹配,等待重试,retryCount:{}",
subFlowId, nodeId, changeSubFlowRecord.getSubFlowNode(), retryCount);
try {
Thread.sleep(300);
changeSubFlowRecord = changeSubFlowRecordService.getBySubFlowId(subFlowId);
retryCount++;
} catch (Exception e) {
log.error("[getOperator] subFlowId:{}, nodeId:{}, 重试异常, e={}", subFlowId, nodeId, e);
break;
}
}
if (changeSubFlowRecord != null && !nodeId.equals(changeSubFlowRecord.getSubFlowNode())) {
log.error("[getOperator] subFlowId:{}, nodeId:{}, subFlowNode:{}, 节点不匹配,已重试{}次,返回当前审批人",
subFlowId, nodeId, changeSubFlowRecord.getSubFlowNode(), retryCount);
}
if (changeSubFlowRecord == null) {
log.error("[getOperator] subFlowId:{}, nodeId:{}, 子工单记录不存在", subFlowId, nodeId);
UserBaseContainerDTO userBaseContainer = new UserBaseContainerDTO();
userBaseContainer.setUserList(new ArrayList<>());
return userBaseContainer;
}
// 从approver字段获取审批人列表,反序列化JSON字符串
String approverJson = changeSubFlowRecord.getApprover();
List<UserReachDTO> totalUserList = new ArrayList<>();
if (StringUtils.isNotBlank(approverJson)) {
List<String> approvers = JSON.parseArray(approverJson, String.class);
if (CollectionUtils.isNotEmpty(approvers)) {
for (String approver : approvers) {
UserReachDTO userReachDTO = new UserReachDTO();
// 查询邮箱对应的名字
IusUserInfoRsp user = iusService.queryUserInfo(approver);
// 流程拓扑图,如果没查询到名字,以邮箱兜底
userReachDTO.setUserName(
user == null || StringUtils.isBlank(user.getName()) ? approver : user.getName());
userReachDTO.setUid(approver);
totalUserList.add(userReachDTO);
}
}
}
UserBaseContainerDTO userBaseContainer = new UserBaseContainerDTO();
userBaseContainer.setUserList(totalUserList);
return userBaseContainer;
}
//public String createAndSubmit(@Valid ChangeSubFlowCreateReq changeSubFlowCreateReq) {
// String uid = RequestLocalBean.getUid();
// String name = RequestLocalBean.getName();
// Map<String, Object> content = new HashMap<>(10);
// content.put("createUserName", uid);
// content.put("createUser", uid);
// content.put("createTime", System.currentTimeMillis());
// content.put("updateTime", System.currentTimeMillis());
// content.put(CommonConstants.FLOW_OPERATION_KEY, FlowOperationTypeEnum.PASS.getValue());
// String flowName = "";
// // 组装工单创建数据
// FlowCreateReqDTO flowCreateReqDTO = buildFlowCreateReqDTO(ChangeFlowEnum.CHANGE_SUB_FLOW.getTopoId(), uid,
// JSON.toJSONString(content), FlowxOperationEnum.CREATE.getName(), name, flowName);
// // 创建工单
// String subFlowId = flowService.createFlow(flowCreateReqDTO);
//
// ChangeExecRecord execRecord = new ChangeExecRecord();
// changeSubFlowService.createSubFlow(execRecord);
// return "";
//}
private FlowCreateReqDTO buildFlowCreateReqDTO(String topoId, String uid, String content, String operateResult,
String name, String flowName) {
FlowCreateReqDTO flowCreateReqDTO = new FlowCreateReqDTO();
flowCreateReqDTO.setTopoId(topoId);
flowCreateReqDTO.setUid(uid);
flowCreateReqDTO.setUserName(name);
flowCreateReqDTO.setOperateResult(operateResult);
flowCreateReqDTO.setWorkOrderId(StringUtils.joinWith("-", topoId, UUID.randomUUID().toString()));
flowCreateReqDTO.setContent(content);
flowCreateReqDTO.setFlowName(flowName);
return flowCreateReqDTO;
}
public List<ChangeExecRecord> list(Integer page, Integer pageSize) {
return changeSubFlowService.query((page - 1) * pageSize, pageSize);
}
/**
* 子流程提交
* @param changeSubFlowSubmitReq 子流程提交请求
* @return 下一节点ID
*/
public String submitSubFlow(ChangeSubFlowSubmitReq changeSubFlowSubmitReq) {
log.info("[submitSubFlow] changeSubFlowSubmitReq:{}", JSON.toJSONString(changeSubFlowSubmitReq));
String subFlowId = changeSubFlowSubmitReq.getSubFlowId();
// 通过子流程工单ID查询行动工单记录
ChangeSubFlowRecord subFlowRecord = changeSubFlowRecordService.getBySubFlowId(subFlowId);
Assert.notNull(subFlowRecord, "行动工单记录不存在");
// 检查工单节点(从行动工单获取)
String currentNode = subFlowRecord.getSubFlowNode();
this.checkNode(currentNode, Collections.singletonList(changeSubFlowSubmitReq.getCurrentNodeId()));
String uid = RequestLocalBean.getUid();
// 验证执行人权限:检查当前用户是否在行动工单的审批人列表中(JSON格式:["user1@example.com", "user2@example.com"])
//if (StringUtils.isBlank(subFlowRecord.getApprover())
// || !subFlowRecord.getApprover().contains("\"" + uid + "\"")) {
// throw ExceptionFactory.createBiz(ResponseCode.NO_AUTH, ResponseCode.NO_AUTH.getMsg());
//}
// 获取工单详情
FlowDataDTO flowDataDTO = flowService.flowDetail(subFlowId);
if (flowDataDTO == null) {
throw ExceptionFactory.createBiz(ResponseCode.DETAIL_FLOW_ERROR, "子流程工单查询错误,不存在");
}
return checkUpdateAndSubmit(subFlowId, flowDataDTO, uid, subFlowRecord, currentNode, changeSubFlowSubmitReq);
}
/**
* 检查并提交子流程节点
*/
private String checkUpdateAndSubmit(String subFlowId, FlowDataDTO flowDataDTO, String uid,
ChangeSubFlowRecord subFlowRecord, String currentNode, ChangeSubFlowSubmitReq changeSubFlowSubmitReq) {
ChangeFlowEnum node = ChangeFlowEnum.getByNodeId(currentNode);
Assert.notNull(node, "节点配置不存在");
// 记录当前节点(用于日志对比)
String oldNodeId = currentNode;
String oldNodeName = node.getName();
log.info("[checkUpdateAndSubmit] 子单流转开始 >>> subFlowId:{}, 当前节点ID:{}, 当前节点名称:{}",
subFlowId, oldNodeId, oldNodeName);
log.debug("[checkUpdateAndSubmit] 提交参数:{}", JSON.toJSONString(changeSubFlowSubmitReq));
// 构建工单内容
Map<String, Object> content = buildFlowContent();
switch (node) {
case CHANGE_SUB_FLOW_START:
// 确认行动方案节点:删除旧的行动项,重新创建
List<ChangeExecConfigReq> changeExecProjectList = changeSubFlowSubmitReq.getChangeExecProjectList();
// 校验行动项列表不能为空
if (CollectionUtils.isEmpty(changeExecProjectList)) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "行动项列表不能为空");
}
// 校验行动项列表:所有行动项必须是同一个部门+人的组合,且与子单记录一致
String expectedDepartment = subFlowRecord.getChangeExecDepartment();
String expectedUserEmail = subFlowRecord.getChangeExecUserEmail();
// 校验所有行动项的必填字段和部门+邮箱必须与子单记录一致
changeExecProjectList.forEach(execConfig -> {
String dept = execConfig.getChangeExecDepartment();
String userEmail = execConfig.getChangeExecUserEmail();
if (StringUtils.isBlank(dept) || StringUtils.isBlank(userEmail)) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST,
"行动项中部门或行动人邮箱不能为空");
}
if (!expectedDepartment.equals(dept) || !expectedUserEmail.equals(userEmail)) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST,
String.format("行动项的部门+人组合必须与子单记录一致。子单记录:部门[%s],行动人[%s];当前行动项:部门[%s],行动人[%s]",
expectedDepartment, expectedUserEmail, dept, userEmail));
}
});
// 校验所有行动项的必填字段(使用公共方法)
changeExecRecordBiz.validateChangeExecProjectRequiredFields(changeExecProjectList, true);
// 删除子单下所有行动项
changeFlowExecService.deleteBySubFlowRecordId(subFlowRecord.getId());
// 根据前端传递的行动项列表重新创建
List<ChangeExecRecord> newExecRecords = changeExecRecordBiz.buildChangeExecRecord(
subFlowRecord.getChangeRecordId(), changeExecProjectList, subFlowId, subFlowRecord.getId());
newExecRecords.forEach(changeFlowExecService::saveRecord);
// 检查是否需要审批,通过paramMap控制流转方向
Map<String, Object> paramMap = new HashMap<>();
String nextNodeId;
if (needApprove(expectedDepartment)) {
// 需要审批:流转到审批行动方案节点,设置上级领导为审批人
log.info("[CHANGE_SUB_FLOW_START] 部门[{}]需要审批,等待上级审批", expectedDepartment);
paramMap.put("type", FlowTransitionType.TYPE_APPROVED);
// 提交到审批行动方案节点
nextNodeId = flowService.submitFlowWithParamMap(subFlowId, flowDataDTO, uid,
ChangeFlowEnum.CHANGE_SUB_FLOW.getTopoId(), JSON.toJSONString(content), paramMap,
FlowxOperationEnum.SUBMIT.getName(), "提交工单", subFlowRecord.getCreateTime());
// 更新子单记录
subFlowRecord.setSubFlowNode(nextNodeId);
subFlowRecord.setStatus(ChangeSubFlowStatusEnum.WAIT_APPROVE_ACTION_PLAN.getStatus());
subFlowRecord.setUpdateTime(DateUtils.getCurrentTime());
// 获取上级领导并设置为审批人
String execUserEmail = subFlowRecord.getChangeExecUserEmail();
if (StringUtils.isBlank(execUserEmail)) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "行动人邮箱不能为空");
}
String leaderEmail = departmentLeaderBiz.getDepartmentLeader(execUserEmail);
if (StringUtils.isBlank(leaderEmail)) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST,
String.format("行动人[%s]的部门上级领导不存在", execUserEmail));
}
subFlowRecord.setApprover(JSON.toJSONString(Collections.singletonList(leaderEmail)));
log.info("[checkUpdateAndSubmit] 子单流转完成 <<< subFlowId:{}, 原节点:{}-{}, 新节点:{}-审批行动方案",
subFlowId, oldNodeId, oldNodeName, nextNodeId);
} else {
// 不需要审批:直接跳到审批变更方案节点,审批人保持为行动人自己
log.info("[CHANGE_SUB_FLOW_START] 部门[{}]不需要审批,自动跳过审批", expectedDepartment);
paramMap.put("type", FlowTransitionType.TYPE_SKIP);
// 提交到审批变更方案节点
nextNodeId = flowService.submitFlowWithParamMap(subFlowId, flowDataDTO, uid,
ChangeFlowEnum.CHANGE_SUB_FLOW.getTopoId(), JSON.toJSONString(content), paramMap,
FlowxOperationEnum.SUBMIT.getName(), "提交工单", subFlowRecord.getCreateTime());
// 更新子单记录
subFlowRecord.setSubFlowNode(nextNodeId);
subFlowRecord.setStatus(ChangeSubFlowStatusEnum.WAIT_APPROVE_CHANGE_PLAN.getStatus());
subFlowRecord.setUpdateTime(DateUtils.getCurrentTime());
// todo:非名单中部门无需审批,行动人确认提交后邮件同步负责人上级对应变更信息
log.info("[checkUpdateAndSubmit] 子单流转完成 <<< subFlowId:{}, 原节点:{}-{}, 新节点:{}-审批变更方案 (自动跳过审批)",
subFlowId, oldNodeId, oldNodeName, nextNodeId);
}
changeSubFlowRecordService.update(subFlowRecord);
return nextNodeId;
case CHANGE_SUB_FLOW_SUBMIT:
// 审批行动方案节点
Boolean approved = changeSubFlowSubmitReq.getApproved();
if (approved == null) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "审批结果不能为空");
}
// 审批不通过时,拒绝原因必填
if (!approved) {
String rejectReason = changeSubFlowSubmitReq.getRejectReason();
if (StringUtils.isBlank(rejectReason)) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "拒绝原因不能为空");
}
subFlowRecord.setRejectReason(rejectReason);
log.info("[CHANGE_SUB_FLOW_SUBMIT] 审批不通过,subFlowId:{}, rejectReason:{}", subFlowId, rejectReason);
}
// 使用paramMap控制流转方向(通过/不通过)
Map<String, Object> approveParamMap = new HashMap<>();
approveParamMap.put("type", approved ? FlowTransitionType.TYPE_APPROVED : FlowTransitionType.TYPE_BACK);
String remark = approved ? "审批通过" : "审批不通过";
String approveNodeId = flowService.submitFlowWithParamMap(subFlowId, flowDataDTO, uid,
ChangeFlowEnum.CHANGE_SUB_FLOW.getTopoId(), JSON.toJSONString(content), approveParamMap,
FlowxOperationEnum.SUBMIT.getName(), remark, subFlowRecord.getCreateTime());
// 更新行动工单
subFlowRecord.setSubFlowNode(approveNodeId);
subFlowRecord.setStatus(approved
? ChangeSubFlowStatusEnum.WAIT_APPROVE_CHANGE_PLAN.getStatus()
: ChangeSubFlowStatusEnum.WAIT_CONFIRM_ACTION_PLAN.getStatus());
subFlowRecord.setUpdateTime(DateUtils.getCurrentTime());
changeSubFlowRecordService.update(subFlowRecord);
String approveNodeName = approved ? "审批变更方案" : "确认行动方案";
log.info("[checkUpdateAndSubmit] 子单流转完成 <<< subFlowId:{}, 原节点:{}-{}, 新节点:{}-{}, 审批结果:{}",
subFlowId, oldNodeId, oldNodeName, approveNodeId, approveNodeName, approved ? "通过" : "不通过");
return approveNodeId;
case CHANGE_SUB_FLOW_EXE:
// 审批变更方案节点
Boolean execApproved = changeSubFlowSubmitReq.getApproved();
if (execApproved == null) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "审批结果不能为空");
}
// 使用paramMap控制流转方向
Map<String, Object> execParamMap = new HashMap<>();
String execNodeId;
if (execApproved) {
// 审批通过:流转到提交执行结果节点
log.info("[CHANGE_SUB_FLOW_EXE] 审批通过,subFlowId:{}", subFlowId);
execParamMap.put("type", FlowTransitionType.TYPE_APPROVED);
// 提交到提交执行结果节点
execNodeId = flowService.submitFlowWithParamMap(subFlowId, flowDataDTO, uid,
ChangeFlowEnum.CHANGE_SUB_FLOW.getTopoId(), JSON.toJSONString(content), execParamMap,
FlowxOperationEnum.SUBMIT.getName(), "审批通过", subFlowRecord.getCreateTime());
// 更新子单记录
subFlowRecord.setSubFlowNode(execNodeId);
subFlowRecord.setStatus(ChangeSubFlowStatusEnum.WAIT_SUBMIT_RESULT.getStatus());
subFlowRecord.setUpdateTime(DateUtils.getCurrentTime());
changeSubFlowRecordService.update(subFlowRecord);
log.info("[checkUpdateAndSubmit] 子单流转完成 <<< subFlowId:{}, 原节点:{}-{}, 新节点:{}-提交执行结果, 审批结果:通过",
subFlowId, oldNodeId, oldNodeName, execNodeId);
// 检查所有子单是否都已到达提交执行结果节点,如果是则流转主工单
if (checkAllSubFlowsApproved(subFlowRecord.getChangeRecordId())) {
submitMainFlowAfterAllSubFlowsApproved(subFlowRecord.getChangeRecordId());
log.info("[CHANGE_SUB_FLOW_EXE] 所有子单均已到达提交执行结果节点,主工单已流转");
}
} else {
// 审批不通过:回退到确认行动方案节点,拒绝原因必填
String execRejectReason = changeSubFlowSubmitReq.getRejectReason();
if (StringUtils.isBlank(execRejectReason)) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "拒绝原因不能为空");
}
subFlowRecord.setRejectReason(execRejectReason);
log.info("[CHANGE_SUB_FLOW_EXE] 审批不通过,subFlowId:{}, rejectReason:{}", subFlowId, execRejectReason);
execParamMap.put("type", FlowTransitionType.TYPE_BACK);
// 提交到确认行动方案节点
execNodeId = flowService.submitFlowWithParamMap(subFlowId, flowDataDTO, uid,
ChangeFlowEnum.CHANGE_SUB_FLOW.getTopoId(), JSON.toJSONString(content), execParamMap,
FlowxOperationEnum.SUBMIT.getName(), "审批不通过", subFlowRecord.getCreateTime());
// 更新子单记录
subFlowRecord.setSubFlowNode(execNodeId);
subFlowRecord.setStatus(ChangeSubFlowStatusEnum.CANCELLED.getStatus());
subFlowRecord.setUpdateTime(DateUtils.getCurrentTime());
changeSubFlowRecordService.update(subFlowRecord);
log.info("[checkUpdateAndSubmit] 子单流转完成 <<< subFlowId:{}, 原节点:{}-{}, 新节点:{}-确认行动方案, 审批结果:不通过",
subFlowId, oldNodeId, oldNodeName, execNodeId);
}
return execNodeId;
case CHANGE_SUB_FLOW_CONFIRM:
// 提交执行结果节点,填写变更结论
// 判断主单节点必须是NEW_CHANGE_FLOW_EXE,否则不允许提交
ChangeRecord mainRecordForCheck = changeRecordMapper.selectByPrimaryKey(subFlowRecord.getChangeRecordId());
if (mainRecordForCheck == null) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "主工单不存在");
}
String mainFlowNode = mainRecordForCheck.getFlowNode();
if (!ChangeFlowEnum.NEW_CHANGE_FLOW_EXE.getNodeId().equals(mainFlowNode)) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST,
String.format("主工单未到达执行变更方案节点,当前主单节点:%s,不允许提交执行结果", mainFlowNode));
}
Integer subFlowChangeResult = changeSubFlowSubmitReq.getChangeResult();
ChangeResultEnum changeResultEnum = ChangeResultEnum.getByValue(subFlowChangeResult);
if (changeResultEnum == null) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "变更结论类型错误");
}
// 填写变更结论和备注到子单记录
subFlowRecord.setChangeResult(subFlowChangeResult);
subFlowRecord.setRemark(changeSubFlowSubmitReq.getRemark());
// 处理子单的变更结果文件(所有结论类型都需要保存文件)
List<ChangeFlowFile> subFlowResultFiles = changeSubFlowSubmitReq.getChangeResultFiles();
if (CollectionUtils.isNotEmpty(subFlowResultFiles)) {
// 先删除旧文件
changeSubFlowFileService.deleteBySubFlowRecordIdAndType(subFlowRecord.getId(), FileTypeEnum.CHANGE_RESULT_INFO);
// 保存新文件
List<ChangeSubFlowFile> fileList = buildChangeFileRecordForSubFlow(
subFlowRecord.getId(), subFlowResultFiles, FileTypeEnum.CHANGE_RESULT_INFO.getType());
fileList.forEach(changeSubFlowFileService::saveRecord);
}
switch (changeResultEnum) {
case FINISH_ALL:
case FINISH_PART:
return handleSubFlowFinish(subFlowRecord, changeSubFlowSubmitReq, flowDataDTO, uid, content, changeResultEnum, oldNodeId, oldNodeName);
case CANCEL:
return handleSubFlowCancel(subFlowRecord, changeSubFlowSubmitReq, flowDataDTO, uid, content, oldNodeId, oldNodeName);
case DELAY:
return handleSubFlowDelay(subFlowRecord, changeSubFlowSubmitReq, oldNodeId, oldNodeName);
default:
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "不支持的变更结论类型");
}
default:
log.warn("[checkUpdateAndSubmit] 不支持的子流程节点类型:subFlowId:{}, nodeId:{}, 节点名称:{}",
subFlowId, currentNode, node.getName());
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "不支持的子流程节点类型");
}
}
/**
* 检查节点是否匹配
*/
private void checkNode(String currentNode, List<String> expectedNodes) {
if (!expectedNodes.contains(currentNode)) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST,
String.format("当前节点[%s]与预期节点[%s]不匹配", currentNode, expectedNodes));
}
}
/**
* 判断部门是否应该跳过审批
*
* @param department 部门名称
* @return true 表示应该跳过审批(不在需要审批的部门列表中)
*/
/**
* 判断部门是否需要审批
*
* @param department 部门名称
* @return true 表示需要审批(在配置的部门列表中)
*/
private boolean needApprove(String department) {
if (StringUtils.isBlank(department)) {
return false;
}
List<String> needApproveDepts = appConfig.getNeedApproveDepartments();
// 如果没有配置或配置为空,默认都不需要审批
if (CollectionUtils.isEmpty(needApproveDepts)) {
return false;
}
// 检查部门是否在需要审批的列表中
return needApproveDepts.stream()
.filter(StringUtils::isNotBlank)
.map(String::trim)
.anyMatch(dept -> dept.equals(department.trim()));
}
/**
* 处理子单完成(FINISH_ALL / FINISH_PART)
*/
private String handleSubFlowFinish(ChangeSubFlowRecord subFlowRecord, ChangeSubFlowSubmitReq req,
FlowDataDTO flowDataDTO, String uid, Object content, ChangeResultEnum resultStatus,
String oldNodeId, String oldNodeName) {
// 提交子单流程到结束节点,使用 paramMap 方式,type=1 表示通过
Map<String, Object> finishParamMap = new HashMap<>();
finishParamMap.put("type", FlowTransitionType.TYPE_APPROVED);
String endNodeId = flowService.submitFlowWithParamMap(subFlowRecord.getSubFlowId(), flowDataDTO, uid,
ChangeFlowEnum.CHANGE_SUB_FLOW.getTopoId(), JSON.toJSONString(content), finishParamMap,
FlowxOperationEnum.SUBMIT.getName(), "提交执行结果", subFlowRecord.getCreateTime());
// 更新子单状态为FINISHED
subFlowRecord.setSubFlowNode(endNodeId);
subFlowRecord.setStatus(ChangeSubFlowStatusEnum.FINISHED.getStatus());
subFlowRecord.setUpdateTime(DateUtils.getCurrentTime());
changeSubFlowRecordService.update(subFlowRecord);
log.info("[handleSubFlowFinish] 子单完成,subFlowId:{}, result:{}", subFlowRecord.getSubFlowId(), resultStatus);
log.info("[checkUpdateAndSubmit] 子单流转完成 <<< subFlowId:{}, 原节点:{}-{}, 新节点:{}-结束, 变更结论:{}",
subFlowRecord.getSubFlowId(), oldNodeId, oldNodeName, endNodeId, resultStatus.getDesc());
// 发送邮件
sendSubFlowFinishEmail(subFlowRecord);
// 检查所有子单是否都已完成,如果是则流转主工单
if (checkAllSubFlowsFinishedForMainFlow(subFlowRecord.getChangeRecordId())) {
submitMainFlowAfterAllSubFlowsFinished(subFlowRecord.getChangeRecordId());
log.info("[handleSubFlowFinish] 所有子单均已完成,主工单已流转");
}
return endNodeId;
}
/**
* 处理子单取消(CANCEL)
*/
private String handleSubFlowCancel(ChangeSubFlowRecord subFlowRecord, ChangeSubFlowSubmitReq req,
FlowDataDTO flowDataDTO, String uid, Object content,
String oldNodeId, String oldNodeName) {
// 1. 取消原因必填
if (StringUtils.isBlank(req.getCancelReason())) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "变更取消原因不可为空");
}
// 2. 保存取消原因
subFlowRecord.setCancelReason(req.getCancelReason());
// 3. 提交子单流程到结束节点,使用 paramMap 方式,type=1 表示通过
Map<String, Object> cancelParamMap = new HashMap<>();
cancelParamMap.put("type", FlowTransitionType.TYPE_APPROVED);
String cancelNodeId = flowService.submitFlowWithParamMap(subFlowRecord.getSubFlowId(), flowDataDTO, uid,
ChangeFlowEnum.CHANGE_SUB_FLOW.getTopoId(), JSON.toJSONString(content), cancelParamMap,
FlowxOperationEnum.SUBMIT.getName(), "取消工单", subFlowRecord.getCreateTime());
// 5. 更新子单状态为CANCELLED
subFlowRecord.setSubFlowNode(cancelNodeId);
subFlowRecord.setStatus(ChangeSubFlowStatusEnum.CANCELLED.getStatus());
subFlowRecord.setUpdateTime(DateUtils.getCurrentTime());
changeSubFlowRecordService.update(subFlowRecord);
log.info("[handleSubFlowCancel] 子单已取消,subFlowId:{}, reason:{}", subFlowRecord.getSubFlowId(), req.getCancelReason());
log.info("[checkUpdateAndSubmit] 子单流转完成 <<< subFlowId:{}, 原节点:{}-{}, 新节点:{}-结束, 变更结论:取消",
subFlowRecord.getSubFlowId(), oldNodeId, oldNodeName, cancelNodeId);
// 发送邮件
sendSubFlowFinishEmail(subFlowRecord);
// 检查所有子单是否都已完成,如果是则流转主工单
if (checkAllSubFlowsFinishedForMainFlow(subFlowRecord.getChangeRecordId())) {
submitMainFlowAfterAllSubFlowsFinished(subFlowRecord.getChangeRecordId());
log.info("[handleSubFlowCancel] 所有子单均已完成,主工单已流转");
}
return cancelNodeId;
}
/**
* 处理子单延期(DELAY)
*/
private String handleSubFlowDelay(ChangeSubFlowRecord subFlowRecord, ChangeSubFlowSubmitReq req,
String oldNodeId, String oldNodeName) {
// 验证延期时间
Long changeConfirmResultTime = req.getChangeConfirmResultTime();
Long tomorrowSpecificTime = DateUtils.getTomorrowSpecificTime("00:00:00");
if (changeConfirmResultTime == null || changeConfirmResultTime < tomorrowSpecificTime) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "时间不可晚于下次执行时间");
}
// 更新子单延期时间,不流转工单
subFlowRecord.setChangeConfirmResultTime(changeConfirmResultTime);
subFlowRecord.setUpdateTime(DateUtils.getCurrentTime());
changeSubFlowRecordService.update(subFlowRecord);
// todo:延期需要重新发邮件,改变执行邮件状态
log.info("[handleSubFlowDelay] 子单延期,subFlowId:{}, delayTime:{}", subFlowRecord.getSubFlowId(), changeConfirmResultTime);
log.info("[checkUpdateAndSubmit] 子单延期(不流转)<<< subFlowId:{}, 节点保持:{}-{}, 延期时间:{}",
subFlowRecord.getSubFlowId(), oldNodeId, oldNodeName, changeConfirmResultTime);
return null;
}
/**
* 构建子单文件记录
*
* @param subFlowRecordId 子单ID
* @param files 文件列表
* @param type 文件类型
* @return 文件记录列表
*/
private List<ChangeSubFlowFile> buildChangeFileRecordForSubFlow(Long subFlowRecordId,
List<ChangeFlowFile> files, Integer type) {
return files.stream().map(f -> {
ChangeSubFlowFile file = new ChangeSubFlowFile();
file.setSubFlowRecordId(subFlowRecordId);
file.setFileType(type);
file.setFileName(f.getFileName());
file.setFileUrl(f.getFileUrl());
file.setCreateTime(DateUtils.getCurrentTime());
file.setUpdateTime(DateUtils.getCurrentTime());
return file;
}).collect(Collectors.toList());
}
/**
* 检查主工单下所有子单是否都已到达提交执行结果节点
* 条件:status = WAIT_SUBMIT_RESULT 且 subFlowNode = CHANGE_SUB_FLOW_CONFIRM
*
* @param changeRecordId 主工单ID
* @return true 表示所有子单都已到达提交执行结果节点
*/
private boolean checkAllSubFlowsApproved(Long changeRecordId) {
// 查询该主工单下所有子单
List<ChangeSubFlowRecord> allSubFlows = changeSubFlowRecordMapper.selectByChangeRecordId(changeRecordId);
if (CollectionUtils.isEmpty(allSubFlows)) {
return false;
}
// 检查是否所有子单都满足条件:status = WAIT_SUBMIT_RESULT 且 subFlowNode = CHANGE_SUB_FLOW_CONFIRM
return allSubFlows.stream().allMatch(subFlow ->
ChangeSubFlowStatusEnum.WAIT_SUBMIT_RESULT.getStatus().equals(subFlow.getStatus())
&& ChangeFlowEnum.CHANGE_SUB_FLOW_CONFIRM.getNodeId().equals(subFlow.getSubFlowNode())
);
}
/**
* 构建工单提交内容(包含 updateTime 和 FLOW_OPERATION_KEY)
*
* @return 工单提交内容 Map
*/
private Map<String, Object> buildFlowContent() {
Map<String, Object> content = new HashMap<>(CommonConstants.INIT_HASH_MAP_SIZE);
content.put("updateTime", System.currentTimeMillis());
content.put(CommonConstants.FLOW_OPERATION_KEY, FlowOperationTypeEnum.PASS.getValue());
return content;
}
/**
* 所有子单审批变更方案通过后,流转主工单到部门负责人审批节点
*
* @param changeRecordId 主工单ID
*/
private void submitMainFlowAfterAllSubFlowsApproved(Long changeRecordId) {
// 查询主工单
ChangeRecord changeRecord = changeRecordMapper.selectByPrimaryKey(changeRecordId);
if (changeRecord == null) {
log.error("[submitMainFlowAfterAllSubFlowsApproved] 主工单不存在,changeRecordId:{}", changeRecordId);
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "主工单不存在");
}
// 验证主工单当前节点
String currentNode = changeRecord.getFlowNode();
if (!ChangeFlowEnum.NEW_CHANGE_FLOW_CONFIRM_EXEC_PLAN.getNodeId().equals(currentNode)) {
throw ExceptionFactory.createBiz(ResponseCode.NODE_ERROR,
String.format("主工单当前节点不是确认变更方案节点,无法流转。当前节点:%s", currentNode));
}
try {
Long flowId = changeRecord.getFlowId();
String uid = RequestLocalBean.getUid();
if (StringUtils.isBlank(uid)) {
uid = "system";
}
// 获取工单详情
FlowDataDTO flowDataDTO = flowService.flowDetail(flowId.toString());
if (flowDataDTO == null) {
throw ExceptionFactory.createBiz(ResponseCode.DETAIL_FLOW_ERROR, "工单查询错误,不存在");
}
// 构建提交内容
Map<String, Object> content = buildFlowContent();
// 使用paramMap控制流转方向(所有子单审批通过)
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("type", FlowTransitionType.TYPE_APPROVED);
// 流转主工单到部门负责人审批节点
String ownerApproveNodeId = flowService.submitFlowWithParamMap(flowId.toString(), flowDataDTO, uid,
ChangeFlowEnum.NEW_CHANGE_FLOW.getTopoId(), JSON.toJSONString(content), paramMap,
FlowxOperationEnum.SUBMIT.getName(), "所有子单审批通过,主工单流转", changeRecord.getCreateTime());
changeRecord.setFlowNode(ownerApproveNodeId);
changeRecord.setState(ChangeStatusEnum.WAIT_DEPT_LEADER_APPROVE.getStatus());
changeRecord.setUpdateTime(DateUtils.getCurrentTime());
// 获取变更负责人的上级领导并设置为审批人
String changeCommander = changeRecord.getChangeCommander();
if (StringUtils.isBlank(changeCommander)) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "变更负责人邮箱不能为空");
}
String leaderEmail = departmentLeaderBiz.getDepartmentLeader(changeCommander);
if (StringUtils.isBlank(leaderEmail)) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST,
String.format("变更负责人[%s]的部门上级领导不存在", changeCommander));
}
// 设置审批人为上级领导(JSON 数组格式)
changeRecord.setApprover(JSON.toJSONString(Collections.singletonList(leaderEmail)));
changeFlowService.updateRecord(changeRecord);
log.info("[submitMainFlowAfterAllSubFlowsApproved] 主工单流转成功,flowId:{}, nextNodeId:{}, approver:{}",
flowId, ownerApproveNodeId, leaderEmail);
} catch (Exception e) {
log.error("[submitMainFlowAfterAllSubFlowsApproved] 主工单流转失败,changeRecordId:{}", changeRecordId, e);
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST,
String.format("主工单流转失败:%s", e.getMessage()));
}
}
/**
* 所有子单执行完成后,流转主工单到确认执行结果节点
*
* @param changeRecordId 主工单ID
*/
private void submitMainFlowAfterAllSubFlowsFinished(Long changeRecordId) {
// 查询主工单
ChangeRecord changeRecord = changeRecordMapper.selectByPrimaryKey(changeRecordId);
if (changeRecord == null) {
log.error("[submitMainFlowAfterAllSubFlowsFinished] 主工单不存在,changeRecordId:{}", changeRecordId);
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "主工单不存在");
}
// 验证主工单当前节点
String currentNode = changeRecord.getFlowNode();
if (!ChangeFlowEnum.NEW_CHANGE_FLOW_EXE.getNodeId().equals(currentNode)) {
log.warn("[submitMainFlowAfterAllSubFlowsFinished] 主工单当前节点不是执行变更方案节点,无法流转。当前节点:{}", currentNode);
return;
}
try {
Long flowId = changeRecord.getFlowId();
String uid = RequestLocalBean.getUid();
if (StringUtils.isBlank(uid)) {
uid = "system";
}
String name = RequestLocalBean.getName();
if (StringUtils.isBlank(name)) {
name = "系统";
}
// 获取工单详情
FlowDataDTO flowDataDTO = flowService.flowDetail(flowId.toString());
if (flowDataDTO == null) {
throw ExceptionFactory.createBiz(ResponseCode.DETAIL_FLOW_ERROR, "工单查询错误,不存在");
}
// 构建提交内容
Map<String, Object> content = buildFlowContent();
// 使用paramMap控制流转方向(所有子单执行结果完成)
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("type", FlowTransitionType.TYPE_APPROVED);
// 流转主工单到确认变更结果节点
String confirmNodeId = flowService.submitFlowWithParamMap(flowId.toString(), flowDataDTO, uid,
ChangeFlowEnum.NEW_CHANGE_FLOW.getTopoId(), JSON.toJSONString(content), paramMap,
FlowxOperationEnum.SUBMIT.getName(), "所有子单执行结果完成,主工单流转", changeRecord.getCreateTime());
changeRecord.setFlowNode(confirmNodeId);
changeRecord.setState(ChangeStatusEnum.WAIT_CONFIRM_EXEC_RESULT.getStatus());
changeRecord.setUpdateTime(DateUtils.getCurrentTime());
changeFlowService.updateRecord(changeRecord);
log.info("[submitMainFlowAfterAllSubFlowsFinished] 主工单流转成功,flowId:{}, nextNodeId:{}, state:{}",
flowId, confirmNodeId, ChangeStatusEnum.WAIT_CONFIRM_EXEC_RESULT.getStatus());
} catch (Exception e) {
log.error("[submitMainFlowAfterAllSubFlowsFinished] 主工单流转失败,changeRecordId:{}", changeRecordId, e);
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST,
String.format("主工单流转失败:%s", e.getMessage()));
}
}
/**
* 检查主工单下所有子单是否都已完成
* 以节点为准:如果节点是结束节点(9999),则认为已完成,允许主工单流转
*
* @param changeRecordId 主工单ID
* @return true 表示所有子单都已完成
*/
private boolean checkAllSubFlowsFinishedForMainFlow(Long changeRecordId) {
// 查询该主工单下所有子单
List<ChangeSubFlowRecord> allSubFlows = changeSubFlowRecordMapper.selectByChangeRecordId(changeRecordId);
if (CollectionUtils.isEmpty(allSubFlows)) {
return false;
}
// 检查是否所有子单都满足条件:以节点为准,节点是结束节点则认为已完成
return allSubFlows.stream().allMatch(subFlow -> {
// 判断节点是否为结束节点(9999),节点是最准确的
boolean isEndNode = ChangeFlowEnum.SUB_FLOW_END.getNodeId().equals(subFlow.getSubFlowNode());
if (isEndNode) {
// 节点是结束节点,认为已完成,允许流转
// 如果状态不对,打印日志提醒
Integer status = subFlow.getStatus();
if (!ChangeSubFlowStatusEnum.FINISHED.getStatus().equals(status)
&& !ChangeSubFlowStatusEnum.CANCELLED.getStatus().equals(status)) {
log.warn("[checkAllSubFlowsFinishedForMainFlow] 子单节点已是结束节点,但状态不正确,subFlowId:{}, status:{}, 期望状态:{}或{}",
subFlow.getSubFlowId(), status,
ChangeSubFlowStatusEnum.FINISHED.getStatus(),
ChangeSubFlowStatusEnum.CANCELLED.getStatus());
}
return true;
}
return false;
});
}
/**
* 发送子单完成/取消邮件
*
* @param subFlowRecord 子单记录
*/
private void sendSubFlowFinishEmail(ChangeSubFlowRecord subFlowRecord) {
try {
// 查询主单信息
ChangeRecord mainRecord = changeRecordMapper.selectByPrimaryKey(subFlowRecord.getChangeRecordId());
if (mainRecord == null) {
log.warn("[sendSubFlowFinishEmail] 主单不存在,跳过发送邮件,changeRecordId:{}", subFlowRecord.getChangeRecordId());
return;
}
// 收件人:变更负责人+所有变更行动人+变更管理QM
String changeExecUserEmail = subFlowRecord.getChangeExecUserEmail();
List<String> receiverList = Collections.singletonList(changeExecUserEmail);
receiverList.add(mainRecord.getChangeCommander());
receiverList.add(appConfig.getChangeManageQM());
// 抄送人:所有变更行动人上一级LEADER+变更管理委员会
List<String> ccList = departmentLeaderBiz.getDepartmentLeaders(Collections.singletonList(changeExecUserEmail));
ccList.add(appConfig.getChangeCommittee());
// 构建邮件主题:【已完结】变更主体+变更内容+工单ID-子单ID
String subjectParam = ChangeSubjectEnum
.getChangeSubjectEnum(mainRecord.getChangeSubject()).getDesc()
+ mainRecord.getChangeContent() + mainRecord.getFlowId() + "-" + subFlowRecord.getSubFlowId();
// 构建邮件参数
HashMap<String, Object> emailMap = new HashMap<>();
emailMap.put("changeId", mainRecord.getFlowId() + "-" + subFlowRecord.getSubFlowId());
emailMap.put("changeSubject", ChangeSubjectEnum.getChangeSubjectEnum(mainRecord.getChangeSubject()).getDesc());
emailMap.put("flowUrl", mainRecord.getFlowId());
// 发送邮件
changeFlowBiz.qcSendEmail(receiverList, ccList, subjectParam, EmailTemplateEnum.YX_QC_CHANGE_FINISH, emailMap);
// 如果是供应商,再次发送供应商邮件
if (mainRecord.getCreateSource().equals(CreateSourceEnum.TONG_ZHOU.getType())) {
sendSupplierEmail.sendSupplierEmail(mainRecord.getCreateSupplier(), subjectParam,
EmailTemplateEnum.YX_QC_CHANGE_FINISH, emailMap);
}
// 如果是其他端发起但是是供应商变更,再次发送供应商邮件
if (!mainRecord.getCreateSource().equals(CreateSourceEnum.TONG_ZHOU.getType())
&& ChangeSubjectEnum.SUPPLIER.getType().equals(mainRecord.getChangeSubject())) {
sendSupplierEmail.sendSupplierEmail(mainRecord.getChangeSupplier(), subjectParam,
EmailTemplateEnum.YX_QC_CHANGE_FINISH, emailMap);
}
log.info("[sendSubFlowFinishEmail] 子单邮件发送成功,subFlowId:{}", subFlowRecord.getSubFlowId());
} catch (Exception e) {
log.error("[sendSubFlowFinishEmail] 子单邮件发送失败,subFlowId:{}", subFlowRecord.getSubFlowId(), e);
}
}
/**
* 根据变更行动工单ID查询详情
* 说明:展示内容和主单的detail一样,但行动项只展示该变更行动工单下绑定的行动项
*
* @param subFlowId 变更行动工单ID(即topo)
* @return 变更工单详情
*/
public ChangeFlowVO detailBySubFlowId(String subFlowId) {
log.info("[detailBySubFlowId] 查询变更行动工单详情,subFlowId:{}", subFlowId);
if (StringUtils.isBlank(subFlowId)) {
throw ExceptionFactory.createBiz(ResponseCode.ERROR_FLOW_ID, "变更行动工单ID不能为空");
}
// 根据 subFlowId 查询变更行动工单记录
ChangeSubFlowRecord subFlowRecord = changeSubFlowRecordService.getBySubFlowId(subFlowId);
if (subFlowRecord == null) {
throw ExceptionFactory.createBiz(ResponseCode.ERROR_FLOW_ID, "变更行动工单不存在");
}
Long changeRecordId = subFlowRecord.getChangeRecordId();
Long subFlowRecordId = subFlowRecord.getId();
// 先根据 changeRecordId 查询主单,获取 flow_id
ChangeRecord mainRecord = changeRecordMapper.selectByPrimaryKey(changeRecordId);
if (mainRecord == null) {
throw ExceptionFactory.createBiz(ResponseCode.ERROR_FLOW_ID, "主工单不存在");
}
// 调用主单的 detail 方法获取完整信息(传入 flow_id)
ChangeFlowVO changeFlowVO = changeFlowBiz.detail(mainRecord.getFlowId());
// topoId:这是子单详情页面,需要设置为子单的 topoId
changeFlowVO.setTopoId(ChangeFlowEnum.CHANGE_SUB_FLOW.getTopoId());
// flowNode:应该使用子单的数据,而不是主单的数据
changeFlowVO.setFlowNode(subFlowRecord.getSubFlowNode());
// mainFlowNode:设置主单的节点,用于前端判断是否允许提交执行结果
changeFlowVO.setMainFlowNode(mainRecord.getFlowNode());
// 子单特有字段:从子单记录中获取并设置到VO中
changeFlowVO.setChangeResult(subFlowRecord.getChangeResult());
changeFlowVO.setRemark(subFlowRecord.getRemark());
changeFlowVO.setCancelReason(subFlowRecord.getCancelReason());
changeFlowVO.setChangeConfirmResultTime(subFlowRecord.getChangeConfirmResultTime());
// 查询子单的文件(替换主单的文件)
List<ChangeSubFlowFile> subFlowFiles = changeSubFlowFileService.getFileList(subFlowRecordId);
if (CollectionUtils.isNotEmpty(subFlowFiles)) {
List<ChangeFlowFile> fileList = subFlowFiles.stream().map(f -> {
ChangeFlowFile file = new ChangeFlowFile();
file.setFileName(f.getFileName());
file.setFileUrl(f.getFileUrl());
file.setFileType(f.getFileType());
return file;
}).collect(Collectors.toList());
changeFlowVO.setFiles(fileList);
} else {
changeFlowVO.setFiles(new ArrayList<>());
}
// 只保留该变更行动工单下绑定的行动项
List<ChangeExecRecord> execRecords = changeFlowExecService.getBySubFlowRecordId(subFlowRecordId);
if (CollectionUtils.isNotEmpty(execRecords)) {
List<ChangeFlowExecVO> filteredExecList = execRecords.stream().map(c -> {
ChangeFlowExecVO vo = new ChangeFlowExecVO();
BeanUtils.copyProperties(c, vo);
vo.setChangeExecId(c.getId());
return vo;
}).collect(Collectors.toList());
// 填充用户名信息
try {
Set<String> userEmail = filteredExecList.stream().map(ChangeFlowExecVO::getChangeExecUserEmail)
.collect(Collectors.toSet());
AjaxResponse<List<IusUserInfoRsp>> userListInfo = iusRpcService
.queryUserListInfo(UserQueryDTO.builder().uids(new ArrayList<>(userEmail)).build());
List<IusUserInfoRsp> data = userListInfo.getData();
filteredExecList.forEach(i -> {
Optional<IusUserInfoRsp> anyExeUser = data.stream()
.filter(u -> u.getUid().equals(i.getChangeExecUserEmail())).findAny();
if (anyExeUser.isPresent()) {
i.setChangeExecUserName(anyExeUser.get().getName());
} else {
// 批量查询没查到的人名,通过全量查询单独再查一次
IusUserInfoRsp user = iusService.queryUserInfo(i.getChangeExecUserEmail());
if (user != null && StringUtils.isNotBlank(user.getName())) {
i.setChangeExecUserName(user.getName());
}
}
});
} catch (Exception ex) {
log.error("query user info has ex", ex);
}
changeFlowVO.setChangeExecProjectList(filteredExecList);
} else {
changeFlowVO.setChangeExecProjectList(new ArrayList<>());
}
return changeFlowVO;
}
/**
* 查询变更行动工单列表
*
* @param page 页码
* @param pageSize 每页大小
* @param queryReq 查询条件
* @return 变更行动工单列表
*/
public ChangeSubFlowListVO querySubFlowList(Integer page, Integer pageSize, ChangeSubFlowListQueryReq queryReq) {
log.info("[querySubFlowList] page:{}, pageSize:{}, queryReq:{}", page, pageSize, JSON.toJSONString(queryReq));
String currentUser = RequestLocalBean.getUid();
// 如果 onlyMyFollowed=true,设置 currentUser,用于在子单查询时过滤
// 子单查询条件:change_exec_user_email = 当前用户 OR approver 包含当前用户 OR change_commander = 当前用户
if (queryReq.getOnlyMyFollowed() && StringUtils.isNotBlank(currentUser)) {
queryReq.setCurrentUser(currentUser);
}
// 根据主单条件(flowId, itemId, supplier)查询主单,获取 changeRecordIds
if (queryReq.getFlowId() != null || StringUtils.isNotBlank(queryReq.getItemId())
|| StringUtils.isNotBlank(queryReq.getSupplier())) {
List<Long> changeRecordIds = filterFlowRecordIds(queryReq);
if (CollectionUtils.isEmpty(changeRecordIds)) {
// 如果主单查询结果为空,直接返回
PageVO pageVO = PageUtils.buildPageVo(0L, pageSize, page);
ChangeSubFlowListVO result = new ChangeSubFlowListVO();
result.setPageVo(pageVO);
result.setChangeSubFlowList(new ArrayList<>());
return result;
}
queryReq.setChangeRecordIds(changeRecordIds);
}
// 进行分页查询子单
PageHelper.startPage(page, pageSize);
List<ChangeSubFlowRecord> subFlowRecords = changeSubFlowRecordMapper.selectByCondition(queryReq);
PageInfo<ChangeSubFlowRecord> subFlowRecordPageInfo = new PageInfo<>(subFlowRecords);
// 第四步:批量获取主单信息和行动项信息,构建返回列表
List<ChangeSubFlowVO> list = new ArrayList<>();
if (CollectionUtils.isNotEmpty(subFlowRecords)) {
// 批量获取主单信息
List<Long> changeRecordIds = subFlowRecords.stream().map(ChangeSubFlowRecord::getChangeRecordId).distinct()
.collect(Collectors.toList());
final Map<Long, ChangeRecord> changeRecordMap;
if (CollectionUtils.isNotEmpty(changeRecordIds)) {
List<ChangeRecord> changeRecords = changeFlowService.getByIds(changeRecordIds);
changeRecordMap = changeRecords.stream().filter(Objects::nonNull)
.collect(Collectors.toMap(ChangeRecord::getId, r -> r));
} else {
changeRecordMap = new HashMap<>();
}
// 批量获取行动项信息(用于获取行动人、部门、完成时间等)
List<Long> subFlowRecordIds = subFlowRecords.stream().map(ChangeSubFlowRecord::getId)
.collect(Collectors.toList());
Map<Long, List<ChangeExecRecord>> execRecordMap = new HashMap<>();
if (CollectionUtils.isNotEmpty(subFlowRecordIds)) {
List<ChangeExecRecord> allExecRecords = changeFlowExecService.getBySubFlowRecordIds(subFlowRecordIds);
if (CollectionUtils.isNotEmpty(allExecRecords)) {
execRecordMap = allExecRecords.stream().filter(exec -> exec.getSubFlowRecordId() != null)
.collect(Collectors.groupingBy(ChangeExecRecord::getSubFlowRecordId));
}
}
// 构建返回列表
for (ChangeSubFlowRecord subFlowRecord: subFlowRecords) {
ChangeSubFlowVO vo = new ChangeSubFlowVO();
vo.setSubFlowId(subFlowRecord.getSubFlowId());
vo.setStatus(subFlowRecord.getStatus());
vo.setStatusDesc(ChangeSubFlowStatusEnum.getDescByStatus(subFlowRecord.getStatus()));
vo.setCreateTime(subFlowRecord.getCreateTime());
// 获取主单信息
ChangeRecord changeRecord = changeRecordMap.get(subFlowRecord.getChangeRecordId());
if (changeRecord != null) {
vo.setFlowId(changeRecord.getFlowId());
}
// 获取行动项信息(取第一个行动项的信息作为展示)
List<ChangeExecRecord> execRecords = execRecordMap.get(subFlowRecord.getId());
if (CollectionUtils.isNotEmpty(execRecords)) {
ChangeExecRecord firstExec = execRecords.get(0);
vo.setChangeExecUserEmail(firstExec.getChangeExecUserEmail());
vo.setChangeExecDepartment(firstExec.getChangeExecDepartment());
// 完成时间:如果有完成时间则使用,否则使用预期完成时间(从主单获取)
if (firstExec.getChangeExecFinishTime() != null) {
vo.setChangeExecFinishTime(firstExec.getChangeExecFinishTime());
} else if (changeRecord != null) {
vo.setChangeExecFinishTime(changeRecord.getChangeConfirmResultTime());
}
}
// 设置子单归属关系
if (StringUtils.isNotBlank(currentUser)) {
// 是否是执行人(change_exec_user_email)
boolean isExecutor = currentUser.equals(subFlowRecord.getChangeExecUserEmail());
// 是否是审批人(approver)
boolean isApprover = false;
String subApproverJson = subFlowRecord.getApprover();
if (StringUtils.isNotBlank(subApproverJson)) {
isApprover = subApproverJson.contains(currentUser);
}
// 是否是主单变更负责人(change_commander,关联主单的负责人)
boolean isMainFlowCommander = currentUser.equals(subFlowRecord.getChangeCommander());
ChangeSubFlowOwnershipVO flowOwnership = ChangeSubFlowOwnershipVO.builder()
.isExecutor(isExecutor)
.isApprover(isApprover)
.isMainFlowCommander(isMainFlowCommander)
.build();
vo.setFlowOwnership(flowOwnership);
}
list.add(vo);
}
// 批量查询用户名信息
try {
Set<String> userEmails = list.stream().map(ChangeSubFlowVO::getChangeExecUserEmail)
.filter(StringUtils::isNotBlank).collect(Collectors.toSet());
if (CollectionUtils.isNotEmpty(userEmails)) {
AjaxResponse<List<IusUserInfoRsp>> userListInfo = iusRpcService
.queryUserListInfo(UserQueryDTO.builder().uids(new ArrayList<>(userEmails)).build());
List<IusUserInfoRsp> userData = userListInfo.getData();
Map<String, String> userNameMap = new HashMap<>();
if (CollectionUtils.isNotEmpty(userData)) {
userNameMap = userData.stream()
.collect(Collectors.toMap(IusUserInfoRsp::getUid, IusUserInfoRsp::getName));
}
// 填充用户名和展示信息
for (ChangeSubFlowVO vo: list) {
String email = vo.getChangeExecUserEmail();
if (StringUtils.isNotBlank(email)) {
String name = userNameMap.get(email);
if (StringUtils.isBlank(name)) {
// 批量查询没查到的人名,通过全量查询单独再查一次
IusUserInfoRsp user = iusService.queryUserInfo(email);
if (user != null && StringUtils.isNotBlank(user.getName())) {
name = user.getName();
}
}
vo.setChangeExecUserName(name);
// 构建展示信息:姓名+邮箱
if (StringUtils.isNotBlank(name)) {
vo.setChangeExecUserView(name + "(" + email + ")");
} else {
vo.setChangeExecUserView(email);
}
}
}
}
} catch (Exception ex) {
log.error("query user info has ex", ex);
}
}
PageVO pageVO = PageUtils.buildPageVo(subFlowRecordPageInfo.getTotal(), pageSize, page);
ChangeSubFlowListVO result = new ChangeSubFlowListVO();
result.setPageVo(pageVO);
result.setChangeSubFlowList(list);
return result;
}
/**
* 根据主单条件查询主单记录ID列表
*
* @param queryReq 查询条件
* @return 主单记录ID列表
*/
private List<Long> filterFlowRecordIds(ChangeSubFlowListQueryReq queryReq) {
ChangeFlowListQueryReq mainQueryReq = new ChangeFlowListQueryReq();
mainQueryReq.setFlowId(queryReq.getFlowId());
mainQueryReq.setItemId(queryReq.getItemId());
mainQueryReq.setSupplier(queryReq.getSupplier());
// 查询主单(不分页,只获取ID)
List<ChangeRecord> changeRecords = changeRecordMapper.selectByCondition(mainQueryReq);
if (CollectionUtils.isEmpty(changeRecords)) {
return new ArrayList<>();
}
return changeRecords.stream().map(ChangeRecord::getId).distinct().collect(Collectors.toList());
}
}
package com.netease.mail.yanxuan.change.biz.biz;
import com.netease.mail.yanxuan.change.biz.service.rpc.IusService;
import com.netease.mail.yanxuan.change.integration.flow.ius.req.IusDepartmentReq;
import com.netease.mail.yanxuan.change.integration.flow.ius.rsp.SecondaryDepartments;
import com.netease.mail.yanxuan.change.integration.flow.ius.rsp.leaderChain;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
/**
* 部门领导查询业务类
*
* @author system
*/
@Component
@Slf4j
public class DepartmentLeaderBiz {
@Autowired
private IusService iusService;
/**
* 批量获取用户的部门上级领导列表
*
* @param userEmails 用户邮箱列表
* @return 上级领导邮箱列表(去重)
*/
public List<String> getDepartmentLeaders(List<String> userEmails) {
if (CollectionUtils.isEmpty(userEmails)) {
return new ArrayList<>();
}
IusDepartmentReq iusDepartmentReq = new IusDepartmentReq();
iusDepartmentReq.setHasOrgPos(true);
iusDepartmentReq.setIcac(true);
iusDepartmentReq.setUids(userEmails);
HashMap<String, List<SecondaryDepartments>> map = iusService.queryDepartment(iusDepartmentReq);
ArrayList<List<SecondaryDepartments>> secondaryDepartments = new ArrayList<>(map.values());
List<String> leaderList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(secondaryDepartments)) {
// 外层循环是不同人,内层循环是相同人不同部门
for (List<SecondaryDepartments> secondaryDepartment : secondaryDepartments) {
for (SecondaryDepartments dep : secondaryDepartment) {
List<leaderChain> leaderChainList = dep.getLeaderChainList();
if (CollectionUtils.isNotEmpty(leaderChainList)) {
// 获取上一级领导(leaderChainList 的最后一个元素,列表是从上往下逐级下降的:最上级 -> ... -> 上一级)
leaderChain directLeader = leaderChainList.get(leaderChainList.size() - 1);
if (directLeader != null && StringUtils.isNotBlank(directLeader.getUid())) {
leaderList.add(directLeader.getUid());
}
}
}
}
}
// 去重
if (CollectionUtils.isNotEmpty(leaderList)) {
leaderList = leaderList.stream().distinct().collect(Collectors.toList());
}
return leaderList;
}
/**
* 获取单个用户的部门上级领导(取第一个)
*
* @param userEmail 用户邮箱
* @return 上级领导邮箱,如果不存在则返回 null
*/
public String getDepartmentLeader(String userEmail) {
if (StringUtils.isBlank(userEmail)) {
return null;
}
List<String> leaders = getDepartmentLeaders(Collections.singletonList(userEmail));
if (CollectionUtils.isNotEmpty(leaders)) {
return leaders.get(0);
}
return null;
}
}
package com.netease.mail.yanxuan.change.biz.callback;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.google.common.base.Joiner;
import com.netease.mail.dp.des.process.api.entity.CallbackQueryReq;
import com.netease.mail.dp.des.process.api.entity.ExcelCell;
import com.netease.mail.dp.des.process.api.entity.ExcelTitle;
import com.netease.mail.dp.des.starter.callback.DesCallbackService;
import com.netease.mail.yanxuan.change.biz.service.change.ChangeExecConfigService;
import com.netease.mail.yanxuan.change.common.enums.ChangeCommanderEnum;
import com.netease.mail.yanxuan.change.common.enums.ChangeLevelEnum;
import com.netease.mail.yanxuan.change.common.enums.ChangeSubjectEnum;
import com.netease.mail.yanxuan.change.common.enums.NeedFileEnum;
import com.netease.mail.yanxuan.change.common.util.DateUtils;
import com.netease.mail.yanxuan.change.common.util.ExcelUtil;
import com.netease.mail.yanxuan.change.dal.entity.ChangeExecConfig;
import com.netease.mail.yanxuan.change.dal.entity.ChangeType;
import com.netease.mail.yanxuan.change.dal.mapper.ChangeConfigMapper;
import com.netease.mail.yanxuan.change.dal.mapper.ChangeTypeMapper;
import com.netease.mail.yanxuan.change.dal.meta.model.po.ChangeConfigPo;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeConfigQueryReq;
import com.netease.mail.yanxuan.change.integration.excel.ChangeConfigExcelDTO;
import com.netease.mail.yanxuan.change.integration.excel.ChangeConfigExcelVo;
import lombok.extern.slf4j.Slf4j;
/**
* 变更配置导出回调。一层主表 + 二层行动项:无行动项时输出一行主数据,有多条行动项时主数据复制多行,总行数按展开后的行数。
*/
@Slf4j
@Component
public class ChangeConfigExportCallback implements DesCallbackService {
@Autowired
private ChangeConfigMapper changeConfigMapper;
@Autowired
private ChangeTypeMapper changeTypeMapper;
@Autowired
private ChangeExecConfigService changeExecConfigService;
@Override
public ExcelTitle queryExcelTitle(CallbackQueryReq req) {
log.info("[op: change config export callback queryExcelTitle] req={}", JSON.toJSONString(req));
ChangeConfigQueryReq query = JSON.parseObject(req.getParamJson(), ChangeConfigQueryReq.class);
List<ChangeConfigPo> list = changeConfigMapper.selectChangeConfigPo(query);
ExcelTitle excelTitle = ExcelUtil.fetchExceltile(ChangeConfigExcelVo.class);
excelTitle.setTotal((long) list.size());
log.info("[op: change config export callback queryExcelTitle] result={}", JSON.toJSONString(excelTitle));
return excelTitle;
}
@Override
public List<List<ExcelCell>> queryExcelData(CallbackQueryReq req) {
log.info("[op: change config export callback queryExcelData] req={}", JSON.toJSONString(req));
ChangeConfigQueryReq query = JSON.parseObject(req.getParamJson(), ChangeConfigQueryReq.class);
query.setOffset(req.getOffset());
query.setLimit(req.getLimit());
List<ChangeConfigExcelDTO> list = getExportList(query);
log.debug("[op: change config export callback queryExcelData] size:{}", list.size());
return list.stream().map(ChangeConfigExcelVo::init).collect(Collectors.toList());
}
@Override
public com.netease.mail.dp.des.process.api.entity.PivotTable queryPivotTable(CallbackQueryReq req) {
// 不需要透视表,返回null
return null;
}
private List<ChangeConfigExcelDTO> getExportList(ChangeConfigQueryReq query) {
List<ChangeConfigPo> changeConfigPos = changeConfigMapper.selectChangeConfigPoWithLimit(query);
if (changeConfigPos.isEmpty()) {
return new ArrayList<>();
}
// 处理执行部门格式
changeConfigPos.forEach(i -> {
if (StringUtils.isNotBlank(i.getParticipateChangeExecDepartment())) {
String replaceDep = i.getParticipateChangeExecDepartment().replace("[", "").replace("]", "").replace(" ", "");
List<String> depList = Arrays.stream(replaceDep.split(","))
.filter(StringUtils::isNotBlank)
.distinct()
.collect(Collectors.toList());
i.setParticipateChangeExecDepartment(Joiner.on(",").join(depList));
}
});
// 批量查询变更类型:收集所有的 parentChangeClassId 和 sonChangeClassId
Set<Long> changeTypeIds = new HashSet<>();
for (ChangeConfigPo po : changeConfigPos) {
if (po.getParentChangeClassId() != null) {
changeTypeIds.add(po.getParentChangeClassId().longValue());
}
if (po.getSonChangeClassId() != null) {
changeTypeIds.add(po.getSonChangeClassId().longValue());
}
}
Map<Long, ChangeType> changeTypeMap = new HashMap<>();
if (!changeTypeIds.isEmpty()) {
List<ChangeType> changeTypes = changeTypeMapper.selectByIds(new ArrayList<>(changeTypeIds));
if (changeTypes != null) {
for (ChangeType ct : changeTypes) {
changeTypeMap.put(ct.getId(), ct);
}
}
}
final Map<Long, ChangeType> finalChangeTypeMap = changeTypeMap;
List<ChangeConfigExcelDTO> expanded = new ArrayList<>();
for (ChangeConfigPo po : changeConfigPos) {
List<ChangeExecConfig> execs = changeExecConfigService.queryChangeExecConfig(po.getId());
if (CollectionUtils.isEmpty(execs)) {
expanded.add(buildRow(po, null, finalChangeTypeMap));
} else {
for (ChangeExecConfig exec : execs) {
expanded.add(buildRow(po, exec, finalChangeTypeMap));
}
}
}
return expanded;
}
private ChangeConfigExcelDTO buildRow(ChangeConfigPo po, ChangeExecConfig exec, Map<Long, ChangeType> changeTypeMap) {
ChangeConfigExcelDTO dto = new ChangeConfigExcelDTO();
// 一层:主表字段
dto.setId(po.getId());
if (po.getChangeSubject() != null) {
dto.setChangeSubject(ChangeSubjectEnum.getChangeSubjectEnum(po.getChangeSubject()) != null
? ChangeSubjectEnum.getChangeSubjectEnum(po.getChangeSubject()).getDesc() : "");
}
String changeType = "";
if (po.getParentChangeClassId() != null && po.getSonChangeClassId() != null) {
ChangeType parentType = changeTypeMap.get(po.getParentChangeClassId().longValue());
ChangeType sonType = changeTypeMap.get(po.getSonChangeClassId().longValue());
if (parentType != null && sonType != null) {
changeType = parentType.getTypeName() + ">" + sonType.getTypeName();
} else if (parentType != null) {
changeType = parentType.getTypeName();
} else if (sonType != null) {
changeType = sonType.getTypeName();
}
}
dto.setChangeType(changeType);
if (po.getChangeLevel() != null) {
dto.setChangeLevel(ChangeLevelEnum.getByType(po.getChangeLevel()));
}
dto.setChangeDepartment(po.getChangeDepartment());
dto.setParticipateChangeExecDepartment(po.getParticipateChangeExecDepartment());
if (po.getChangeCommanderType() != null) {
ChangeCommanderEnum commanderEnum = ChangeCommanderEnum.getChangeCommanderEnum(po.getChangeCommanderType());
dto.setChangeCommanderType(commanderEnum != null ? commanderEnum.getName() : "");
}
dto.setChangeCommander(po.getChangeCommander());
if (po.getNeedFile() != null) {
for (NeedFileEnum e : NeedFileEnum.values()) {
if (e.getStatus().equals(po.getNeedFile())) {
dto.setNeedFile(e.getDesc());
break;
}
}
}
dto.setCreator(po.getCreator());
if (po.getCreateTime() != null) {
dto.setCreateTime(DateUtils.parseLongToString(po.getCreateTime(), DateUtils.DATE_TIME_FORMAT));
}
if (po.getUpdateTime() != null) {
dto.setUpdateTime(DateUtils.parseLongToString(po.getUpdateTime(), DateUtils.DATE_TIME_FORMAT));
}
// 二层:行动项
if (exec != null) {
dto.setChangeExecDepartment(exec.getChangeExecDepartment());
dto.setChangeExecUser(exec.getChangeExecUser());
dto.setChangeRiskDesc(exec.getChangeRiskDesc());
dto.setChangeExecProject(exec.getChangeExecProject());
dto.setChangeChecking(exec.getChangeChecking());
}
return dto;
}
}
......@@ -14,6 +14,9 @@ import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import com.netease.mail.yanxuan.change.biz.service.ChangeFlowExecService;
import com.netease.mail.yanxuan.change.biz.service.ChangeSubFlowRecordService;
import com.netease.mail.yanxuan.change.dal.entity.ChangeSubFlowRecord;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
......@@ -100,12 +103,31 @@ public class ChangeFlowExportCallback implements DesCallbackService {
@Autowired
private SupplierService supplierService;
@Autowired
private ChangeFlowExecService changeFlowExecService;
@Autowired
private ChangeSubFlowRecordService changeSubFlowRecordService;
@Override
public ExcelTitle queryExcelTitle(CallbackQueryReq req) {
log.info("[op: change flow export callback queryExcelTitle] req={}", JSON.toJSONString(req));
ChangeFlowExportQuery query = JSON.parseObject(req.getParamJson(), ChangeFlowExportQuery.class);
ChangeFlowListQueryReq changeFlowListQueryReq = new ChangeFlowListQueryReq();
BeanUtils.copyProperties(query, changeFlowListQueryReq);
// 处理 changeExecUser 参数(与 query 方法保持一致)
if (StringUtils.isNotBlank(changeFlowListQueryReq.getChangeExecUser())) {
List<Long> recordIds = changeFlowExecService.queryByExecUser(changeFlowListQueryReq.getChangeExecUser());
changeFlowListQueryReq.setChangeRecordIds(recordIds);
log.info("[queryExcelTitle] recordIds: {}", recordIds);
if (CollectionUtils.isEmpty(recordIds)) {
ExcelTitle excelTitle = ExcelUtil.fetchExceltile(ChangeFlowExcelVo.class);
excelTitle.setTotal(0L);
return excelTitle;
}
}
List<ChangeRecord> list = changeRecordMapper.selectByCondition(changeFlowListQueryReq);
ExcelTitle excelTitle = ExcelUtil.fetchExceltile(ChangeFlowExcelVo.class);
excelTitle.setTotal((long) list.size());
......@@ -127,6 +149,17 @@ public class ChangeFlowExportCallback implements DesCallbackService {
private List<ChangeFlowExcelDTO> getExportList(ChangeFlowExportQuery query) {
ChangeFlowListQueryReq changeFlowListQueryReq = new ChangeFlowListQueryReq();
BeanUtils.copyProperties(query, changeFlowListQueryReq);
// 处理 changeExecUser 参数(与 query 方法保持一致)
if (StringUtils.isNotBlank(changeFlowListQueryReq.getChangeExecUser())) {
List<Long> recordIds = changeFlowExecService.queryByExecUser(changeFlowListQueryReq.getChangeExecUser());
changeFlowListQueryReq.setChangeRecordIds(recordIds);
log.info("[getExportList] recordIds: {}", recordIds);
if (CollectionUtils.isEmpty(recordIds)) {
return Lists.newArrayList();
}
}
List<ChangeRecord> changeRecords = changeRecordMapper.selectByConditionAndLimit(changeFlowListQueryReq);
log.debug("[getExportList] changeRecords:{}", JSON.toJSONString(changeRecords));
if (CollectionUtils.isEmpty(changeRecords)) {
......@@ -201,6 +234,22 @@ public class ChangeFlowExportCallback implements DesCallbackService {
.queryGoodsPrincipalInfo(allItemList);
log.debug("[getExportList] spuTOS:{}", spuTOS.size());
}
// 批量查询所有变更行动工单,构建 Map<changeRecordId, List<subFlowId>>,提升性能
Map<Long, List<String>> subFlowIdsMap = new HashMap<>();
List<Long> changeRecordIds = changeRecords.stream()
.map(ChangeRecord::getId)
.collect(Collectors.toList());
List<ChangeSubFlowRecord> subFlowRecords = changeSubFlowRecordService.getByChangeRecordIds(changeRecordIds);
if (CollectionUtils.isNotEmpty(subFlowRecords)) {
subFlowIdsMap = subFlowRecords.stream()
.filter(record -> StringUtils.isNotBlank(record.getSubFlowId()))
.collect(Collectors.groupingBy(
ChangeSubFlowRecord::getChangeRecordId,
Collectors.mapping(ChangeSubFlowRecord::getSubFlowId, Collectors.toList())
));
}
List<ChangeFlowExcelDTO> changeFlowExcelDTOList = new ArrayList<>();
// 导出excel,按照商品/供应商和行动项,n*n重复拆分,例2个商品3个行动项,拆分成2*3=6行,其他属性一致
for (ChangeRecord record : changeRecords) {
......@@ -215,7 +264,17 @@ public class ChangeFlowExportCallback implements DesCallbackService {
Long itemId = itemVO.getItemId();
// 组装通用信息,除商品信息,行动项以外其他字段一致
ChangeFlowExcelDTO changeFlowExcelDTO = this.obtainCommon(record, changeSubjectEnum, orgMap);
changeFlowExcelDTO.setSupplier("/");
String changeSupplier = StringUtils.isBlank(record.getChangeSupplier()) ? "" : record.getChangeSupplier();
String changeSupplierName = "";
if (StringUtils.isNotBlank(changeSupplier)) {
List<SupplierSimpleRsp> supplierSimple = supplierService.getSupplierName(changeSupplier);
if (CollectionUtils.isNotEmpty(supplierSimple)) {
SupplierSimpleRsp supplier = supplierSimple.get(0);
changeSupplierName = supplier.getSupplierName();
}
}
changeFlowExcelDTO.setSupplier(
StringUtils.isBlank(changeSupplier) ? "/" : changeSupplier + changeSupplierName);
changeFlowExcelDTO.setChangeItem(itemVO.getItemName());
changeFlowExcelDTO.setSPUId(itemId);
List<QcCategoryVO> qcCategoryList = qcService.getQcCategoryListByItemId(itemId);
......@@ -256,6 +315,7 @@ public class ChangeFlowExportCallback implements DesCallbackService {
changeFlowExcelDTO.setPurchase("/");
changeFlowExcelDTO.setGoodsProject("/");
changeFlowExcelDTO.setGoodsSqe("/");
changeFlowExcelDTO.setGoodsCompliance("/");
} else {
buildGoodsPrincipal(changeGoodsPrincipalPO, changeFlowExcelDTO);
}
......@@ -266,6 +326,9 @@ public class ChangeFlowExportCallback implements DesCallbackService {
// 组装行动项相关信息
obtainChangeExec(changeExecRecord, changeFlowExcelDTO, userListInfo);
this.obtainOther(record, changeFlowExcelDTO);
// 设置变更行动工单(用逗号分隔)
List<String> subFlowIds = subFlowIdsMap.getOrDefault(record.getId(), new ArrayList<>());
changeFlowExcelDTO.setSubFlowIds(CollectionUtils.isEmpty(subFlowIds) ? "/" : String.join(",", subFlowIds));
changeFlowExcelDTOList.add(changeFlowExcelDTO);
}
}
......@@ -299,9 +362,13 @@ public class ChangeFlowExportCallback implements DesCallbackService {
changeFlowExcelDTO.setPurchase(null);
changeFlowExcelDTO.setGoodsSqe(null);
changeFlowExcelDTO.setGoodsProject(null);
changeFlowExcelDTO.setGoodsCompliance(null);
// 组装行动项相关信息
this.obtainChangeExec(changeExecRecord, changeFlowExcelDTO, userListInfo);
this.obtainOther(record, changeFlowExcelDTO);
// 设置变更行动工单(用逗号分隔)
List<String> subFlowIds = subFlowIdsMap.getOrDefault(record.getId(), new ArrayList<>());
changeFlowExcelDTO.setSubFlowIds(CollectionUtils.isEmpty(subFlowIds) ? "/" : String.join(",", subFlowIds));
changeFlowExcelDTOList.add(changeFlowExcelDTO);
}
break;
......@@ -313,15 +380,40 @@ public class ChangeFlowExportCallback implements DesCallbackService {
}
private void obtainOther(ChangeRecord record, ChangeFlowExcelDTO changeFlowExcelDTO) {
ChangeResultEnum resultEnum = ChangeResultEnum.getByStatus(record.getChangeResult());
ChangeResultEnum resultEnum = ChangeResultEnum.getByValue(record.getChangeResult());
log.debug("[obtainOther] record:{}, resultEnum:{}", JSON.toJSONString(record), resultEnum);
changeFlowExcelDTO.setChangeResult(resultEnum == null ? "/" : resultEnum.getDesc());
if (record.getState().equals(ChangeStatusEnum.CANCEL.getStatus())
|| record.getState().equals(ChangeStatusEnum.END.getStatus())) {
changeFlowExcelDTO.setChangeEndTime(DateUtils.parseLongToString(record.getUpdateTime(), DateUtils.DATE_TIME_FORMAT));
// 时间展示逻辑:
// - 未完成的工单:展示预期完成时间(changeConfirmResultTime)
// - 已完成工单(END):展示实际完成时间(updateTime)
// - 取消的工单(CANCEL):展示取消时间(updateTime)
Integer state = record.getState();
Long changeConfirmResultTime = record.getChangeConfirmResultTime();
Long updateTime = record.getUpdateTime();
if (ChangeStatusEnum.END.getStatus().equals(state)) {
// 已完成工单:展示实际完成时间(updateTime)
if (updateTime != null) {
changeFlowExcelDTO.setChangeEndTime(DateUtils.parseLongToString(updateTime, DateUtils.DATE_TIME_FORMAT));
} else {
changeFlowExcelDTO.setChangeEndTime("/");
}
} else if (ChangeStatusEnum.CANCEL.getStatus().equals(state)) {
// 取消的工单:展示取消时间(updateTime)
if (updateTime != null) {
changeFlowExcelDTO.setChangeEndTime(DateUtils.parseLongToString(updateTime, DateUtils.DATE_TIME_FORMAT));
} else {
changeFlowExcelDTO.setChangeEndTime("/");
}
} else {
// 未完成的工单:展示预期完成时间(changeConfirmResultTime)
if (changeConfirmResultTime != null) {
changeFlowExcelDTO.setChangeEndTime(DateUtils.parseLongToString(changeConfirmResultTime, DateUtils.DATE_TIME_FORMAT));
} else {
changeFlowExcelDTO.setChangeEndTime("/");
}
}
}
private void buildGoodsPrincipal(ChangeGoodsPrincipalPO changeGoodsPrincipalPO, ChangeFlowExcelDTO changeFlowExcelDTO) {
......@@ -333,15 +425,19 @@ public class ChangeFlowExportCallback implements DesCallbackService {
String goodsProjectEmail = StringUtils.isBlank(changeGoodsPrincipalPO.getGoodsProjectEmail()) ? "" : changeGoodsPrincipalPO.getGoodsProjectEmail();
String goodsSqeName = StringUtils.isBlank(changeGoodsPrincipalPO.getGoodsSqeName()) ? "" : changeGoodsPrincipalPO.getGoodsSqeName();
String goodsSqeEmail = StringUtils.isBlank(changeGoodsPrincipalPO.getGoodsSqeEmail()) ? "" : changeGoodsPrincipalPO.getGoodsSqeEmail();
String goodsComplianceName = StringUtils.isBlank(changeGoodsPrincipalPO.getGoodsComplianceName()) ? "" : changeGoodsPrincipalPO.getGoodsComplianceName();
String goodsComplianceEmail = StringUtils.isBlank(changeGoodsPrincipalPO.getGoodsComplianceEmail()) ? "" : changeGoodsPrincipalPO.getGoodsComplianceEmail();
String functionary = functionaryName + functionaryEmail;
String purchase = purchaseName + purchaseEmail;
String goodsProject = goodsProjectName + goodsProjectEmail;
String goodsSqe = goodsSqeName + goodsSqeEmail;
String goodsCompliance = goodsComplianceName + goodsComplianceEmail;
changeFlowExcelDTO.setFunctionary(StringUtils.isBlank(functionary) ? "/" :functionary);
changeFlowExcelDTO.setPurchase(StringUtils.isBlank(purchase) ? "/" :purchase);
changeFlowExcelDTO.setGoodsProject(StringUtils.isBlank(goodsProject) ? "/" :goodsProject);
changeFlowExcelDTO.setGoodsSqe(StringUtils.isBlank(goodsSqe) ? "/" :goodsSqe);
changeFlowExcelDTO.setGoodsCompliance(StringUtils.isBlank(goodsCompliance) ? "/" :goodsCompliance);
}
private void obtainChangeExec(ChangeExecRecord changeExecRecord, ChangeFlowExcelDTO changeFlowExcelDTO,
......
......@@ -6,6 +6,8 @@
*/
package com.netease.mail.yanxuan.change.biz.config;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
......@@ -77,12 +79,36 @@ public class AppConfig {
private String sqeEmail;
/**
* 商品关联合规兜底人名
*/
@Value("${change.goods.compliance.name:崔亦仙}")
private String complianceName;
/**
* 商品关联合规兜底邮箱
*/
@Value("${change.goods.compliance.email:cuiyixian@corp.netease.com}")
private String complianceEmail;
/**
* 变更管理QM
*/
@Value("${changeManageQM:cuiyixian@corp.netease.com}")
private String changeManageQM;
/**
* 变更管理员(用于主单审批流程)
*/
@Value("${changeAdmin:wb.xietao@mesg.corp.netease.com}")
private String changeAdmin;
/**
* 质量部负责人(用于主单审批流程)
*/
@Value("${changeQualityLeader:wb.xietao@mesg.corp.netease.com}")
private String changeQualityLeader;
/**
* 变更管理委员会
*/
@Value("${changeCommittee:yx_change_SC@qun.mail.163.com}")
......@@ -90,4 +116,17 @@ public class AppConfig {
@Value("${exportLimit:20}")
private Long exportLimit;
/**
* 需要审批的部门列表(只有这些部门的行动工单需要审批,其他部门自动通过)
* 配置格式:部门名称列表,逗号分隔,例如:质量部,研发部
*/
@ValueMapping("${changeSubflowNeedApproveDepartments:[]}")
private List<String> needApproveDepartments;
/**
* 工单超期判断的工作日数(默认2天)
*/
@Value("${change.overdue.workdays:2}")
private Integer overdueWorkdays;
}
\ No newline at end of file
......@@ -34,4 +34,23 @@ public interface BuildAndSendEmail {
* @param changeExecRecords
*/
void buildAndSendEmailSubmit(ChangeRecord changeRecord, List<ChangeExecRecord> changeExecRecords);
/**
* 发送确认变更结果完成邮件通知
* 接收人:变更行动人、变更行动人上一级主管、变更供应商、变更管理QA群组
* todo: 待业务确认邮件模板和内容参数
*
* @param changeRecord 变更记录
*/
void sendConfirmResultFinishEmail(ChangeRecord changeRecord);
/**
* 发送确认变更结果取消邮件通知
* 接收人:变更行动人、变更行动人上一级主管、变更供应商、变更管理QA群组
* todo: 待业务确认邮件模板和内容参数
*
* @param changeRecord 变更记录
* @param cancelReason 取消原因
*/
void sendConfirmResultCancelEmail(ChangeRecord changeRecord, String cancelReason);
}
\ No newline at end of file
......@@ -9,7 +9,6 @@ package com.netease.mail.yanxuan.change.biz.service;
import java.util.List;
import com.netease.mail.yanxuan.change.dal.entity.ChangeExecRecord;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeExecConfigReq;
import com.netease.mail.yanxuan.change.dal.meta.model.vo.ChangeFlowExecVO;
/**
......@@ -26,6 +25,13 @@ public interface ChangeFlowExecService {
List<ChangeFlowExecVO> getChangeFlowExecRecord(Long changeRecordId);
/**
* 查询行动项实体列表
* @param changeRecordId
* @return
*/
List<ChangeExecRecord> getChangeExecRecordList(Long changeRecordId);
/**
* 批量保存变更行动执行记录
* @param changeExecRecordList
*/
......@@ -43,4 +49,33 @@ public interface ChangeFlowExecService {
* @return
*/
List<Long> queryByExecUser(String changeExecUser);
/**
* 根据变更行动工单记录ID查询行动项列表
* @param subFlowRecordId 变更行动工单记录ID
* @return 行动项列表
*/
List<ChangeExecRecord> getBySubFlowRecordId(Long subFlowRecordId);
/**
* 批量根据变更行动工单记录ID列表查询行动项列表
* @param subFlowRecordIds 变更行动工单记录ID列表
* @return 行动项列表
*/
List<ChangeExecRecord> getBySubFlowRecordIds(List<Long> subFlowRecordIds);
/**
* 根据变更行动人和部门查询变更行动工单记录ID列表(合并查询)
* @param changeExecUser 变更行动人(邮箱),可为空
* @param changeExecDepartment 变更行动部门,可为空
* @return 变更行动工单记录ID列表
*/
List<Long> querySubFlowRecordIdsByExecCondition(String changeExecUser, String changeExecDepartment);
/**
* 根据变更行动工单记录ID删除行动项
* @param subFlowRecordId 变更行动工单记录ID
* @return 删除的记录数
*/
Integer deleteBySubFlowRecordId(Long subFlowRecordId);
}
\ No newline at end of file
......@@ -8,8 +8,6 @@ package com.netease.mail.yanxuan.change.biz.service;
import com.netease.mail.yanxuan.change.common.enums.CreateSourceEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
/**
......@@ -24,6 +22,11 @@ public class ChangeFlowExportQuery {
private String itemId;
/**
* 变更SKU
*/
private String skuId;
/**
* 变更供应商(变更主体为供应商时有效)
*/
private String supplier;
......@@ -75,6 +78,11 @@ public class ChangeFlowExportQuery {
private Integer createSource;
/**
* 创建供应商(同舟端创建)
*/
private String createSupplier;
/**
* 服务编码 yanxuan-qc-change-flow
*/
protected String serviceCode;
......@@ -88,6 +96,23 @@ public class ChangeFlowExportQuery {
*/
private Long flowId;
/**
* 变更执行人
*/
private String changeExecUser;
/**
* 数据可见范围:仅查看我跟进的工单(默认true)
* true: 仅展示 "变更申请人/变更负责人/变更审批人=当前用户" 的变更工单
* false: 展示所有工单
*/
private Boolean onlyMyFollowed;
/**
* 当前用户邮箱(用于数据可见范围过滤)
*/
private String currentUser;
private Long limit;
private Long offset;
......
......@@ -52,6 +52,13 @@ public interface ChangeFlowService {
ChangeRecord getById(Long id);
/**
* 批量根据ID列表查询工单详情
* @param ids ID列表
* @return 工单详情列表
*/
List<ChangeRecord> getByIds(List<Long> ids);
/**
* 根据状态及时间获取数据
*
* @param entityId
......
package com.netease.mail.yanxuan.change.biz.service;
import java.util.List;
import com.netease.mail.yanxuan.change.common.enums.FileTypeEnum;
import com.netease.mail.yanxuan.change.dal.entity.ChangeSubFlowFile;
/**
* 子单文件服务
*/
public interface ChangeSubFlowFileService {
/**
* 保存文件
*/
void saveRecord(ChangeSubFlowFile file);
/**
* 批量保存文件
*/
void batchSaveRecord(List<ChangeSubFlowFile> files);
/**
* 根据子单记录ID查询文件列表
*/
List<ChangeSubFlowFile> getFileList(Long subFlowRecordId);
/**
* 根据子单记录ID删除文件
*/
Integer deleteBySubFlowRecordId(Long subFlowRecordId);
/**
* 根据子单记录ID和文件类型删除文件
*/
void deleteBySubFlowRecordIdAndType(Long subFlowRecordId, FileTypeEnum type);
}
/**
* @(#)ChangeSubFlowRecordService.java, 2024/01/01.
* <p/>
* Copyright 2024 Netease, Inc. All rights reserved.
* NETEASE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.netease.mail.yanxuan.change.biz.service;
import java.util.List;
import com.netease.mail.yanxuan.change.dal.entity.ChangeSubFlowRecord;
/**
* 变更行动工单服务
* @Author system
* @Date 2024/01/01
*/
public interface ChangeSubFlowRecordService {
/**
* 保存变更行动工单记录
* @param subFlowRecord 变更行动工单记录
*/
void saveRecord(ChangeSubFlowRecord subFlowRecord);
/**
* 更新变更行动工单记录
* @param subFlowRecord 变更行动工单记录
* @return 是否成功
*/
Boolean update(ChangeSubFlowRecord subFlowRecord);
/**
* 根据主单记录ID查询变更行动工单列表
* @param changeRecordId 主单记录ID
* @return 变更行动工单列表
*/
List<ChangeSubFlowRecord> getByChangeRecordId(Long changeRecordId);
/**
* 根据变更行动工单ID查询变更行动工单记录
* @param subFlowId 变更行动工单ID(即topo)
* @return 变更行动工单记录
*/
ChangeSubFlowRecord getBySubFlowId(String subFlowId);
/**
* 批量根据主单记录ID列表查询变更行动工单列表
* @param changeRecordIds 主单记录ID列表
* @return 变更行动工单列表
*/
List<ChangeSubFlowRecord> getByChangeRecordIds(List<Long> changeRecordIds);
}
package com.netease.mail.yanxuan.change.biz.service;
import com.netease.mail.yanxuan.change.dal.entity.ChangeExecRecord;
import java.util.List;
/**
* @author TmindA
*/
public interface ChangeSubFlowService {
/**
* 创建子工单
*/
void createSubFlow(ChangeExecRecord execRecord);
/**
* 查询子工单
* @param limit
* @param offset
* @return
*/
List<ChangeExecRecord> query(int limit, Integer offset);
}
......@@ -18,6 +18,7 @@ import com.netease.mail.dp.des.interaction.api.req.TaskReq;
import com.netease.mail.dp.des.starter.client.DesClient;
import com.netease.mail.yanxuan.change.biz.config.AppConfig;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeFlowRequest;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeConfigQueryReq;
/**
* @Author zcwang
......@@ -49,4 +50,21 @@ public class DESExportService {
log.info("create task result: {}", desResult);
return desResult;
}
public String changeConfigExport(ChangeConfigQueryReq query, String uid, String serviceCode) {
TaskReq taskReq = new TaskReq();
taskReq.setUserEmail(uid);
taskReq.setProductCode(serviceCode);
taskReq.setFileName("变更配置列表导出");
taskReq.setTaskType(TaskType.BIVARIATE);
taskReq.setDataType(DataType.CALLBACK);
CallbackQueryParam callbackQueryParam = new CallbackQueryParam();
callbackQueryParam.setParamJson(JSON.toJSONString(query));
callbackQueryParam.setCallbackBeanName("changeConfigExportCallback");
callbackQueryParam.setLimit(appConfig.getExportLimit());
taskReq.setCallbackQueryParam(callbackQueryParam);
String desResult = desClient.addTask(taskReq);
log.info("create change config export task result: {}", desResult);
return desResult;
}
}
\ No newline at end of file
......@@ -7,11 +7,13 @@
package com.netease.mail.yanxuan.change.biz.service.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import com.netease.mail.yanxuan.change.biz.biz.DepartmentLeaderBiz;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
......@@ -65,6 +67,9 @@ public class BuildAndSendEmailImpl implements BuildAndSendEmail {
@Autowired
private ChangeConfigService changeConfigService;
@Autowired
private DepartmentLeaderBiz departmentLeaderBiz;
@Override
@Async("asyncBuild")
public void buildAndSendEmailCreate(ChangeRecord changeRecord, List<ChangeExecRecord> changeExecRecords, StringBuilder changeType, String changeCommander, ChangeFlowCreateReq changeFlowCreateReq) {
......@@ -114,7 +119,7 @@ public class BuildAndSendEmailImpl implements BuildAndSendEmail {
List<String> execUserEmailList = changeFlowExecRecord.stream().map(ChangeFlowExecVO::getChangeExecUserEmail)
.collect(Collectors.toList());
// 获取指定人的上级部门主管邮箱
List<String> ccList = changeFlowBiz.getDepartmentInfo(execUserEmailList);
List<String> ccList = departmentLeaderBiz.getDepartmentLeaders(execUserEmailList);
// 变更执行,抄送:所有变更行动人上一级LEADER,变更负责人、变更转交人(如有)
ccList.add(changeCommander);
// 变更执行,收件人:所有变更行动人+变更管理QM(cuiyixian@corp.netease.com)
......@@ -191,7 +196,7 @@ public class BuildAndSendEmailImpl implements BuildAndSendEmail {
List<String> execUserEmailList = changeFlowExecRecord.stream().map(ChangeFlowExecVO::getChangeExecUserEmail)
.collect(Collectors.toList());
// 获取指定人的上级部门主管邮箱
List<String> ccList = changeFlowBiz.getDepartmentInfo(execUserEmailList);
List<String> ccList = departmentLeaderBiz.getDepartmentLeaders(execUserEmailList);
// 变更执行,抄送:所有变更行动人上一级LEADER,变更负责人、变更转交人(如有)
ccList.add(changeCommander);
// 变更执行,收件人:所有变更行动人+变更管理QM(cuiyixian@corp.netease.com)
......@@ -210,4 +215,108 @@ public class BuildAndSendEmailImpl implements BuildAndSendEmail {
EmailTemplateEnum.YX_QC_CHANGE_EXECUTE, param);
}
}
@Override
@Async("asyncBuild")
public void sendConfirmResultFinishEmail(ChangeRecord changeRecord) {
try {
// 构建邮件参数(todo: 邮件内容参数待业务确认)
HashMap<String, Object> finishMap = new HashMap<>();
finishMap.put("changeId", changeRecord.getFlowId());
finishMap.put("changeSubject", ChangeSubjectEnum.getChangeSubjectEnum(changeRecord.getChangeSubject()).getDesc());
finishMap.put("changeContent", changeRecord.getChangeContent());
IusUserInfoRsp user = iusService.queryUserInfo(changeRecord.getChangeCommander());
finishMap.put("changeCommander", user == null ? changeRecord.getChangeCommander() : user.getName());
// todo: 其他邮件内容参数待业务补充
String finishSubjectParam = changeRecord.getFlowId().toString();
// 收件人:变更负责人、变更行动人
List<String> receiver = new ArrayList<>(Collections.singletonList(changeRecord.getChangeCommander()));
List<ChangeFlowExecVO> execRecord = changeFlowExecService.getChangeFlowExecRecord(changeRecord.getId());
List<String> userEmailList = execRecord.stream()
.map(ChangeFlowExecVO::getChangeExecUserEmail)
.filter(email -> StringUtils.isNotBlank(email))
.collect(Collectors.toList());
receiver.addAll(userEmailList);
// 抄送人:变更行动人上一级主管、变更管理QA群组
userEmailList.addAll(receiver);
List<String> ccList = departmentLeaderBiz.getDepartmentLeaders(userEmailList);
ccList.add(appConfig.getChangeManageQM());
// 发送邮件(todo: 邮件模板待业务确认)
changeFlowBiz.qcSendEmail(receiver, ccList, finishSubjectParam, EmailTemplateEnum.YX_QC_CHANGE_FINISH, finishMap);
// 如果是供应商变更,发送供应商邮件
if (changeRecord.getCreateSource().equals(CreateSourceEnum.TONG_ZHOU.getType())) {
sendSupplierEmail.sendSupplierEmail(changeRecord.getCreateSupplier(), finishSubjectParam,
EmailTemplateEnum.YX_QC_CHANGE_FINISH, finishMap);
}
// 如果是其他端发起但是是供应商变更,发送供应商邮件
if (!changeRecord.getCreateSource().equals(CreateSourceEnum.TONG_ZHOU.getType())
&& ChangeSubjectEnum.SUPPLIER.getType().equals(changeRecord.getChangeSubject())) {
sendSupplierEmail.sendSupplierEmail(changeRecord.getChangeSupplier(), finishSubjectParam,
EmailTemplateEnum.YX_QC_CHANGE_FINISH, finishMap);
}
log.info("[sendConfirmResultFinishEmail] 确认变更结果完成邮件已发送,flowId:{}", changeRecord.getFlowId());
} catch (Exception e) {
log.error("[sendConfirmResultFinishEmail] 发送确认变更结果完成邮件失败,flowId:{}, error:{}",
changeRecord.getFlowId(), e.getMessage(), e);
}
}
@Override
@Async("asyncBuild")
public void sendConfirmResultCancelEmail(ChangeRecord changeRecord, String cancelReason) {
try {
// 构建邮件参数(todo: 邮件内容参数待业务确认)
HashMap<String, Object> cancelMap = new HashMap<>();
cancelMap.put("changeId", changeRecord.getFlowId());
cancelMap.put("changeSubject", ChangeSubjectEnum.getChangeSubjectEnum(changeRecord.getChangeSubject()).getDesc());
cancelMap.put("changeContent", changeRecord.getChangeContent());
IusUserInfoRsp user = iusService.queryUserInfo(changeRecord.getChangeCommander());
cancelMap.put("changeCommander", user == null ? changeRecord.getChangeCommander() : user.getName());
cancelMap.put("cancelReason", cancelReason);
// todo: 其他邮件内容参数待业务补充
String cancelSubjectParam = changeRecord.getFlowId().toString();
// 收件人:变更负责人、变更行动人
List<String> receiver = new ArrayList<>(Collections.singletonList(changeRecord.getChangeCommander()));
List<ChangeFlowExecVO> execRecord = changeFlowExecService.getChangeFlowExecRecord(changeRecord.getId());
List<String> userEmailList = execRecord.stream()
.map(ChangeFlowExecVO::getChangeExecUserEmail)
.filter(email -> StringUtils.isNotBlank(email))
.collect(Collectors.toList());
receiver.addAll(userEmailList);
// 抄送人:变更行动人上一级主管、变更管理QA群组
userEmailList.addAll(receiver);
List<String> ccList = departmentLeaderBiz.getDepartmentLeaders(userEmailList);
ccList.add(appConfig.getChangeManageQM());
// 发送邮件(todo: 邮件模板待业务确认)
changeFlowBiz.qcSendEmail(receiver, ccList, cancelSubjectParam, EmailTemplateEnum.YX_QC_CHANGE_SUBMIT_CANCEL, cancelMap);
// 如果是供应商变更,发送供应商邮件
if (changeRecord.getCreateSource().equals(CreateSourceEnum.TONG_ZHOU.getType())) {
sendSupplierEmail.sendSupplierEmail(changeRecord.getCreateSupplier(), cancelSubjectParam,
EmailTemplateEnum.YX_QC_CHANGE_SUBMIT_CANCEL, cancelMap);
}
// 如果是其他端发起但是是供应商变更,发送供应商邮件
if (!changeRecord.getCreateSource().equals(CreateSourceEnum.TONG_ZHOU.getType())
&& ChangeSubjectEnum.SUPPLIER.getType().equals(changeRecord.getChangeSubject())) {
sendSupplierEmail.sendSupplierEmail(changeRecord.getChangeSupplier(), cancelSubjectParam,
EmailTemplateEnum.YX_QC_CHANGE_SUBMIT_CANCEL, cancelMap);
}
log.info("[sendConfirmResultCancelEmail] 确认变更结果取消邮件已发送,flowId:{}, cancelReason:{}",
changeRecord.getFlowId(), cancelReason);
} catch (Exception e) {
log.error("[sendConfirmResultCancelEmail] 发送确认变更结果取消邮件失败,flowId:{}, error:{}",
changeRecord.getFlowId(), e.getMessage(), e);
}
}
}
\ No newline at end of file
......@@ -11,6 +11,7 @@ import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
......@@ -39,13 +40,19 @@ public class ChangeFlowExecServiceImpl implements ChangeFlowExecService {
.changeExecUser(c.getChangeExecUser()).changeExecUserEmail(c.getChangeExecUserEmail())
.changeRiskDesc(c.getChangeRiskDesc()).changeExecProject(c.getChangeExecProject())
.changeChecking(c.getChangeChecking()).changeExecFinishTime(c.getChangeExecFinishTime())
.changeExecFinishDesc(c.getChangeExecFinishDesc()).build())
.changeExecFinishDesc(c.getChangeExecFinishDesc())
.subFlowId(c.getSubFlowId()).build())
.collect(Collectors.toList());
}
return new ArrayList<>();
}
@Override
public List<ChangeExecRecord> getChangeExecRecordList(Long changeRecordId) {
return changeExecRecordMapper.selectByChangeRecordId(changeRecordId);
}
@Override
public void batchSaveRecord(List<ChangeExecRecord> changeExecRecordList) {
changeExecRecordMapper.insertList(changeExecRecordList);
}
......@@ -69,4 +76,39 @@ public class ChangeFlowExecServiceImpl implements ChangeFlowExecService {
public List<Long> queryByExecUser(String changeExecUser) {
return changeExecRecordMapper.queryByUser("%" + changeExecUser + "%");
}
@Override
public List<ChangeExecRecord> getBySubFlowRecordId(Long subFlowRecordId) {
if (subFlowRecordId == null) {
return new ArrayList<>();
}
return changeExecRecordMapper.selectBySubFlowRecordId(subFlowRecordId);
}
@Override
public List<ChangeExecRecord> getBySubFlowRecordIds(List<Long> subFlowRecordIds) {
if (CollectionUtils.isEmpty(subFlowRecordIds)) {
return new ArrayList<>();
}
return changeExecRecordMapper.selectBySubFlowRecordIds(subFlowRecordIds);
}
@Override
public List<Long> querySubFlowRecordIdsByExecCondition(String changeExecUser, String changeExecDepartment) {
// 如果两个条件都为空,返回空列表
if (StringUtils.isBlank(changeExecUser) && StringUtils.isBlank(changeExecDepartment)) {
return new ArrayList<>();
}
// 如果 changeExecUser 不为空,添加 LIKE 通配符
String userCondition = StringUtils.isNotBlank(changeExecUser) ? "%" + changeExecUser + "%" : null;
return changeExecRecordMapper.querySubFlowRecordIdsByExecCondition(userCondition, changeExecDepartment);
}
@Override
public Integer deleteBySubFlowRecordId(Long subFlowRecordId) {
if (subFlowRecordId == null) {
return 0;
}
return changeExecRecordMapper.deleteBySubFlowRecordId(subFlowRecordId);
}
}
......@@ -6,8 +6,10 @@
*/
package com.netease.mail.yanxuan.change.biz.service.impl;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
......@@ -56,6 +58,14 @@ public class ChangeFlowServiceImpl implements ChangeFlowService {
}
@Override
public List<ChangeRecord> getByIds(List<Long> ids) {
if (CollectionUtils.isEmpty(ids)) {
return new ArrayList<>();
}
return changeRecordMapper.selectByIds(ids);
}
@Override
public List<ChangeRecord> getByStatusAndTime(Long entityId, Integer sendEmail, String nodeId1, String nodeId2, long tomorrowSpecificTime) {
return changeRecordMapper.getByStatusAndTime(entityId, sendEmail, nodeId1, nodeId2, tomorrowSpecificTime);
}
......
package com.netease.mail.yanxuan.change.biz.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.netease.mail.yanxuan.change.biz.service.ChangeSubFlowFileService;
import com.netease.mail.yanxuan.change.common.enums.FileTypeEnum;
import com.netease.mail.yanxuan.change.dal.entity.ChangeSubFlowFile;
import com.netease.mail.yanxuan.change.dal.mapper.ChangeSubFlowFileMapper;
/**
* 子单文件服务实现
*/
@Service
public class ChangeSubFlowFileServiceImpl implements ChangeSubFlowFileService {
@Autowired
private ChangeSubFlowFileMapper changeSubFlowFileMapper;
@Override
public void saveRecord(ChangeSubFlowFile file) {
changeSubFlowFileMapper.insertSelective(file);
}
@Override
public void batchSaveRecord(List<ChangeSubFlowFile> files) {
changeSubFlowFileMapper.insertList(files);
}
@Override
public List<ChangeSubFlowFile> getFileList(Long subFlowRecordId) {
return changeSubFlowFileMapper.selectBySubFlowRecordId(subFlowRecordId);
}
@Override
public Integer deleteBySubFlowRecordId(Long subFlowRecordId) {
return changeSubFlowFileMapper.deleteBySubFlowRecordId(subFlowRecordId);
}
@Override
public void deleteBySubFlowRecordIdAndType(Long subFlowRecordId, FileTypeEnum type) {
changeSubFlowFileMapper.deleteBySubFlowRecordIdAndType(subFlowRecordId, type.getType());
}
}
/**
* @(#)ChangeSubFlowRecordServiceImpl.java, 2024/01/01.
* <p/>
* Copyright 2024 Netease, Inc. All rights reserved.
* NETEASE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.netease.mail.yanxuan.change.biz.service.impl;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.netease.mail.yanxuan.change.biz.service.ChangeSubFlowRecordService;
import com.netease.mail.yanxuan.change.dal.entity.ChangeSubFlowRecord;
import com.netease.mail.yanxuan.change.dal.mapper.ChangeSubFlowRecordMapper;
/**
* @Author system
* @Date 2024/01/01
*/
@Service
public class ChangeSubFlowRecordServiceImpl implements ChangeSubFlowRecordService {
@Autowired
private ChangeSubFlowRecordMapper changeSubFlowRecordMapper;
@Override
public void saveRecord(ChangeSubFlowRecord subFlowRecord) {
changeSubFlowRecordMapper.insertSelective(subFlowRecord);
}
@Override
public Boolean update(ChangeSubFlowRecord subFlowRecord) {
return changeSubFlowRecordMapper.updateByPrimaryKeySelective(subFlowRecord) > 0;
}
@Override
public List<ChangeSubFlowRecord> getByChangeRecordId(Long changeRecordId) {
return changeSubFlowRecordMapper.selectByChangeRecordId(changeRecordId);
}
@Override
public ChangeSubFlowRecord getBySubFlowId(String subFlowId) {
return changeSubFlowRecordMapper.selectBySubFlowId(subFlowId);
}
@Override
public List<ChangeSubFlowRecord> getByChangeRecordIds(List<Long> changeRecordIds) {
if (CollectionUtils.isEmpty(changeRecordIds)) {
return new ArrayList<>();
}
return changeSubFlowRecordMapper.selectByChangeRecordIds(changeRecordIds);
}
}
package com.netease.mail.yanxuan.change.biz.service.impl;
import com.netease.mail.yanxuan.change.biz.service.ChangeSubFlowService;
import com.netease.mail.yanxuan.change.dal.entity.ChangeExecRecord;
import com.netease.mail.yanxuan.change.dal.mapper.ChangeExecRecordMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author TmindA
*/
@Service
public class ChangeSubFlowServiceImpl implements ChangeSubFlowService {
@Autowired
private ChangeExecRecordMapper changeExecRecordMapper;
@Override
public void createSubFlow(ChangeExecRecord execRecord) {
changeExecRecordMapper.insertSelective(execRecord);
}
@Override
public List<ChangeExecRecord> query(int limit, Integer offset) {
return changeExecRecordMapper.queryList(limit, offset);
}
}
......@@ -89,19 +89,27 @@ public class InteriorChangeConfigServiceImpl implements InteriorChangeConfigServ
*/
@Override
public GoodsResponseRpc queryCommanderInfo(ChangeCommanderPO changeCommander, String commander) {
log.info("[queryCommanderInfo] entry, parentChangeClassId:{}, sonChangeClassId:{}, createSource:{}, supplier:{}, changeSupplierId:{}, flowCreator:{}, commander:{}",
changeCommander.getParentChangeClassId(), changeCommander.getSonChangeClassId(), changeCommander.getCreateSource(),
changeCommander.getSupplier(), changeCommander.getChangeSupplierId(), changeCommander.getFlowCreator(), commander);
GoodsResponseRpc rpc = new GoodsResponseRpc();
//获取模板名称 通过一级变更类型 二级变更类型ID 定位模板
ChangeConfig changeConfig = changeConfigService.queryChangeType(changeCommander.getParentChangeClassId(),
changeCommander.getSonChangeClassId());
if (changeConfig == null) {
log.warn("[queryCommanderInfo] changeConfig is null, parentChangeClassId:{}, sonChangeClassId:{}",
changeCommander.getParentChangeClassId(), changeCommander.getSonChangeClassId());
return null;
}
//查询出当前模板配置中选择的 角色
ChangeCommanderEnum changeCommanderEnum = ChangeCommanderEnum
.getChangeCommanderEnum(changeConfig.getChangeCommanderType());
if (changeCommanderEnum == null) {
log.warn("[queryCommanderInfo] changeCommanderEnum is null, changeCommanderType:{}", changeConfig.getChangeCommanderType());
return null;
}
log.info("[queryCommanderInfo] changeConfig found, changeSubject:{}, changeCommanderType:{}, changeCommanderEnum:{}",
changeConfig.getChangeSubject(), changeConfig.getChangeCommanderType(), changeCommanderEnum.name());
//直接排除 选项为固定人 或者发起人
if (Objects.equals(changeConfig.getChangeCommanderType(), ChangeCommanderEnum.COMMANDER_ORIGINATOR.getId())) {
//发起人;
......@@ -110,14 +118,19 @@ public class InteriorChangeConfigServiceImpl implements InteriorChangeConfigServ
List<GoodsResponseRpc> goodsResponseRpcList = queryChangeSupplier(
ChangeCommanderEnum.SUPPLIERS_ASSOCIATED, changeCommander.getSupplier(),
SupplierChangePrincipalEnum.GOODS_BU.getName());
return goodsResponseRpcList.get(0);
GoodsResponseRpc result = goodsResponseRpcList != null && !goodsResponseRpcList.isEmpty() ? goodsResponseRpcList.get(0) : null;
log.info("[queryCommanderInfo] branch=COMMANDER_ORIGINATOR+TONG_ZHOU, result email:{}, name:{}",
result != null ? result.getEmail() : null, result != null ? result.getName() : null);
return result;
}
rpc.setEmail(changeCommander.getFlowCreator());
log.info("[queryCommanderInfo] branch=COMMANDER_ORIGINATOR, result email:{}", rpc.getEmail());
return rpc;
}
if (Objects.equals(changeConfig.getChangeCommanderType(), ChangeCommanderEnum.FIXED_PERSON.getId())) {
//固定人;
rpc.setEmail(changeConfig.getChangeCommander());
log.info("[queryCommanderInfo] branch=FIXED_PERSON, result email:{}, from changeConfig.changeCommander", rpc.getEmail());
return rpc;
}
if (Objects.equals(ChangeSubjectEnum.PRODUCT.getType(), changeConfig.getChangeSubject())) {
......@@ -125,25 +138,34 @@ public class InteriorChangeConfigServiceImpl implements InteriorChangeConfigServ
&& SupplierChangePrincipalEnum.PURCHASE.getName().equals(changeConfig.getChangeCommander())) {
GoodsResponseRpc goodsResponseRpc = new GoodsResponseRpc();
goodsResponseRpc.setEmail(commander);
log.info("[queryCommanderInfo] branch=PRODUCT+GOODS_ASSOCIATED+PURCHASE, result email:{}", commander);
return goodsResponseRpc;
}
//商品变更
List<GoodsResponseRpc> goodsResponseRpcList = queryGoodsCommander(changeCommanderEnum, changeCommander.getGoodsInfos(),
changeConfig.getChangeCommander());
return goodsResponseRpcList.get(0);
GoodsResponseRpc productResult = goodsResponseRpcList != null && !goodsResponseRpcList.isEmpty() ? goodsResponseRpcList.get(0) : null;
log.info("[queryCommanderInfo] branch=PRODUCT+queryGoodsCommander, result email:{}, name:{}",
productResult != null ? productResult.getEmail() : null, productResult != null ? productResult.getName() : null);
return productResult;
} else if (Objects.equals(ChangeSubjectEnum.SUPPLIER.getType(), changeConfig.getChangeSubject())) {
if (ChangeCommanderEnum.SUPPLIERS_ASSOCIATED.equals(changeCommanderEnum)
&& SupplierChangePrincipalEnum.PURCHASE.getName().equals(changeConfig.getChangeCommander())) {
GoodsResponseRpc goodsResponseRpc = new GoodsResponseRpc();
goodsResponseRpc.setEmail(commander);
log.info("[queryCommanderInfo] branch=SUPPLIER+SUPPLIERS_ASSOCIATED+PURCHASE, result email:{}", commander);
return goodsResponseRpc;
}
//供应商 采购 计划 SQE
List<GoodsResponseRpc> goodsResponseRpcList = queryChangeSupplier(changeCommanderEnum, changeCommander.getChangeSupplierId(),
changeConfig.getChangeCommander());
return goodsResponseRpcList.get(0);
GoodsResponseRpc supplierResult = goodsResponseRpcList != null && !goodsResponseRpcList.isEmpty() ? goodsResponseRpcList.get(0) : null;
log.info("[queryCommanderInfo] branch=SUPPLIER+queryChangeSupplier, result email:{}, name:{}",
supplierResult != null ? supplierResult.getEmail() : null, supplierResult != null ? supplierResult.getName() : null);
return supplierResult;
}
//其他变更 备注 其他变更之后固定人 和发起人在上面判断会被排除掉 changeConfig.getChangeCommander();
log.warn("[queryCommanderInfo] no branch matched, return null, changeSubject:{}", changeConfig.getChangeSubject());
return null;
}
......@@ -204,6 +226,28 @@ public class InteriorChangeConfigServiceImpl implements InteriorChangeConfigServ
emails.add(rpc);
}
}
} else if (ChangePrincipalEnum.COMPLIANCE.getName().equals(commander)) {
for (Long goods: goodsInfos) {
try {
String detail = qcApiRpcService.getQaUserListByItemId(goods.intValue());
logger.info("[queryGoodsCommander] compliance:{}", detail);
JSONObject jsonObject = JSONObject.parseObject(detail);
if (jsonObject != null) {
List<JSONObject> qaUserList = JSONObject.parseArray(jsonObject.getString("data"),
JSONObject.class);
if (CollectionUtils.isNotEmpty(qaUserList)) {
for (JSONObject qaUser: qaUserList) {
GoodsResponseRpc rpc = new GoodsResponseRpc();
rpc.setEmail(qaUser.getString("uid"));
rpc.setName(qaUser.getString("userName"));
emails.add(rpc);
}
}
}
} catch (Exception e) {
logger.error("[queryGoodsCommander] compliance error, itemId:{}", goods, e);
}
}
}
//逻辑发生变更
/*
......@@ -256,6 +300,8 @@ public class InteriorChangeConfigServiceImpl implements InteriorChangeConfigServ
req.setSearchType(SupplierChangePrincipalEnum.SQE.getType());
} else if (SupplierChangePrincipalEnum.GOODS_BU.getName().equals(commander)) {
req.setSearchType(SupplierChangePrincipalEnum.GOODS_BU.getType());
} else if (SupplierChangePrincipalEnum.COMPLIANCE.getName().equals(commander)) {
req.setSearchType(SupplierChangePrincipalEnum.COMPLIANCE.getType());
} else {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "供应商角色类型错误");
}
......@@ -263,12 +309,16 @@ public class InteriorChangeConfigServiceImpl implements InteriorChangeConfigServ
req.setSupplierIdList(supplierIdList);
//进行参数解析
List<SupplierUserRsp> listAjaxResponse = supplierInfoService.querySupplierRelatedUser(req);
log.info("[queryChangeSupplier] changeSupplierId:{}, commander:{}, searchType:{}, supplierUserCount:{}",
changeSupplierId, commander, req.getSearchType(), listAjaxResponse != null ? listAjaxResponse.size() : 0);
log.debug("[querySupplierRelatedUser] response:{}", JSON.toJSONString(listAjaxResponse));
if (!CollectionUtils.isEmpty(listAjaxResponse)) {
Optional<SupplierUserRsp> anySupplierUser = listAjaxResponse.stream()
.filter(i -> i.getSupplierId().equals(changeSupplierId)).findAny();
if (anySupplierUser.isPresent()) {
Map<String, String> uid2NameMap = anySupplierUser.get().getUid2NameMap();
log.info("[queryChangeSupplier] uid2NameMap from supplier, changeSupplierId:{}, uid2NameMapKeys:{}",
changeSupplierId, uid2NameMap != null ? uid2NameMap.keySet() : null);
for (String key: uid2NameMap.keySet()) {
GoodsResponseRpc goodsResponseRpc = new GoodsResponseRpc();
goodsResponseRpc.setName(uid2NameMap.get(key));
......@@ -281,6 +331,7 @@ public class InteriorChangeConfigServiceImpl implements InteriorChangeConfigServ
GoodsResponseRpc goodsResponseRpc = new GoodsResponseRpc();
this.getEmail(goodsResponseRpc, req.getSearchType());
goodsResponseRpcList.add(goodsResponseRpc);
log.info("[queryChangeSupplier] fallback to getEmail, searchType:{}, result email:{}", req.getSearchType(), goodsResponseRpc.getEmail());
}
return goodsResponseRpcList;
}
......@@ -359,6 +410,27 @@ public class InteriorChangeConfigServiceImpl implements InteriorChangeConfigServ
goodsRpc(ChangePrincipalEnum.SQE.getType(), data.getRealName(), map.get(itemId));
}
}
//合规
for (Long itemId: itemIds) {
try {
String compliance = qcApiRpcService.getQaUserListByItemId(itemId.intValue());
logger.info("[queryGoodsPrincipalInfo] compliance:{}", compliance);
JSONObject jsonObject1 = JSONObject.parseObject(compliance);
List<JSONObject> qaUserList = jsonObject1 == null ? null
: JSONObject.parseArray(jsonObject1.getString("data"), JSONObject.class);
if (CollectionUtils.isNotEmpty(qaUserList)) {
JSONObject qaUser = qaUserList.get(0);
map.get(itemId).setGoodsComplianceName(qaUser.getString("userName"));
map.get(itemId).setGoodsComplianceEmail(qaUser.getString("uid"));
goodsRpc(ChangePrincipalEnum.COMPLIANCE.getType(), qaUser.getString("uid"), map.get(itemId));
} else {
goodsRpc(ChangePrincipalEnum.COMPLIANCE.getType(), null, map.get(itemId));
}
} catch (Exception e) {
logger.error("[queryGoodsPrincipalInfo] compliance error, itemId:{}", itemId, e);
goodsRpc(ChangePrincipalEnum.COMPLIANCE.getType(), null, map.get(itemId));
}
}
return map;
}
......@@ -527,6 +599,11 @@ public class InteriorChangeConfigServiceImpl implements InteriorChangeConfigServ
goodsRpc.setGoodsSqeName(appConfig.getSqeName());
goodsRpc.setGoodsSqeEmail(appConfig.getSqeEmail());
break;
case 5:
//合规
goodsRpc.setGoodsComplianceName(appConfig.getComplianceName());
goodsRpc.setGoodsComplianceEmail(appConfig.getComplianceEmail());
break;
}
}
......@@ -579,6 +656,11 @@ public class InteriorChangeConfigServiceImpl implements InteriorChangeConfigServ
goodsResponseRpc.setName(appConfig.getSqeName());
goodsResponseRpc.setEmail(appConfig.getSqeEmail());
break;
case 5:
//合规
goodsResponseRpc.setName(appConfig.getComplianceName());
goodsResponseRpc.setEmail(appConfig.getComplianceEmail());
break;
}
}
......
......@@ -7,6 +7,7 @@
package com.netease.mail.yanxuan.change.biz.service.rpc;
import java.util.List;
import java.util.Map;
import com.netease.mail.yanxuan.change.common.bean.RequestLocalBean;
import org.apache.commons.collections4.CollectionUtils;
......@@ -166,4 +167,56 @@ public class FlowService {
approverDTO.setOperateResult(FlowxOperationEnum.DELIVER.getName());
flowRpcService.updateApprovers(CommonConstants.FLOWX_PRODUCT, approverDTO);
}
/**
* 提交工单(使用paramMap控制流转方向)
* @param flowId
* @param flowDataDTO
* @param uid
* @param topoId
* @param content
* @param paramMap 流转参数(例如:type=1表示通过,type=0表示不通过)
* @param operateResult
* @param remark
* @param createTime
* @return
*/
public String submitFlowWithParamMap(String flowId, FlowDataDTO flowDataDTO, String uid, String topoId, String content,
Map<String, Object> paramMap, String operateResult, String remark, Long createTime) {
String name = RequestLocalBean.getName();
// 系统自动提交,没有登录态获取不到名字,查询ius系统填充人名,还未查询到以邮箱兜底
if (StringUtils.isBlank(name)) {
IusUserInfoRsp user = iusService.queryUserInfo(uid);
name = user == null || StringUtils.isBlank(user.getName()) ? uid : user.getName();
}
NodeSubmitReqDTO nodeSubmitReqDTO = new NodeSubmitReqDTO();
nodeSubmitReqDTO.setFlowId(flowId);
nodeSubmitReqDTO.setNodeId(flowDataDTO.getFlowMeta().getCurrNodeDataList().get(0).getNodeId());
nodeSubmitReqDTO.setUid(uid);
nodeSubmitReqDTO.setUserName(name);
nodeSubmitReqDTO.setTopoId(topoId);
nodeSubmitReqDTO.setContent(content);
nodeSubmitReqDTO.setOperateResult(operateResult);
nodeSubmitReqDTO.setRemark(remark);
nodeSubmitReqDTO.setCreateTime(createTime);
nodeSubmitReqDTO.setParamMap(paramMap);
nodeSubmitReqDTO.setOccLock(flowDataDTO.getOccLock() + 1);
log.info("[submitFlowWithParamMap] nodeSubmitReqDTO={}", JSON.toJSONString(nodeSubmitReqDTO));
List<String> nextNodeIdList;
// 提交工单
AjaxResponse<List<String>> submitResponse = flowRpcService.submit(CommonConstants.FLOWX_PRODUCT,
nodeSubmitReqDTO);
if (ResponseCodeEnum.SUCCESS.getCode() == submitResponse.getCode()) {
nextNodeIdList = submitResponse.getData();
log.info("[submitFlowWithParamMap] nextNodeIdList={}", JSON.toJSONString(nextNodeIdList));
} else {
log.error("[submitFlowWithParamMap] submit flow failed, query={}, errMsg={}", JSON.toJSONString(nodeSubmitReqDTO),
JSON.toJSONString(submitResponse));
throw ExceptionFactory.createBiz(ResponseCode.SUBMIT_FLOW_ERROR, "提交工单错误");
}
if (CollectionUtils.isEmpty(nextNodeIdList)) {
return ChangeFlowEnum.END.getNodeId();
}
return nextNodeIdList.get(0);
}
}
\ No newline at end of file
package com.netease.mail.yanxuan.change.biz.util;
import com.netease.mail.yanxuan.change.dal.meta.model.vo.PageVO;
/**
* 分页工具类
* @Author system
* @Date 2024/01/01
*/
public class PageUtils {
/**
* 构建分页信息
* 根据总记录数、每页大小和当前页码构建分页VO
*
* @param total 总记录数
* @param pageSize 每页大小
* @param page 当前页码
* @return 分页信息
*/
public static PageVO buildPageVo(Long total, Integer pageSize, Integer page) {
Integer totalCount = Math.toIntExact(total);
int totalPage;
int i = totalCount % pageSize;
if (i != 0) {
totalPage = totalCount / pageSize + 1;
} else {
totalPage = totalCount / pageSize;
}
return PageVO.builder().page(page).totalPage(totalPage).pageSize(pageSize).pageCount(pageSize)
.totalCount(totalCount).build();
}
}
package com.netease.mail.yanxuan.change.common.constants;
/**
* 工单流转类型常量
* 用于 paramMap 中的 type 参数,控制 topo 图的流转方向
* 统一规则:1=通过,2=取消,3=回退,4=跳过
*
* @Author AI Assistant
* @Date 2025/01/04
*/
public class FlowTransitionType {
/**
* 类型1:通过/前进到下一节点
*/
public static final Integer TYPE_APPROVED = 1;
/**
* 类型2:取消
*/
public static final Integer TYPE_CANCEL = 2;
/**
* 类型3:回退
*/
public static final Integer TYPE_BACK = 3;
/**
* 类型4:跳过
*/
public static final Integer TYPE_SKIP = 4;
private FlowTransitionType() {
// 工具类,禁止实例化
}
}
......@@ -13,17 +13,41 @@ import lombok.Getter;
* @Date 2022/11/16
*/
public enum ChangeFlowEnum {
/**
* 老工作流
*/
CHANGE_FLOW("0000", "全链路变更", "qc-change-flow"),
CHANGE_FLOW_START("79560500", "开始节点", "qc-change-flow"),
CHANGE_FLOW_SUBMIT("79560501", "变更申请提交", "qc-change-flow"),
CHANGE_FLOW_EXE("79560502","变更执行", "qc-change-flow"),
CHANGE_FLOW_CONFIRM("79560503","变更结果确认", "qc-change-flow"),
END("9999", "结束", "qc-change-flow");
END("9999", "结束", "qc-change-flow"),
/**
* 新工作流
*/
NEW_CHANGE_FLOW("0000", "全链路变更", "yanxuan-qc-change-flow"),
NEW_CHANGE_FLOW_START("79560701", "提交变更申请", "yanxuan-qc-change-flow"),
NEW_CHANGE_FLOW_CONFIRM_EXEC_PLAN("79560702", "确认变更方案", "yanxuan-qc-change-flow"),
NEW_CHANGE_FLOW_OWNER_APPROVE("79560703","部门负责人审批", "yanxuan-qc-change-flow"),
NEW_CHANGE_FLOW_ADMIN_APPROVE("79560704","变更管理员审批", "yanxuan-qc-change-flow"),
NEW_CHANGE_FLOW_QUALITY_APPROVE("79560705","质量部负责人审批", "yanxuan-qc-change-flow"),
NEW_CHANGE_FLOW_EXE("79560706","执行变更方案", "yanxuan-qc-change-flow"),
NEW_CHANGE_FLOW_CONFIRM("79560707","确认变更结果", "yanxuan-qc-change-flow"),
NEW_END("9999", "结束", "yanxuan-qc-change-flow"),
/**
* 子工作流
*/
CHANGE_SUB_FLOW("0000", "全链路变更子工单", "qc-change-sub-flow"),
CHANGE_SUB_FLOW_START("79560601", "确认行动方案", "qc-change-sub-flow"),
CHANGE_SUB_FLOW_SUBMIT("79560602", "审批行动方案", "qc-change-sub-flow"),
CHANGE_SUB_FLOW_EXE("79560603","审批变更方案", "qc-change-sub-flow"),
CHANGE_SUB_FLOW_CONFIRM("79560604","提交执行结果", "qc-change-sub-flow"),
SUB_FLOW_END("9999", "结束", "qc-change-sub-flow");
@Getter
private final String nodeId;
@Getter
private final String name;
@Getter
......
......@@ -9,7 +9,8 @@ public enum ChangePrincipalEnum {
GOODS_BU(1,"商品开发"),
PURCHASE(2,"采购"),
PROSPECTUS(3,"计划"),
SQE(4,"SQE");
SQE(4,"SQE"),
COMPLIANCE(5,"合规");
ChangePrincipalEnum(Integer type, String name) {
......
......@@ -17,24 +17,24 @@ public enum ChangeResultEnum {
CANCEL(3, "不通过,变更取消"),
DELAY(4, "延期确认");
private Integer status;
private Integer value;
private String desc;
ChangeResultEnum(Integer status, String desc) {
this.status = status;
ChangeResultEnum(Integer value, String desc) {
this.value = value;
this.desc = desc;
}
public static ChangeResultEnum getByStatus(Integer status) {
for (ChangeResultEnum value : ChangeResultEnum.values()) {
if (value.getStatus().equals(status)) {
return value;
public static ChangeResultEnum getByValue(Integer value) {
for (ChangeResultEnum resultEnum : ChangeResultEnum.values()) {
if (resultEnum.getValue().equals(value)) {
return resultEnum;
}
}
return null;
}
public Integer getStatus() {
return status;
public Integer getValue() {
return value;
}
public String getDesc() {
......
......@@ -6,6 +6,9 @@
*/
package com.netease.mail.yanxuan.change.common.enums;
import java.util.Arrays;
import java.util.Objects;
/**
* @Author zcwang
* @Date 2022/11/14
......@@ -15,7 +18,17 @@ public enum ChangeStatusEnum {
CANCEL(2, "取消完结"),
DELAY(3, "延期"),
END(4, "完结"),
WAIT(5, "待发起");
WAIT(5, "待发起"),
WAIT_SUBMIT_CHANGE_APPLY(6, "待提交变更申请"),
WAIT_CONFIRM_CHANGE_PLAN(7, "待确认变更方案"),
WAIT_DEPT_LEADER_APPROVE(8, "待部门负责人审批"),
WAIT_CHANGE_ADMIN_APPROVE(9, "待变更管理员审批"),
WAIT_QUALITY_LEADER_APPROVE(10, "待质量部负责人审批"),
WAIT_EXEC_CHANGE_PLAN(11, "待执行变更方案"),
WAIT_CONFIRM_EXEC_RESULT(12, "待确认执行结果"),
FINISHED(13, "已完结"),
CANCELLED(14, "已取消");
private Integer status;
......@@ -33,4 +46,20 @@ public enum ChangeStatusEnum {
public String getDesc() {
return desc;
}
/**
* 根据状态码获取描述
* @param status 状态码
* @return 状态描述
*/
public static String getDescByStatus(Integer status) {
if (status == null) {
return null;
}
return Arrays.stream(ChangeStatusEnum.values())
.filter(e -> Objects.equals(e.getStatus(), status))
.map(ChangeStatusEnum::getDesc)
.findFirst()
.orElse(null);
}
}
\ No newline at end of file
/**
* 变更行动工单状态枚举
*
* @Author system
* @Date 2024/01/01
*/
package com.netease.mail.yanxuan.change.common.enums;
import lombok.Getter;
/**
* 变更行动工单状态枚举
*/
@Getter
public enum ChangeSubFlowStatusEnum {
/**
* 待确认行动方案
*/
WAIT_CONFIRM_ACTION_PLAN(1, "待确认行动方案"),
/**
* 待审批行动方案
*/
WAIT_APPROVE_ACTION_PLAN(2, "待审批行动方案"),
/**
* 待审批变更方案
*/
WAIT_APPROVE_CHANGE_PLAN(3, "待审批变更方案"),
/**
* 待提交执行结果
*/
WAIT_SUBMIT_RESULT(4, "待提交执行结果"),
/**
* 已完结
*/
FINISHED(5, "已完结"),
/**
* 已取消
*/
CANCELLED(6, "已取消");
private final Integer status;
private final String desc;
ChangeSubFlowStatusEnum(Integer status, String desc) {
this.status = status;
this.desc = desc;
}
/**
* 根据状态值获取枚举
*/
public static ChangeSubFlowStatusEnum getByStatus(Integer status) {
if (status == null) {
return null;
}
for (ChangeSubFlowStatusEnum statusEnum : values()) {
if (statusEnum.getStatus().equals(status)) {
return statusEnum;
}
}
return null;
}
/**
* 根据状态值获取描述
*/
public static String getDescByStatus(Integer status) {
ChangeSubFlowStatusEnum statusEnum = getByStatus(status);
return statusEnum != null ? statusEnum.getDesc() : null;
}
}
......@@ -31,7 +31,15 @@ public enum FlowxOperationEnum {
/**
* 系统自动提交
*/
SYSTEM(7, "系统自动提交工单");
SYSTEM(7, "系统自动提交工单"),
/**
* 取消变更
*/
CANCEL(8, "取消变更"),
/**
* 终止变更
*/
TERMINATION(9, "终止变更");
/**
* 类型
......
......@@ -26,4 +26,8 @@ public enum NeedFileEnum {
public Integer getStatus() {
return status;
}
public String getDesc() {
return desc;
}
}
\ No newline at end of file
......@@ -14,7 +14,8 @@ public enum SupplierChangePrincipalEnum {
PURCHASE(1,"采购"),
PROSPECTUS(2,"计划"),
SQE(3,"SQE"),
GOODS_BU(4,"商品开发");
GOODS_BU(4,"商品开发"),
COMPLIANCE(5,"合规");
SupplierChangePrincipalEnum(Integer type, String name) {
......
/**
* 同舟端状态枚举
* 用于同舟端传入的状态编码,映射到内部详细状态
*
* @Author AI Assistant
* @Date 2025/12/16
*/
package com.netease.mail.yanxuan.change.common.enums;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 同舟端状态枚举
* 可选值:待发起、待行动方案确认、待变更审批、待变更执行、待确认执行结果、已完结、已取消
*
* 映射关系:
* - 待发起 -> 6 (WAIT_SUBMIT_CHANGE_APPLY)
* - 待行动方案确认 -> 7 (WAIT_CONFIRM_CHANGE_PLAN)
* - 待变更审批 -> 8,9,10 (WAIT_DEPT_LEADER_APPROVE, WAIT_CHANGE_ADMIN_APPROVE, WAIT_QUALITY_LEADER_APPROVE)
* - 待变更执行 -> 11 (WAIT_EXEC_CHANGE_PLAN)
* - 待确认执行结果 -> 12 (WAIT_CONFIRM_EXEC_RESULT)
* - 已完结 -> 13 (FINISHED)
* - 已取消 -> 14 (CANCELLED)
*
* 同舟端枚举编码(向后扩展):
* - 待发起 -> 100
* - 待行动方案确认 -> 101
* - 待变更审批 -> 102
* - 待变更执行 -> 103
* - 待确认执行结果 -> 104
* - 已完结 -> 105
* - 已取消 -> 106
*/
public enum TongZhouStatusEnum {
/**
* 待发起
*/
WAIT_START("待发起", 100, Arrays.asList(ChangeStatusEnum.WAIT_SUBMIT_CHANGE_APPLY.getStatus())),
/**
* 待行动方案确认
*/
WAIT_CONFIRM_ACTION_PLAN("待行动方案确认", 101, Arrays.asList(ChangeStatusEnum.WAIT_CONFIRM_CHANGE_PLAN.getStatus())),
/**
* 待变更审批(包含:待部门负责人审批、待变更管理员审批、待质量部负责人审批)
*/
WAIT_CHANGE_APPROVE("待变更审批", 102, Arrays.asList(
ChangeStatusEnum.WAIT_DEPT_LEADER_APPROVE.getStatus(),
ChangeStatusEnum.WAIT_CHANGE_ADMIN_APPROVE.getStatus(),
ChangeStatusEnum.WAIT_QUALITY_LEADER_APPROVE.getStatus()
)),
/**
* 待变更执行
*/
WAIT_CHANGE_EXEC("待变更执行", 103, Arrays.asList(ChangeStatusEnum.WAIT_EXEC_CHANGE_PLAN.getStatus())),
/**
* 待确认执行结果
*/
WAIT_CONFIRM_RESULT("待确认执行结果", 104, Arrays.asList(ChangeStatusEnum.WAIT_CONFIRM_EXEC_RESULT.getStatus())),
/**
* 已完结
*/
FINISHED("已完结", 105, Arrays.asList(ChangeStatusEnum.FINISHED.getStatus())),
/**
* 已取消
*/
CANCELLED("已取消", 106, Arrays.asList(ChangeStatusEnum.CANCELLED.getStatus()));
private final Integer code; // 同舟端枚举编码(用于向后扩展)
private final List<Integer> statusList;
TongZhouStatusEnum(String desc, Integer code, List<Integer> statusList) {
this.code = code;
this.statusList = statusList;
}
/**
* 根据同舟端枚举编码转换为内部状态列表
* @param code 同舟端枚举编码(100-106)
* @return 内部状态列表,如果不存在返回null
*/
public static List<Integer> convertToStatusListByCode(Integer code) {
if (code == null) {
return null;
}
for (TongZhouStatusEnum statusEnum : values()) {
if (statusEnum.code.equals(code)) {
return new ArrayList<>(statusEnum.statusList);
}
}
return null;
}
}
......@@ -84,6 +84,16 @@ public class ChangeExecRecord {
private String changeExecFinishDesc;
/**
* 变更执行工单topo ID
*/
private String subFlowId;
/**
* 变更行动工单记录ID(关联到变更行动工单中间表,新数据使用)
*/
private Long subFlowRecordId;
/**
* 创建时间
*/
private Long createTime;
......
......@@ -168,6 +168,12 @@ public class ChangeRecord {
private String creator;
/**
* 变更审批人(JSON格式,存储当前节点的审批人列表,用于查询过滤)
* 格式:["user1@example.com", "user2@example.com"]
*/
private String approver;
/**
* 待办id
*/
private Integer todoId;
......@@ -183,6 +189,11 @@ public class ChangeRecord {
private String changeProfitAmount;
/**
* 批次说明
*/
private String batchDescription;
/**
* 创建时间
*/
private Long createTime;
......
package com.netease.mail.yanxuan.change.dal.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 子单文件实体
*/
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Entity
@Table(name = "TB_YX_QC_CHANGE_SUB_FLOW_FILE")
public class ChangeSubFlowFile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 子单记录ID
*/
private Long subFlowRecordId;
/**
* 文件名
*/
private String fileName;
/**
* 文件URL
*/
private String fileUrl;
/**
* 文件类型
* @see com.netease.mail.yanxuan.change.common.enums.FileTypeEnum
*/
private Integer fileType;
/**
* 创建时间
*/
private Long createTime;
/**
* 更新时间
*/
private Long updateTime;
}
/**
* @(#)ChangeSubFlowRecord.java, 2024/01/01.
* <p/>
* Copyright 2024 Netease, Inc. All rights reserved.
* NETEASE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.netease.mail.yanxuan.change.dal.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 变更行动工单
* 关系:主单 1 - 变更行动工单 N - 行动项 N
*
* @Author system
* @Date 2024/01/01
*/
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Entity
@Table(name = "TB_YX_QC_CHANGE_SUB_FLOW_RECORD")
public class ChangeSubFlowRecord {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 主单记录ID(变更记录ID)
*/
private Long changeRecordId;
/**
* 变更行动工单ID(即topo)
*/
private String subFlowId;
/**
* 变更行动工单当前节点
*/
private String subFlowNode;
/**
* 变更行动工单状态
* @see com.netease.mail.yanxuan.change.common.enums.ChangeSubFlowStatusEnum
*/
private Integer status;
/**
* 变更行动工单审批人(JSON格式,存储审批人列表,第一次新建时就是行动人)
* 格式:["user1@example.com", "user2@example.com"]
*/
private String approver;
/**
* 变更行动人邮箱(用于查询和展示,新建时从行动项获取)
*/
private String changeExecUserEmail;
/**
* 变更行动部门(用于查询和展示,新建时从行动项获取)
*/
private String changeExecDepartment;
/**
* 变更负责人(用于查询和展示,新建时从主单获取)
*/
private String changeCommander;
/**
* 拒绝原因(审批不通过时填写)
*/
private String rejectReason;
/**
* 变更结论(提交执行结果时填写)
* @see com.netease.mail.yanxuan.change.common.enums.ChangeResultEnum
*/
private Integer changeResult;
/**
* 备注
*/
private String remark;
/**
* 取消原因
*/
private String cancelReason;
/**
* 变更确认结果时间(延期时使用)
*/
private Long changeConfirmResultTime;
/**
* 创建时间
*/
private Long createTime;
/**
* 更新时间
*/
private Long updateTime;
}
......@@ -41,6 +41,13 @@ public interface ChangeConfigMapper extends tk.mybatis.mapper.common.Mapper<Chan
List<ChangeConfigPo> selectChangeConfigPo(ChangeConfigQueryReq req);
/**
* 分页查询变更配置
* @param req 查询请求,包含 offset 和 limit 分页参数
* @return 变更配置列表
*/
List<ChangeConfigPo> selectChangeConfigPoWithLimit(ChangeConfigQueryReq req);
List<ChangeType> queryByparentChangeClassId();
//暂时弃用
//ChangeExecConfigPo queryChangeDepartment(@Param("id")Long id);
......
......@@ -28,15 +28,68 @@ public interface ChangeExecRecordMapper extends tk.mybatis.mapper.common.Mapper<
List<ChangeExecRecord> selectByChangeRecordId(@Param("changeRecordId") Long id);
@Insert({ "<script>",
"insert into TB_YX_QC_CHANGE_EXEC_RECORD(change_record_id, change_exec_department, change_exec_user_type, change_exec_user, change_risk_desc,change_exec_project, change_checking, change_exec_finish_time, change_exec_finish_desc, create_time, update_time) values ",
"insert into TB_YX_QC_CHANGE_EXEC_RECORD(change_record_id, change_exec_department, change_exec_user_type, change_exec_user, change_exec_user_email, change_risk_desc, change_exec_project, change_checking, change_exec_finish_time, change_exec_finish_desc, sub_flow_id, sub_flow_record_id, create_time, update_time) values ",
"<foreach collection='list' item='item' index='index' separator=','>",
"(#{item.changeRecordId}, #{item.changeExecDepartment}, #{item.changeExecUserType}, #{item.changeExecUser}, #{item.changeRiskDesc}, #{item.changeExecProject}, #{item.changeChecking}, #{item.changeExecFinishTime}, #{item.changeExecFinishDesc}, #{item.createTime}, #{item.updateTime})",
"(#{item.changeRecordId}, #{item.changeExecDepartment}, #{item.changeExecUserType}, #{item.changeExecUser}, #{item.changeExecUserEmail}, #{item.changeRiskDesc}, #{item.changeExecProject}, #{item.changeChecking}, #{item.changeExecFinishTime}, #{item.changeExecFinishDesc}, #{item.subFlowId}, #{item.subFlowRecordId}, #{item.createTime}, #{item.updateTime})",
"</foreach>", "</script>" })
void insertList(@Param("list") List<ChangeExecRecord> changeExecRecordList);
@Delete("delete from TB_YX_QC_CHANGE_EXEC_RECORD where change_record_id = #{changeRecordId}")
Integer deleteByBaseId(@Param("changeRecordId") Long changeRecordId);
/**
* 根据变更行动工单记录ID删除行动项
* @param subFlowRecordId 变更行动工单记录ID
* @return 删除的记录数
*/
@Delete("delete from TB_YX_QC_CHANGE_EXEC_RECORD where sub_flow_record_id = #{subFlowRecordId}")
Integer deleteBySubFlowRecordId(@Param("subFlowRecordId") Long subFlowRecordId);
@Select("SELECT DISTINCT(change_record_id) FROM TB_YX_QC_CHANGE_EXEC_RECORD WHERE change_exec_user_email LIKE #{changeExecUser}")
List<Long> queryByUser(@Param("changeExecUser") String changeExecUser);
@Select("SELECT * FROM TB_YX_QC_CHANGE_EXEC_RECORD ORDER BY id DESC LIMIT #{limit},#{offset}")
List<ChangeExecRecord> queryList(@Param("limit") int limit, @Param("offset") Integer offset);
/**
* 根据变更行动工单记录ID查询行动项列表
* @param subFlowRecordId 变更行动工单记录ID
* @return 行动项列表
*/
@Select("SELECT * FROM TB_YX_QC_CHANGE_EXEC_RECORD WHERE sub_flow_record_id = #{subFlowRecordId}")
List<ChangeExecRecord> selectBySubFlowRecordId(@Param("subFlowRecordId") Long subFlowRecordId);
/**
* 批量根据变更行动工单记录ID列表查询行动项列表
* @param subFlowRecordIds 变更行动工单记录ID列表
* @return 行动项列表
*/
@Select("<script>" +
"SELECT * FROM TB_YX_QC_CHANGE_EXEC_RECORD WHERE sub_flow_record_id IN " +
"<foreach collection='subFlowRecordIds' item='id' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach>" +
"</script>")
List<ChangeExecRecord> selectBySubFlowRecordIds(@Param("subFlowRecordIds") List<Long> subFlowRecordIds);
/**
* 根据变更行动人和部门查询变更行动工单记录ID列表(合并查询)
* @param changeExecUser 变更行动人(邮箱),可为空
* @param changeExecDepartment 变更行动部门,可为空
* @return 变更行动工单记录ID列表
*/
@Select({
"<script>",
"SELECT DISTINCT(sub_flow_record_id) FROM TB_YX_QC_CHANGE_EXEC_RECORD",
"WHERE sub_flow_record_id IS NOT NULL",
"<if test='changeExecUser != null'>",
"AND change_exec_user_email LIKE #{changeExecUser}",
"</if>",
"<if test='changeExecDepartment != null'>",
"AND change_exec_department LIKE CONCAT('%',#{changeExecDepartment},'%')",
"</if>",
"</script>"
})
List<Long> querySubFlowRecordIdsByExecCondition(@Param("changeExecUser") String changeExecUser,
@Param("changeExecDepartment") String changeExecDepartment);
}
......@@ -25,6 +25,19 @@ public interface ChangeRecordMapper extends tk.mybatis.mapper.common.Mapper<Chan
@Select("select * from TB_YX_QC_CHANGE_RECORD where `flow_id` = #{flowId} limit 1")
ChangeRecord selectByFlowId(@Param("flowId") Long flowId);
/**
* 批量根据ID列表查询变更记录
* @param ids ID列表
* @return 变更记录列表
*/
@Select("<script>" +
"SELECT * FROM TB_YX_QC_CHANGE_RECORD WHERE id IN " +
"<foreach collection='ids' item='id' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach>" +
"</script>")
List<ChangeRecord> selectByIds(@Param("ids") List<Long> ids);
List<ChangeRecord> selectByCondition(ChangeFlowListQueryReq changeFlowListQueryReq);
List<ChangeRecord> selectByConditionAndLimit(ChangeFlowListQueryReq changeFlowListQueryReq);
......
package com.netease.mail.yanxuan.change.dal.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import com.netease.mail.yanxuan.change.dal.entity.ChangeSubFlowFile;
/**
* 子单文件 Mapper
*/
@Mapper
public interface ChangeSubFlowFileMapper extends tk.mybatis.mapper.common.Mapper<ChangeSubFlowFile> {
/**
* 根据子单记录ID查询文件列表
*/
@Select("SELECT * FROM TB_YX_QC_CHANGE_SUB_FLOW_FILE WHERE sub_flow_record_id = #{subFlowRecordId}")
List<ChangeSubFlowFile> selectBySubFlowRecordId(@Param("subFlowRecordId") Long subFlowRecordId);
/**
* 批量插入文件
*/
@Insert({ "<script>",
"insert into TB_YX_QC_CHANGE_SUB_FLOW_FILE(sub_flow_record_id, file_type, file_name, file_url, create_time, update_time) values ",
"<foreach collection='list' item='item' index='index' separator=','>",
"(#{item.subFlowRecordId}, #{item.fileType}, #{item.fileName}, #{item.fileUrl}, #{item.createTime}, #{item.updateTime})",
"</foreach>", "</script>" })
void insertList(List<ChangeSubFlowFile> files);
/**
* 根据子单记录ID删除文件
*/
@Delete("delete from TB_YX_QC_CHANGE_SUB_FLOW_FILE where sub_flow_record_id = #{subFlowRecordId}")
Integer deleteBySubFlowRecordId(@Param("subFlowRecordId") Long subFlowRecordId);
/**
* 根据子单记录ID和文件类型删除文件
*/
@Delete("delete from TB_YX_QC_CHANGE_SUB_FLOW_FILE where sub_flow_record_id = #{subFlowRecordId} and file_type = #{type}")
void deleteBySubFlowRecordIdAndType(@Param("subFlowRecordId") Long subFlowRecordId, @Param("type") Integer type);
}
/**
* @(#)ChangeSubFlowRecordMapper.java, 2024/01/01.
* <p/>
* Copyright 2024 Netease, Inc. All rights reserved.
* NETEASE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.netease.mail.yanxuan.change.dal.mapper;
import java.util.List;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeSubFlowListQueryReq;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import com.netease.mail.yanxuan.change.dal.entity.ChangeSubFlowRecord;
/**
* @Author system
* @Date 2024/01/01
*/
@Mapper
public interface ChangeSubFlowRecordMapper extends tk.mybatis.mapper.common.Mapper<ChangeSubFlowRecord> {
/**
* 根据主单记录ID查询变更行动工单列表
* @param changeRecordId 主单记录ID
* @return 变更行动工单列表
*/
@Select("SELECT * FROM TB_YX_QC_CHANGE_SUB_FLOW_RECORD WHERE change_record_id = #{changeRecordId}")
List<ChangeSubFlowRecord> selectByChangeRecordId(@Param("changeRecordId") Long changeRecordId);
/**
* 根据变更行动工单ID查询变更行动工单记录
* @param subFlowId 变更行动工单ID(即topo)
* @return 变更行动工单记录
*/
@Select("SELECT * FROM TB_YX_QC_CHANGE_SUB_FLOW_RECORD WHERE sub_flow_id = #{subFlowId} LIMIT 1")
ChangeSubFlowRecord selectBySubFlowId(@Param("subFlowId") String subFlowId);
/**
* 批量根据主单记录ID列表查询变更行动工单列表
* @param changeRecordIds 主单记录ID列表
* @return 变更行动工单列表
*/
@Select("<script>" +
"SELECT * FROM TB_YX_QC_CHANGE_SUB_FLOW_RECORD " +
"WHERE change_record_id IN " +
"<foreach collection='changeRecordIds' item='id' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach>" +
"</script>")
List<ChangeSubFlowRecord> selectByChangeRecordIds(@Param("changeRecordIds") List<Long> changeRecordIds);
/**
* 批量根据ID列表查询变更行动工单列表
* @param ids ID列表
* @return 变更行动工单列表
*/
@Select("<script>" +
"SELECT * FROM TB_YX_QC_CHANGE_SUB_FLOW_RECORD " +
"WHERE id IN " +
"<foreach collection='ids' item='id' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach>" +
"</script>")
List<ChangeSubFlowRecord> selectByIds(@Param("ids") List<Long> ids);
/**
* 根据条件查询变更行动工单列表
* @param queryReq 查询条件
* @return 变更行动工单列表
*/
List<ChangeSubFlowRecord> selectByCondition(ChangeSubFlowListQueryReq queryReq);
}
......@@ -36,4 +36,18 @@ public interface ChangeTypeMapper extends tk.mybatis.mapper.common.Mapper<Change
@Select("select * from TB_YX_QC_CHANGE_TYPE where parent_id = #{id} and delete_type = 0")
List<ChangeType> queryParent(@Param("id") Long id);
/**
* 批量根据ID列表查询变更类型
* @param ids ID列表
* @return 变更类型列表
*/
@Select("<script>" +
"SELECT * FROM TB_YX_QC_CHANGE_TYPE WHERE id IN " +
"<foreach collection='ids' item='id' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach>" +
" AND delete_type = 0" +
"</script>")
List<ChangeType> selectByIds(@Param("ids") List<Long> ids);
}
\ No newline at end of file
......@@ -49,5 +49,15 @@ public class ChangeGoodsPrincipalPO {
*/
private String goodsSqeEmail;
/**
* 合规 名称
*/
private String goodsComplianceName;
/**
* 合规 邮箱
*/
private String goodsComplianceEmail;
}
......@@ -45,5 +45,14 @@ public class ChangeConfigQueryReq {
*/
private Long updateTime;
/**
* 分页偏移量(导出使用)
*/
private Long offset;
/**
* 分页限制(导出使用)
*/
private Long limit;
}
......@@ -29,7 +29,7 @@ public class ChangeExecConfigReq {
private Long changeTemplateId;
/**
* 变更执行部门名
* 变更行动部门,(历史字段:变更执行部门名),改
*/
@NotBlank(message = "变更执行部门名不能为空")
private String changeExecDepartment;
......@@ -65,7 +65,7 @@ public class ChangeExecConfigReq {
private String changeChecking;
/**
* 行动完成时间
* 变更完成时间(历史字段:行动完成时间),改
*/
private Long changeExecFinishTime;
......
......@@ -6,7 +6,6 @@
*/
package com.netease.mail.yanxuan.change.dal.meta.model.req;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
......@@ -25,7 +24,12 @@ public class ChangeFlowCancelReq {
@NotNull(message = "工单id不能为空")
private Long flowId;
@NotBlank(message = "取消原因不能为空")
@Size(max = 200, message = "取消原因限制200字")
private String cancelReason;
/**
* 操作类型:CANCEL-取消,TERMINATION-终止
* 默认值为 CANCEL,保持向后兼容
*/
private String type = "CANCEL";
}
\ No newline at end of file
......@@ -120,9 +120,8 @@ public class ChangeFlowCreateReq {
private String changeProfitDesc;
/**
* 变更结果确认时间
* 预计变更切换时间,(历史字段:变更结果确认时间),改
*/
//@NotNull(message = "变更结果确认时间不能为空")
private Long changeConfirmResultTime;
/**
......@@ -152,4 +151,10 @@ public class ChangeFlowCreateReq {
* 变更收益金额
*/
private String changeProfitAmount;
/**
* 批次说明,new
*/
@Size(max = 500, message = "批次说明限500字")
private String batchDescription;
}
\ No newline at end of file
......@@ -41,11 +41,16 @@ public class ChangeFlowListQueryReq {
private Long sonChangeClassId;
/**
* 工单状态
* 工单状态(内部详细状态,单个状态值)
*/
private Integer changeState;
/**
* 工单状态列表(用于IN查询,由同舟端状态枚举编码转换而来)
*/
private List<Integer> changeStateList;
/**
* 变更负责人
*/
private String changeCommander;
......@@ -96,6 +101,18 @@ public class ChangeFlowListQueryReq {
*/
private Long flowId;
/**
* 数据可见范围:仅查看我跟进的工单(默认true)
* true: 仅展示 "变更申请人/变更负责人/变更审批人=当前用户" 的变更工单
* false: 展示所有工单
*/
private Boolean onlyMyFollowed;
/**
* 当前用户邮箱(用于数据可见范围过滤)
*/
private String currentUser;
private Long limit;
private Long offset;
......
......@@ -22,6 +22,11 @@ public class ChangeFlowRequest {
private String itemId;
/**
* 变更SKU
*/
private String skuId;
/**
* 变更供应商(变更主体为供应商时有效)
*/
private String supplier;
......@@ -72,6 +77,11 @@ public class ChangeFlowRequest {
*/
private Integer createSource;
/**
* 创建供应商(同舟端创建)
*/
private String createSupplier;
private String serviceCode;
private String uid;
......@@ -80,4 +90,21 @@ public class ChangeFlowRequest {
* 工单id
*/
private Long flowId;
/**
* 变更执行人
*/
private String changeExecUser;
/**
* 数据可见范围:仅查看我跟进的工单(默认true)
* true: 仅展示 "变更申请人/变更负责人/变更审批人=当前用户" 的变更工单
* false: 展示所有工单
*/
private Boolean onlyMyFollowed;
/**
* 当前用户邮箱(用于数据可见范围过滤)
*/
private String currentUser;
}
\ No newline at end of file
......@@ -120,4 +120,19 @@ public class ChangeFlowSubmitReq {
* 变更收益金额
*/
private String changeProfitAmount;
/**
* 审批结果(审批节点使用:true-通过,false-不通过)
*/
private Boolean approved;
/**
* 拒绝原因(审批不通过时必填)
*/
private String rejectReason;
/**
* 要驳回的子单ID列表(审批不通过时,选择要驳回的行动工单,必填)
*/
private List<String> rejectSubFlowIds;
}
\ No newline at end of file
/**
* @(#)ChangeFlowUrgeReq.java, 2024/12/16.
* <p/>
* Copyright 2024 Netease, Inc. All rights reserved.
* NETEASE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.netease.mail.yanxuan.change.dal.meta.model.req;
import javax.validation.constraints.NotNull;
import lombok.Data;
/**
* 主工单催办请求
*
* @Author system
* @Date 2024/12/16
*/
@Data
public class ChangeFlowUrgeReq {
/**
* 工单id
*/
@NotNull(message = "工单id不能为空")
private Long flowId;
}
/**
* @(#)ChangeFlowCreateReq.java, 2022/11/15.
* <p/>
* Copyright 2022 Netease, Inc. All rights reserved.
* NETEASE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.netease.mail.yanxuan.change.dal.meta.model.req;
import lombok.Data;
/**
* @Author zcwang
* @Date 2022/11/15
*/
@Data
public class ChangeSubFlowCreateReq {
}
\ No newline at end of file
/**
* @(#)ChangeSubFlowListQueryReq.java, 2024/01/01.
* <p/>
* Copyright 2024 Netease, Inc. All rights reserved.
* NETEASE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.netease.mail.yanxuan.change.dal.meta.model.req;
import lombok.Data;
import java.util.List;
/**
* 变更行动工单列表查询请求
*
* @Author system
* @Date 2024/01/01
*/
@Data
public class ChangeSubFlowListQueryReq {
/**
* 变更工单ID(主单flowId)
*/
private Long flowId;
/**
* 变更行动工单ID(subFlowId)
*/
private String subFlowId;
/**
* 商品ID
*/
private String itemId;
/**
* 供应商
*/
private String supplier;
/**
* 变更行动人(姓名或邮箱)
*/
private String changeExecUser;
/**
* 变更行动部门
*/
private String changeExecDepartment;
/**
* 行动工单状态
* @see com.netease.mail.yanxuan.change.common.enums.ChangeSubFlowStatusEnum
*/
private Integer status;
/**
* 开始时间(行动工单发起时间)
*/
private Long startTime;
/**
* 结束时间(行动工单发起时间)
*/
private Long endTime;
/**
* 数据可见范围:仅查看我跟进的工单(默认true)
* true: 仅展示当前用户相关的变更行动工单
* false: 展示所有变更行动工单
* “变更行动人/行动项审批人=当前用户” 的变更行动工单
* “变更负责人=当前用户”的变更工单所关联的全部变更行动工单
*/
private Boolean onlyMyFollowed;
/**
* 当前用户邮箱(用于数据可见范围过滤)
*/
private String currentUser;
/**
* 变更记录ID列表(用于过滤)
*/
private List<Long> changeRecordIds;
/**
* 变更行动工单ID列表(用于过滤)
*/
private List<String> subFlowIds;
}
/**
* @(#)ChangeSubFlowSubmitReq.java, 2025/01/18.
* <p/>
* Copyright 2025 Netease, Inc. All rights reserved.
* NETEASE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.netease.mail.yanxuan.change.dal.meta.model.req;
import javax.validation.constraints.NotBlank;
import java.util.List;
import lombok.Data;
/**
* 子流程提交请求
* @Author zcwang
* @Date 2025/01/18
*/
@Data
public class ChangeSubFlowSubmitReq {
/**
* 子流程工单id
*/
@NotBlank(message = "子流程工单id不能为空")
private String subFlowId;
/**
* 当前节点id
*/
@NotBlank(message = "当前节点不能为空")
private String currentNodeId;
/**
* 行动完成时间(提交执行结果时必填)
*/
private Long changeExecFinishTime;
/**
* 行动完成情况(提交执行结果时必填)
*/
private String changeExecFinishDesc;
/**
* 行动项列表(确认行动方案时使用)
*/
private List<ChangeExecConfigReq> changeExecProjectList;
/**
* 审批是否通过(审批行动方案时使用)
*/
private Boolean approved;
/**
* 拒绝原因(审批不通过时必填)
*/
private String rejectReason;
/**
* 变更结论(提交执行结果时使用)
* @see com.netease.mail.yanxuan.change.common.enums.ChangeResultEnum
*/
private Integer changeResult;
/**
* 备注
*/
private String remark;
/**
* 取消原因(变更结论为取消时必填)
*/
private String cancelReason;
/**
* 变更确认结果时间(延期时必填)
*/
private Long changeConfirmResultTime;
/**
* 变更结果文件列表
*/
private List<ChangeFlowFile> changeResultFiles;
}
......@@ -79,4 +79,9 @@ public class ChangeFlowExecVO {
* 行动完成情况
*/
private String changeExecFinishDesc;
/**
* 子流程工单ID
*/
private String subFlowId;
}
\ No newline at end of file
/**
* @(#)ChangeFlowOwnershipVO.java, 2024/12/04.
* <p/>
* Copyright 2024 Netease, Inc. All rights reserved.
* NETEASE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.netease.mail.yanxuan.change.dal.meta.model.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 主单归属关系(用于标识当前用户与主单的归属关系)
* 对应查询条件:creator = 当前用户 OR change_commander = 当前用户 OR approver 包含当前用户
*
* @Author system
* @Date 2024/12/04
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ChangeFlowOwnershipVO {
/**
* 是否是创建人(creator)
*/
private Boolean isCreator;
/**
* 是否是变更负责人(change_commander)
*/
private Boolean isChangeCommander;
/**
* 是否是审批人(approver)
*/
private Boolean isApprover;
}
......@@ -10,7 +10,6 @@ import java.util.List;
import com.netease.mail.yanxuan.change.common.enums.ChangeStatusEnum;
import com.netease.mail.yanxuan.change.common.enums.ChangeSubjectEnum;
import com.netease.mail.yanxuan.change.dal.entity.ChangeExecRecord;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeFlowFile;
import lombok.AllArgsConstructor;
......@@ -111,6 +110,11 @@ public class ChangeFlowVO {
* 变更收益金额
*/
private String changeProfitAmount;
/**
* 批次说明
*/
private String batchDescription;
/**
* 变更商品列表
*/
......@@ -128,6 +132,12 @@ public class ChangeFlowVO {
* @see ChangeStatusEnum
*/
private Integer changeState;
/**
* 变更状态描述
*/
private String changeStateDesc;
/**
* 变更发起人
*/
......@@ -152,11 +162,26 @@ public class ChangeFlowVO {
private Long createTime;
/**
* 变更结果确认时间
* 变更结果确认时间(预期完成时间)
*/
private Long changeConfirmResultTime;
/**
* 变更完结时间(实际完成时间/取消时间)
* 未完成的工单:展示预期完成时间(changeConfirmResultTime)
* 已完成工单:展示实际完成时间(updateTime)
* 取消的工单:展示取消时间(updateTime)
*/
private Long changeEndTime;
/**
* 是否超出预计完成时间(用于前端标红展示)
* true: 已完成工单的实际完成时间超出预期完成时间
* false: 其他情况
*/
private Boolean isOverdue;
/**
* 取消原因
*/
private String cancelReason;
......@@ -190,4 +215,19 @@ public class ChangeFlowVO {
* 当前节点
*/
private String flowNode;
/**
* 主单节点(用于子单详情页面判断,当子单到达CHANGE_SUB_FLOW_CONFIRM但主单未到达NEW_CHANGE_FLOW_EXE时,隐藏提交内容)
*/
private String mainFlowNode;
/**
* 变更行动工单ID列表(即subFlowId列表)
*/
private List<String> subFlowIds;
/**
* 主单归属关系(标识当前用户与主单的归属关系)
*/
private ChangeFlowOwnershipVO flowOwnership;
}
\ No newline at end of file
/**
* @(#)ChangeSubFlowListVO.java, 2024/01/01.
* <p/>
* Copyright 2024 Netease, Inc. All rights reserved.
* NETEASE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.netease.mail.yanxuan.change.dal.meta.model.vo;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 变更行动工单列表VO
*
* @Author system
* @Date 2024/01/01
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ChangeSubFlowListVO {
/**
* 分页信息
*/
private PageVO pageVo;
/**
* 变更行动工单列表
*/
private List<ChangeSubFlowVO> changeSubFlowList;
}
/**
* @(#)ChangeSubFlowOwnershipVO.java, 2024/12/04.
* <p/>
* Copyright 2024 Netease, Inc. All rights reserved.
* NETEASE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.netease.mail.yanxuan.change.dal.meta.model.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 子单归属关系(用于标识当前用户与子单的归属关系)
* 对应查询条件:
* 1. 变更行动人/行动项审批人=当前用户(change_exec_user_email = 当前用户 OR approver 包含当前用户)
* 2. 变更负责人=当前用户的变更工单所关联的全部变更行动工单(change_commander = 当前用户)
*
* @Author system
* @Date 2024/12/04
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ChangeSubFlowOwnershipVO {
/**
* 是否是执行人(change_exec_user_email)
*/
private Boolean isExecutor;
/**
* 是否是审批人(approver)
*/
private Boolean isApprover;
/**
* 是否是主单变更负责人(change_commander,关联主单的负责人)
*/
private Boolean isMainFlowCommander;
}
/**
* @(#)ChangeSubFlowVO.java, 2024/01/01.
* <p/>
* Copyright 2024 Netease, Inc. All rights reserved.
* NETEASE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.netease.mail.yanxuan.change.dal.meta.model.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 变更行动工单VO
*
* @Author system
* @Date 2024/01/01
*/
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class ChangeSubFlowVO {
/**
* 变更行动ID(subFlowId)
*/
private String subFlowId;
/**
* 变更ID(主单的flowId)
*/
private Long flowId;
/**
* 变更行动人邮箱
*/
private String changeExecUserEmail;
/**
* 变更行动人姓名
*/
private String changeExecUserName;
/**
* 变更行动人(姓名+邮箱)
*/
private String changeExecUserView;
/**
* 变更行动部门
*/
private String changeExecDepartment;
/**
* 行动工单状态
* @see com.netease.mail.yanxuan.change.common.enums.ChangeSubFlowStatusEnum
*/
private Integer status;
/**
* 行动工单状态描述
*/
private String statusDesc;
/**
* 行动完成时间(变更行动工单的完成时间,或预期完成时间)
*/
private Long changeExecFinishTime;
/**
* 创建时间(行动工单发起时间)
*/
private Long createTime;
/**
* 子单归属关系(标识当前用户与子单的归属关系)
*/
private ChangeSubFlowOwnershipVO flowOwnership;
}
......@@ -62,6 +62,39 @@
order by create_time desc
</select>
<select id="selectChangeConfigPoWithLimit" resultMap="BaseResultPoMap" parameterType="com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeConfigQueryReq">
select <include refid="Base_Column_List" />
from TB_YX_QC_CHANGE_CONFIG
<where>
<if test="parentChangeClassId != null ">
parent_change_class_id = #{parentChangeClassId} and
</if>
<if test="sonChangeClassId != null ">
son_change_class_id = #{sonChangeClassId} and
</if>
<if test="changeLevel != null and changeLevel != ''">
change_level = #{changeLevel} and
</if>
<if test="changeDepartment != null and changeDepartment != ''">
change_department = #{changeDepartment} and
</if>
<if test="createTime != null">
create_time <![CDATA[ > ]]> #{createTime} and
</if>
<if test="updateTime != null">
create_time <![CDATA[ < ]]> #{updateTime} and
</if>
<if test="changeExecDepartment != null and changeExecDepartment != ''">
participate_change_exec_department like "%" #{changeExecDepartment} "%" and
</if>
1=1
</where>
order by create_time desc
<if test="offset != null and limit != null">
limit #{offset},#{limit}
</if>
</select>
<select id="queryByparentChangeClassId" resultType="com.netease.mail.yanxuan.change.dal.entity.ChangeType">
select * from TB_YX_QC_CHANGE_TYPE where id = #{parentId} or id = #{sonId} and delete_type = 0 order by parent_id
</select>
......
......@@ -29,8 +29,10 @@
<result column="create_supplier" jdbcType="VARCHAR" property="createSupplier" />
<result column="change_result_desc" jdbcType="VARCHAR" property="changeResultDesc" />
<result column="change_profit_amount" jdbcType="VARCHAR" property="changeProfitAmount" />
<result column="batch_description" jdbcType="VARCHAR" property="batchDescription" />
<result column="remark" jdbcType="VARCHAR" property="remark" />
<result column="creator" jdbcType="VARCHAR" property="creator" />
<result column="approver" jdbcType="VARCHAR" property="approver" />
<result column="create_time" jdbcType="BIGINT" property="createTime" />
<result column="update_time" jdbcType="BIGINT" property="updateTime" />
</resultMap>
......@@ -39,10 +41,9 @@
change_level, change_commander, change_department, participate_change_exec_department,
change_item, change_sku, change_supplier, change_reason, change_content, change_risk_desc, change_checking,
change_profit, change_profit_desc, change_confirm_result_time, `state`, cancel_reason,
change_result, create_source, create_supplier, change_result_desc, remark, creator, change_profit_amount,
change_result, create_source, create_supplier, change_result_desc, remark, creator, approver, change_profit_amount, batch_description,
create_time, update_time
</sql>
<select id="selectByCondition" resultMap="BaseResultMap" parameterType="com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeFlowListQueryReq">
select <include refid="Base_Column_List" />
from TB_YX_QC_CHANGE_RECORD
......@@ -68,6 +69,12 @@
<if test="changeState != null">
and `state` = #{changeState}
</if>
<if test="changeStateList != null and changeStateList.size() > 0">
and `state` in
<foreach collection="changeStateList" item="status" open="(" separator="," close=")">
#{status}
</foreach>
</if>
<if test="changeCommander != null and changeCommander != ''">
and `change_commander` = #{changeCommander}
</if>
......@@ -95,6 +102,13 @@
${item}
</foreach>
</if>
<if test="currentUser != null">
and (
creator = #{currentUser}
or change_commander = #{currentUser}
or approver like concat('%',#{currentUser},'%')
)
</if>
</where>
order by create_time desc
</select>
......@@ -124,6 +138,12 @@
<if test="changeState != null">
and `state` = #{changeState}
</if>
<if test="changeStateList != null and changeStateList.size() > 0">
and `state` in
<foreach collection="changeStateList" item="status" open="(" separator="," close=")">
#{status}
</foreach>
</if>
<if test="changeCommander != null and changeCommander != ''">
and `change_commander` = #{changeCommander}
</if>
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.netease.mail.yanxuan.change.dal.mapper.ChangeSubFlowRecordMapper">
<resultMap id="BaseResultMap" type="com.netease.mail.yanxuan.change.dal.entity.ChangeSubFlowRecord">
<id column="id" jdbcType="BIGINT" property="id" />
<result column="change_record_id" jdbcType="BIGINT" property="changeRecordId" />
<result column="sub_flow_id" jdbcType="VARCHAR" property="subFlowId" />
<result column="sub_flow_node" jdbcType="VARCHAR" property="subFlowNode" />
<result column="status" jdbcType="INTEGER" property="status" />
<result column="approver" jdbcType="VARCHAR" property="approver" />
<result column="change_exec_user_email" jdbcType="VARCHAR" property="changeExecUserEmail" />
<result column="change_exec_department" jdbcType="VARCHAR" property="changeExecDepartment" />
<result column="change_commander" jdbcType="VARCHAR" property="changeCommander" />
<result column="reject_reason" jdbcType="VARCHAR" property="rejectReason" />
<result column="change_result" jdbcType="INTEGER" property="changeResult" />
<result column="remark" jdbcType="VARCHAR" property="remark" />
<result column="cancel_reason" jdbcType="VARCHAR" property="cancelReason" />
<result column="change_confirm_result_time" jdbcType="BIGINT" property="changeConfirmResultTime" />
<result column="create_time" jdbcType="BIGINT" property="createTime" />
<result column="update_time" jdbcType="BIGINT" property="updateTime" />
</resultMap>
<sql id="Base_Column_List">
id, change_record_id, sub_flow_id, sub_flow_node, status, approver, change_exec_user_email, change_exec_department, change_commander, reject_reason, change_result, remark, cancel_reason, change_confirm_result_time, create_time, update_time
</sql>
<select id="selectByCondition" resultMap="BaseResultMap" parameterType="com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeSubFlowListQueryReq">
SELECT <include refid="Base_Column_List" />
FROM TB_YX_QC_CHANGE_SUB_FLOW_RECORD
<where>
<if test="subFlowId != null and subFlowId != ''">
and sub_flow_id = #{subFlowId}
</if>
<if test="status != null">
and status = #{status}
</if>
<if test="startTime != null">
and create_time >= #{startTime}
</if>
<if test="endTime != null">
and create_time <![CDATA[ <= ]]> #{endTime}
</if>
<if test="changeRecordIds != null and changeRecordIds.size() > 0">
and change_record_id in
<foreach collection="changeRecordIds" close=")" open="(" item="item" separator=",">
#{item}
</foreach>
</if>
<if test="subFlowIds != null and subFlowIds.size() > 0">
and sub_flow_id in
<foreach collection="subFlowIds" close=")" open="(" item="item" separator=",">
#{item}
</foreach>
</if>
<if test="changeExecUser != null and changeExecUser != ''">
and change_exec_user_email LIKE CONCAT('%', #{changeExecUser}, '%')
</if>
<if test="changeExecDepartment != null and changeExecDepartment != ''">
and change_exec_department LIKE CONCAT('%', #{changeExecDepartment}, '%')
</if>
<if test="currentUser != null and currentUser != ''">
and (change_exec_user_email = #{currentUser} or approver LIKE CONCAT('%"', #{currentUser}, '"%') or change_commander = #{currentUser})
</if>
</where>
order by create_time desc
</select>
</mapper>
......@@ -21,7 +21,9 @@ public enum EmailTemplateEnum {
YX_QC_CHANGE_FINISH("yx_qc_change_finish","工单完结","【已完结】%s 变更风险行动项已完结,请开始执行变更。"),
YX_QC_CHANGE_PENDING("yx_qc_change_pending","待处理","【待处理】执行标准号 执行标准名称已变更 请排查对应的商品变更");
YX_QC_CHANGE_PENDING("yx_qc_change_pending","待处理","【待处理】执行标准号 执行标准名称已变更 请排查对应的商品变更"),
YX_QC_CHANGE_URGE("yx_qc_change_urge","变更催办","【催办提醒】%s 变更工单超期催办通知");
/**
* 模版code
*/
......
package com.netease.mail.yanxuan.change.integration.excel;
import cn.afterturn.easypoi.excel.annotation.Excel;
import lombok.Data;
/**
* 变更配置导出DTO(一层保持原样,二层为行动项:变更行动项部门、变更行动人、变更风险描述、行动项、变更验证措施)
*/
@Data
public class ChangeConfigExcelDTO {
@Excel(name = "配置ID")
private Long id;
@Excel(name = "变更主体")
private String changeSubject;
@Excel(name = "变更类型")
private String changeType;
@Excel(name = "变更等级")
private String changeLevel;
@Excel(name = "变更负责部门")
private String changeDepartment;
@Excel(name = "变更执行部门")
private String participateChangeExecDepartment;
@Excel(name = "变更负责人类型")
private String changeCommanderType;
@Excel(name = "变更负责人")
private String changeCommander;
@Excel(name = "是否需要资料")
private String needFile;
@Excel(name = "创建人")
private String creator;
@Excel(name = "创建时间")
private String createTime;
@Excel(name = "更新时间")
private String updateTime;
/** 二层:变更行动项部门 */
@Excel(name = "变更行动项部门")
private String changeExecDepartment;
/** 二层:变更行动人 */
@Excel(name = "变更行动人")
private String changeExecUser;
/** 二层:变更风险描述 */
@Excel(name = "变更风险描述")
private String changeRiskDesc;
/** 二层:行动项 */
@Excel(name = "行动项")
private String changeExecProject;
/** 二层:变更验证措施 */
@Excel(name = "变更验证措施")
private String changeChecking;
}
package com.netease.mail.yanxuan.change.integration.excel;
import java.util.ArrayList;
import java.util.List;
import com.netease.mail.dp.des.common.api.enums.ExcelCellType;
import com.netease.mail.dp.des.process.api.entity.ExcelCell;
import com.netease.mail.yanxuan.change.common.anno.ExcelTitleName;
import lombok.Data;
/**
* 变更配置导出VO(一层保持原样,二层新增:变更行动项部门、变更行动人、变更风险描述、行动项、变更验证措施)
*/
@Data
public class ChangeConfigExcelVo {
@ExcelTitleName(title = "配置ID", order = 1)
private Long id;
@ExcelTitleName(title = "变更主体", order = 2)
private String changeSubject;
@ExcelTitleName(title = "变更类型", order = 3)
private String changeType;
@ExcelTitleName(title = "变更等级", order = 4)
private String changeLevel;
@ExcelTitleName(title = "变更负责部门", order = 5)
private String changeDepartment;
@ExcelTitleName(title = "变更执行部门", order = 6)
private String participateChangeExecDepartment;
@ExcelTitleName(title = "变更负责人类型", order = 7)
private String changeCommanderType;
@ExcelTitleName(title = "变更负责人", order = 8)
private String changeCommander;
@ExcelTitleName(title = "是否需要资料", order = 9)
private String needFile;
@ExcelTitleName(title = "创建人", order = 10)
private String creator;
@ExcelTitleName(title = "创建时间", order = 11)
private String createTime;
@ExcelTitleName(title = "更新时间", order = 12)
private String updateTime;
@ExcelTitleName(title = "变更行动项部门", order = 13)
private String changeExecDepartment;
@ExcelTitleName(title = "变更行动人", order = 14)
private String changeExecUser;
@ExcelTitleName(title = "变更风险描述", order = 15)
private String changeRiskDesc;
@ExcelTitleName(title = "行动项", order = 16)
private String changeExecProject;
@ExcelTitleName(title = "变更验证措施", order = 17)
private String changeChecking;
public static List<ExcelCell> init(ChangeConfigExcelDTO dto) {
List<ExcelCell> cells = new ArrayList<>();
ExcelCell excelCell;
// 1. 配置ID
excelCell = new ExcelCell();
excelCell.setCellType(ExcelCellType.TEXT);
excelCell.setValue(dto.getId() != null ? dto.getId().toString() : "");
cells.add(excelCell);
// 2. 变更主体
excelCell = new ExcelCell();
excelCell.setValue(dto.getChangeSubject() != null ? dto.getChangeSubject() : "");
cells.add(excelCell);
// 3. 变更类型
excelCell = new ExcelCell();
excelCell.setValue(dto.getChangeType() != null ? dto.getChangeType() : "");
cells.add(excelCell);
// 4. 变更等级
excelCell = new ExcelCell();
excelCell.setValue(dto.getChangeLevel() != null ? dto.getChangeLevel() : "");
cells.add(excelCell);
// 5. 变更负责部门
excelCell = new ExcelCell();
excelCell.setValue(dto.getChangeDepartment() != null ? dto.getChangeDepartment() : "");
cells.add(excelCell);
// 6. 变更执行部门
excelCell = new ExcelCell();
excelCell.setValue(dto.getParticipateChangeExecDepartment() != null ? dto.getParticipateChangeExecDepartment() : "");
cells.add(excelCell);
// 7. 变更负责人类型
excelCell = new ExcelCell();
excelCell.setValue(dto.getChangeCommanderType() != null ? dto.getChangeCommanderType() : "");
cells.add(excelCell);
// 8. 变更负责人
excelCell = new ExcelCell();
excelCell.setValue(dto.getChangeCommander() != null ? dto.getChangeCommander() : "");
cells.add(excelCell);
// 9. 是否需要资料
excelCell = new ExcelCell();
excelCell.setValue(dto.getNeedFile() != null ? dto.getNeedFile() : "");
cells.add(excelCell);
// 10. 创建人
excelCell = new ExcelCell();
excelCell.setValue(dto.getCreator() != null ? dto.getCreator() : "");
cells.add(excelCell);
// 11. 创建时间
excelCell = new ExcelCell();
excelCell.setValue(dto.getCreateTime() != null ? dto.getCreateTime() : "");
cells.add(excelCell);
// 12. 更新时间
excelCell = new ExcelCell();
excelCell.setValue(dto.getUpdateTime() != null ? dto.getUpdateTime() : "");
cells.add(excelCell);
// 13. 变更行动项部门
excelCell = new ExcelCell();
excelCell.setValue(dto.getChangeExecDepartment() != null ? dto.getChangeExecDepartment() : "");
cells.add(excelCell);
// 14. 变更行动人
excelCell = new ExcelCell();
excelCell.setValue(dto.getChangeExecUser() != null ? dto.getChangeExecUser() : "");
cells.add(excelCell);
// 15. 变更风险描述
excelCell = new ExcelCell();
excelCell.setValue(dto.getChangeRiskDesc() != null ? dto.getChangeRiskDesc() : "");
cells.add(excelCell);
// 16. 行动项
excelCell = new ExcelCell();
excelCell.setValue(dto.getChangeExecProject() != null ? dto.getChangeExecProject() : "");
cells.add(excelCell);
// 17. 变更验证措施
excelCell = new ExcelCell();
excelCell.setValue(dto.getChangeChecking() != null ? dto.getChangeChecking() : "");
cells.add(excelCell);
return cells;
}
}
......@@ -166,6 +166,12 @@ public class ChangeFlowExcelDTO {
private String goodsProject;
/**
* 合规负责人
*/
@Excel(name = "合规负责人")
private String goodsCompliance;
/**
* 变更行动部门
*/
@Excel(name = "变更行动部门")
......@@ -217,4 +223,10 @@ public class ChangeFlowExcelDTO {
*/
@Excel(name = "变更完结时间")
private String changeEndTime;
/**
* 变更行动工单
*/
@Excel(name = "变更行动工单")
private String subFlowIds;
}
\ No newline at end of file
......@@ -171,58 +171,70 @@ public class ChangeFlowExcelVo {
private String goodsProjectName;
/**
* 合规负责人
*/
@ExcelTitleName(title = "合规负责人", order = 26)
private String goodsCompliance;
/**
* 变更行动部门
*/
@ExcelTitleName(title = "变更行动部门", order = 26)
@ExcelTitleName(title = "变更行动部门", order = 27)
private String execDepartment;
/**
* 变更行动人
*/
@ExcelTitleName(title = "变更行动人", order = 27)
@ExcelTitleName(title = "变更行动人", order = 28)
private String changeExecUser;
/**
* 行动项变更风险描述
*/
@ExcelTitleName(title = "变更风险描述", order = 28)
@ExcelTitleName(title = "变更风险描述", order = 29)
private String changeExecRiskDesc;
/**
* 行动项
*/
@ExcelTitleName(title = "行动项", order = 29)
@ExcelTitleName(title = "行动项", order = 30)
private String changeExecProject;
/**
* 变更验证措施
*/
@ExcelTitleName(title = "变更验证措施", order = 30)
@ExcelTitleName(title = "变更验证措施", order = 31)
private String changeChecking;
/**
* 行动完成时间
*/
@ExcelTitleName(title = "行动完成时间", order = 31)
@ExcelTitleName(title = "行动完成时间", order = 32)
private String changeExecFinishTime;
/**
* 行动完成情况
*/
@ExcelTitleName(title = "行动完成情况", order = 32)
@ExcelTitleName(title = "行动完成情况", order = 33)
private String changeExecFinishDesc;
/**
* 变更结论
*/
@ExcelTitleName(title = "变更结论", order = 33)
@ExcelTitleName(title = "变更结论", order = 34)
private String changeResult;
/**
* 变更完结时间
*/
@ExcelTitleName(title = "变更完结时间", order = 34)
@ExcelTitleName(title = "变更完结时间", order = 35)
private Integer changeEndTime;
/**
* 变更行动工单
*/
@ExcelTitleName(title = "变更行动工单", order = 36)
private String subFlowIds;
public static List<ExcelCell> init(ChangeFlowExcelDTO dto) {
List<ExcelCell> cellInfo = new ArrayList<>();
......@@ -327,7 +339,11 @@ public class ChangeFlowExcelVo {
excelCell = new ExcelCell();
excelCell.setValue(dto.getGoodsProject());
cellInfo.add(excelCell);
// 26.变更行动部门
// 26.合规负责人
excelCell = new ExcelCell();
excelCell.setValue(dto.getGoodsCompliance());
cellInfo.add(excelCell);
// 27.变更行动部门
excelCell = new ExcelCell();
excelCell.setValue(dto.getExecDepartment());
cellInfo.add(excelCell);
......@@ -363,6 +379,10 @@ public class ChangeFlowExcelVo {
excelCell = new ExcelCell();
excelCell.setValue(dto.getChangeEndTime());
cellInfo.add(excelCell);
// 35.变更行动工单
excelCell = new ExcelCell();
excelCell.setValue(dto.getSubFlowIds());
cellInfo.add(excelCell);
return cellInfo;
}
......
......@@ -17,4 +17,7 @@ public interface QcApiRpcService {
@GetMapping(value = "/xhr/item/basic/sqe.json")
String getDetail(@RequestParam("itemId") Integer itemId);
@GetMapping(value = "/xhr/qaManager/getQaUserListByItemId.json")
String getQaUserListByItemId(@RequestParam("itemId") Integer itemId);
}
package com.netease.mail.yanxuan.change.integration.flow.supplier;
import com.netease.mail.yanxuan.change.integration.flow.supplier.req.SupplierRelatedUserReq;
import com.netease.mail.yanxuan.change.integration.flow.supplier.rsp.ItemSupplierRsp;
import com.netease.mail.yanxuan.change.integration.flow.supplier.rsp.SupplierUserRsp;
import com.netease.yanxuan.flowx.sdk.meta.controller.communal.AjaxResponse;
import java.util.List;
/**
......@@ -16,7 +16,11 @@ public interface FlowRpcSupplierInfoService {
*/
List<SupplierUserRsp> querySupplierRelatedUser(SupplierRelatedUserReq req);
/**
* 根据商品Id列表查询供应商
* @param itemIds 商品Id列表
* @return 供应商信息列表
*/
List<ItemSupplierRsp> querySupplierByItemIds(List<Long> itemIds);
}
package com.netease.mail.yanxuan.change.integration.flow.supplier.impl;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -11,9 +13,12 @@ import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSON;
import com.netease.mail.yanxuan.change.integration.flow.supplier.FlowRpcSupplierInfoService;
import com.netease.mail.yanxuan.change.integration.flow.supplier.req.SupplierRelatedUserReq;
import com.netease.mail.yanxuan.change.integration.flow.supplier.rsp.ItemSupplierRsp;
import com.netease.mail.yanxuan.change.integration.flow.supplier.rsp.SupplierUserRsp;
import com.netease.mail.yanxuan.change.integration.rpc.RpcListData;
import com.netease.mail.yanxuan.change.integration.rpc.RpcTemplate;
import com.netease.mail.yanxuan.change.integration.supplier.SupplierRpcService;
import com.netease.yanxuan.flowx.sdk.meta.controller.communal.AjaxResponse;
/**
* @author WangJiaXiang
......@@ -27,6 +32,9 @@ public class FlowRpcSupplierInfoServiceImpl implements FlowRpcSupplierInfoServic
@Autowired
private RpcTemplate rpcTemplate;
@Autowired
private SupplierRpcService supplierRpcService;
@Value("${supplier.url}")
private String supplierUrl;
......@@ -45,4 +53,27 @@ public class FlowRpcSupplierInfoServiceImpl implements FlowRpcSupplierInfoServic
}
return objects;
}
@Override
public List<ItemSupplierRsp> querySupplierByItemIds(List<Long> itemIds) {
if (CollectionUtils.isEmpty(itemIds)) {
return new ArrayList<>();
}
try {
// 调用底层SupplierRpcService查询供应商信息
AjaxResponse<List<ItemSupplierRsp>> response = supplierRpcService.querySupplierByItemIds(itemIds);
logger.info("[querySupplierByItemIds] querySupplierByItemIds response: {}", JSON.toJSONString(response));
if (response == null || CollectionUtils.isEmpty(response.getData())) {
return new ArrayList<>();
}
return response.getData();
} catch (Exception e) {
logger.error("[querySupplierByItemIds] query supplier failed, itemIds={}", JSON.toJSONString(itemIds), e);
return new ArrayList<>();
}
}
}
......@@ -12,7 +12,7 @@ import java.util.List;
public class SupplierRelatedUserReq {
/**
* 搜索类型(1-采购 2-计划 3-SQE 4-商品开发)
* 搜索类型(1-采购 2-计划 3-SQE 4-商品开发 5-合规)
*/
private Integer searchType;
......
package com.netease.mail.yanxuan.change.integration.flow.supplier.rsp;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 商品关联供应商响应对象
*
* @author generated
* @date 2024/01/01
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ItemSupplierRsp {
/**
* 商品id
*/
private Long itemId;
/**
* 供应商ID
*/
private String supplierId;
/**
* 供应商名称
*/
private String supplierName;
/**
* 供应商的跟进采购Uid
*/
private String supplierTraceBusiness;
/**
* 供应商的跟进采购姓名
*/
private String supplierTraceBusinessName;
/**
* 质检报告id
*/
private Long qcReportId;
}
package com.netease.mail.yanxuan.change.integration.supplier;
import com.netease.mail.yanxuan.change.integration.flow.supplier.rsp.ItemSupplierRsp;
import com.netease.yanxuan.flowx.sdk.meta.controller.communal.AjaxResponse;
import com.netease.yanxuan.missa.client.annotation.MissaClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
/**
* 供应商RPC服务
*
* @author generated
* @date 2024/01/01
*/
@Service
@MissaClient(serviceCode = "yanxuan-supplier-ms")
public interface SupplierRpcService {
/**
* 根据商品Id列表查询供应商
* @param itemIds 商品Id列表
* @return 供应商信息列表
*/
@PostMapping("/api/initiateProject/source/getBatchRelatedSupplier.json")
AjaxResponse<List<ItemSupplierRsp>> querySupplierByItemIds(@RequestBody List<Long> itemIds);
}
......@@ -3,6 +3,7 @@ package com.netease.mail.yanxuan.change.web.controller;
import com.netease.mail.yanxuan.change.common.bean.AjaxResult;
import com.netease.mail.yanxuan.change.biz.service.change.AdminChangeConfigService;
import com.netease.mail.yanxuan.change.biz.service.DESExportService;
import com.netease.mail.yanxuan.change.common.bean.RequestLocalBean;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeConfigDeleteReq;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeConfigQueryReq;
......@@ -31,6 +32,9 @@ public class ChangeConfigController {
@Autowired
private FlowRpcSupplierSendService supplierSendService;
@Autowired
private DESExportService desExportService;
/**
* 通过参数查询配置模板
* @return
......@@ -134,4 +138,32 @@ public class ChangeConfigController {
//return supplierSendService.queryByConditionForQc(req); 暂用模拟数据
return listAjaxResponse;
}
/**
* 导出变更配置列表
*
* @return
*/
@GetMapping("/export")
public AjaxResult<String> export(
@RequestParam(name = "changeDepartment", required = false) String changeDepartment,
@RequestParam(name = "changeExecDepartment", required = false) String changeExecDepartment,
@RequestParam(name = "parentChangeClassId", required = false) Integer parentChangeClassId,
@RequestParam(name = "sonChangeClassId", required = false) Integer sonChangeClassId,
@RequestParam(name = "changeLevel", required = false) Integer changeLevel,
@RequestParam(name = "createTime", required = false) Long createTime,
@RequestParam(name = "endTime", required = false) Long endTime,
@RequestParam(name = "serviceCode") String serviceCode) {
ChangeConfigQueryReq req = new ChangeConfigQueryReq();
req.setChangeDepartment(changeDepartment);
req.setChangeExecDepartment(changeExecDepartment);
req.setParentChangeClassId(parentChangeClassId);
req.setSonChangeClassId(sonChangeClassId);
req.setChangeLevel(changeLevel);
req.setCreateTime(createTime);
req.setUpdateTime(endTime);
String uid = RequestLocalBean.getUid();
String result = desExportService.changeConfigExport(req, uid, serviceCode);
return AjaxResult.success(result);
}
}
......@@ -32,6 +32,7 @@ import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeFlowCancelReq;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeFlowCreateReq;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeFlowDeliverReq;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeFlowListQueryReq;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeFlowUrgeReq;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeFlowRequest;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeFlowSubmitReq;
import com.netease.mail.yanxuan.change.dal.meta.model.rpc.CommanderResponse;
......@@ -67,7 +68,7 @@ public class ChangeFlowController {
private DESExportService desExportService;
/**
* 变更工单详情查询
* 变更工单详情查询(改)
*
* @return
*/
......@@ -77,7 +78,7 @@ public class ChangeFlowController {
}
/**
* 变更工单列表查询
* 变更工单列表查询(改)
*
* @return
*/
......@@ -97,7 +98,8 @@ public class ChangeFlowController {
@RequestParam(name = "changeExecUser", required = false) String changeExecUser,
@RequestParam(name = "startTime", required = false) Long startTime,
@RequestParam(name = "endTime", required = false) Long endTime,
@RequestParam(name = "flowId", required = false) Long flowId) {
@RequestParam(name = "flowId", required = false) Long flowId,
@RequestParam(name = "onlyMyFollowed", defaultValue = "true") Boolean onlyMyFollowed) {
ChangeFlowListQueryReq changeFlowListQueryReq = new ChangeFlowListQueryReq();
changeFlowListQueryReq.setChangeExecUser(changeExecUser);
changeFlowListQueryReq.setItemId(itemId);
......@@ -112,16 +114,18 @@ public class ChangeFlowController {
changeFlowListQueryReq.setStartTime(startTime);
changeFlowListQueryReq.setEndTime(endTime);
changeFlowListQueryReq.setFlowId(flowId);
changeFlowListQueryReq.setOnlyMyFollowed(onlyMyFollowed);
// QC端/协同中心可以查看所有,不加入过滤条件
if (!CreateSourceEnum.QC.getType().equals(createSource)
&& !CreateSourceEnum.COLLABORATION_CENTER.getType().equals(createSource)) {
changeFlowListQueryReq.setCreateSource(createSource);
}
if (CreateSourceEnum.TONG_ZHOU.getType().equals(createSource)) {
if (CreateSourceEnum.QC.getType().equals(createSource)
|| CreateSourceEnum.COLLABORATION_CENTER.getType().equals(createSource)) {
// 不设置 createSource,可以查看所有
} else if (CreateSourceEnum.TONG_ZHOU.getType().equals(createSource)) {
Assert.isTrue(Strings.isNotBlank(supplier), "同舟端查询供应商id不可为空");
changeFlowListQueryReq.setCreateSource(createSource);
changeFlowListQueryReq.setSupplier(null);
changeFlowListQueryReq.setCreateSupplier(supplier);
} else {
changeFlowListQueryReq.setCreateSource(createSource);
}
return AjaxResult.success(changeFlowBiz.query(page, pageSize, changeFlowListQueryReq));
}
......@@ -137,7 +141,7 @@ public class ChangeFlowController {
}
/**
* 发起变更,如果发起人=审核人,自动跳转到第一个节点
* 发起变更,如果发起人=审核人,自动跳转到第一个节点(改)
*
* @return
*/
......@@ -147,7 +151,7 @@ public class ChangeFlowController {
}
/**
* 变更工单提交
* 变更工单提交(改)
*
* @return
*/
......@@ -178,6 +182,18 @@ public class ChangeFlowController {
}
/**
* 主工单催办
* 找出超期的行动工单,对具体的行动工单执行人进行提醒
*
* @return
*/
@PostMapping("/urge")
public AjaxResult<Void> urge(@RequestBody @Valid ChangeFlowUrgeReq req) {
changeFlowBiz.urge(req.getFlowId());
return AjaxResult.success();
}
/**
* 引用变更工单
*
* @return
......@@ -190,12 +206,13 @@ public class ChangeFlowController {
}
/**
* 导出变更
* 导出变更(改)
*
* @return
*/
@GetMapping("/export")
public AjaxResult<String> export(@RequestParam(name = "itemId", required = false) String itemId,
@RequestParam(name = "skuId", required = false) String skuId,
@RequestParam(name = "supplier", required = false) String supplier,
@RequestParam(name = "parentChangeClassId", required = false) Long parentChangeClassId,
@RequestParam(name = "sonChangeClassId", required = false) Long sonChangeClassId,
......@@ -203,13 +220,16 @@ public class ChangeFlowController {
@RequestParam(name = "changeCommander", required = false) String changeCommander,
@RequestParam(name = "changeDepartment", required = false) String changeDepartment,
@RequestParam(name = "changeExecDepartment", required = false) String changeExecDepartment,
@RequestParam(name = "changeExecUser", required = false) String changeExecUser,
@RequestParam(name = "startTime", required = false) Long startTime,
@RequestParam(name = "endTime", required = false) Long endTime,
@RequestParam(name = "createSource", required = false, defaultValue = "1") Integer createSource,
@RequestParam(name = "serviceCode") String serviceCode,
@RequestParam(name = "flowId", required = false) Long flowId) {
@RequestParam(name = "flowId", required = false) Long flowId,
@RequestParam(name = "onlyMyFollowed", defaultValue = "true") Boolean onlyMyFollowed) {
ChangeFlowRequest changeFlowRequest = new ChangeFlowRequest();
changeFlowRequest.setItemId(itemId);
changeFlowRequest.setSkuId(skuId);
changeFlowRequest.setSupplier(supplier);
changeFlowRequest.setParentChangeClassId(parentChangeClassId);
changeFlowRequest.setSonChangeClassId(sonChangeClassId);
......@@ -217,17 +237,23 @@ public class ChangeFlowController {
changeFlowRequest.setChangeCommander(changeCommander);
changeFlowRequest.setChangeDepartment(changeDepartment);
changeFlowRequest.setChangeExecDepartment(changeExecDepartment);
changeFlowRequest.setChangeExecUser(changeExecUser);
changeFlowRequest.setStartTime(startTime);
changeFlowRequest.setEndTime(endTime);
changeFlowRequest.setServiceCode(serviceCode);
changeFlowRequest.setFlowId(flowId);
changeFlowRequest.setOnlyMyFollowed(onlyMyFollowed);
// QC端/协同中心可以查看所有,不加入过滤条件
if (!CreateSourceEnum.QC.getType().equals(createSource)
&& !CreateSourceEnum.COLLABORATION_CENTER.getType().equals(createSource)) {
changeFlowRequest.setCreateSource(createSource);
}
if (CreateSourceEnum.TONG_ZHOU.getType().equals(createSource)) {
if (CreateSourceEnum.QC.getType().equals(createSource)
|| CreateSourceEnum.COLLABORATION_CENTER.getType().equals(createSource)) {
// 不设置 createSource,可以查看所有
} else if (CreateSourceEnum.TONG_ZHOU.getType().equals(createSource)) {
Assert.isTrue(Strings.isNotBlank(supplier), "同舟端查询供应商id不可为空");
changeFlowRequest.setCreateSource(createSource);
changeFlowRequest.setSupplier(null);
changeFlowRequest.setCreateSupplier(supplier);
} else {
changeFlowRequest.setCreateSource(createSource);
}
String s = this.exportAddressBook(changeFlowRequest);
return AjaxResult.success(s);
......@@ -235,6 +261,14 @@ public class ChangeFlowController {
public String exportAddressBook(ChangeFlowRequest req) {
req.setUid(RequestLocalBean.getUid());
// 数据可见范围:仅查看我跟进的工单(默认true)
if (req.getOnlyMyFollowed() != null && req.getOnlyMyFollowed()) {
String currentUser = RequestLocalBean.getUid();
if (Strings.isNotBlank(currentUser)) {
req.setCurrentUser(currentUser);
log.info("[export] 数据可见范围:仅查看我跟进的工单, currentUser:{}", currentUser);
}
}
return desExportService.changeFlowExport(req);
}
......@@ -272,4 +306,5 @@ public class ChangeFlowController {
public AjaxResult<List<CommanderResponse>> queryCommanderInfoV2(@RequestBody QueryCommanderEmailReq req) {
return AjaxResult.success(interiorChangeConfigService.queryCommanderEmail(req));
}
}
/**
* @(#)ChangeFlowController.java, 2022/11/14.
* <p/>
* Copyright 2022 Netease, Inc. All rights reserved.
* NETEASE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.netease.mail.yanxuan.change.web.controller;
import com.netease.mail.yanxuan.change.biz.biz.ChangeSubFlowBiz;
import com.netease.mail.yanxuan.change.common.bean.AjaxResult;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeSubFlowListQueryReq;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeSubFlowSubmitReq;
import com.netease.mail.yanxuan.change.dal.meta.model.vo.ChangeFlowVO;
import com.netease.mail.yanxuan.change.dal.meta.model.vo.ChangeSubFlowListVO;
import com.netease.yanxuan.flowx.sdk.meta.dto.exec.InterfaceInputDTO;
import com.netease.yanxuan.flowx.sdk.meta.dto.exec.UserBaseContainerDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* @Author zcwang
* @Date 2022/11/14
*/
@Slf4j
@RestController
@RequestMapping("/change/sub/flow")
public class ChangeSubFlowController {
@Autowired
private ChangeSubFlowBiz changeSubFlowBiz;
/**
* 变更行动工单列表查询,new
*
* @param page 页码
* @param pageSize 每页大小
* @param flowId 变更工单ID(主单flowId)
* @param subFlowId 变更行动工单ID
* @param itemId 商品ID
* @param supplier 供应商
* @param changeExecUser 变更行动人(姓名或邮箱)
* @param changeExecDepartment 变更行动部门
* @param status 行动工单状态
* @param startTime 开始时间(行动工单发起时间)
* @param endTime 结束时间(行动工单发起时间)
* @param onlyMyFollowed 数据可见范围:仅查看我跟进的工单(默认true)
* @return 变更行动工单列表
*/
@GetMapping("/page")
public AjaxResult<ChangeSubFlowListVO> page(@RequestParam(name = "page", defaultValue = "1") Integer page,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
@RequestParam(name = "flowId", required = false) Long flowId,
@RequestParam(name = "subFlowId", required = false) String subFlowId,
@RequestParam(name = "itemId", required = false) String itemId,
@RequestParam(name = "supplier", required = false) String supplier,
@RequestParam(name = "changeExecUser", required = false) String changeExecUser,
@RequestParam(name = "changeExecDepartment", required = false) String changeExecDepartment,
@RequestParam(name = "status", required = false) Integer status,
@RequestParam(name = "startTime", required = false) Long startTime,
@RequestParam(name = "endTime", required = false) Long endTime,
@RequestParam(name = "onlyMyFollowed", defaultValue = "true") Boolean onlyMyFollowed) {
ChangeSubFlowListQueryReq queryReq = new ChangeSubFlowListQueryReq();
queryReq.setFlowId(flowId);
queryReq.setSubFlowId(subFlowId);
queryReq.setItemId(itemId);
queryReq.setSupplier(supplier);
queryReq.setChangeExecUser(changeExecUser);
queryReq.setChangeExecDepartment(changeExecDepartment);
queryReq.setStatus(status);
queryReq.setStartTime(startTime);
queryReq.setEndTime(endTime);
queryReq.setOnlyMyFollowed(onlyMyFollowed);
return AjaxResult.success(changeSubFlowBiz.querySubFlowList(page, pageSize, queryReq));
}
/**
* 变更行动工单详情查询,new
* 说明:展示内容和主单的detail一样,但行动项只展示该变更行动工单下绑定的行动项
*
* @param subFlowId 变更行动工单ID(即topo)
* @return 变更工单详情
*/
@GetMapping("/detail")
public AjaxResult<ChangeFlowVO> detail(@RequestParam String subFlowId) {
return AjaxResult.success(changeSubFlowBiz.detailBySubFlowId(subFlowId));
}
/**
* 子流程提交,new
*
* @return
*/
@PostMapping("/submit")
public AjaxResult<String> submit(@RequestBody @Valid ChangeSubFlowSubmitReq changeSubFlowSubmitReq) {
return AjaxResult.success(changeSubFlowBiz.submitSubFlow(changeSubFlowSubmitReq));
}
/**
* 获取审批人
* @param interfaceInput 接口输入参数
* @return 审批人列表
*/
@PostMapping("/getOperator")
public AjaxResult<UserBaseContainerDTO> getOperator(@RequestBody InterfaceInputDTO interfaceInput) {
return AjaxResult.success(changeSubFlowBiz.getOperator(interfaceInput));
}
}
package com.netease.mail.yanxuan.change.web.controller;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
......@@ -13,10 +18,12 @@ import com.netease.mail.yanxuan.change.biz.config.AppConfig;
import com.netease.mail.yanxuan.change.biz.service.ISupplierItemService;
import com.netease.mail.yanxuan.change.common.bean.AjaxResult;
import com.netease.mail.yanxuan.change.common.bean.ResponseCodeEnum;
import com.netease.mail.yanxuan.change.integration.flow.supplier.FlowRpcSupplierInfoService;
import com.netease.mail.yanxuan.change.integration.flow.supplier.FlowRpcSupplierSendService;
import com.netease.mail.yanxuan.change.integration.flow.supplier.FlowRpcSupplierService;
import com.netease.mail.yanxuan.change.integration.flow.supplier.req.SupplierGoodsInfoReq;
import com.netease.mail.yanxuan.change.integration.flow.supplier.req.SupplierSearchRemoteReq;
import com.netease.mail.yanxuan.change.integration.flow.supplier.rsp.ItemSupplierRsp;
import com.netease.mail.yanxuan.change.integration.flow.supplier.rsp.SupplierSimpleRsp;
import com.netease.mail.yanxuan.change.integration.todo.SupplierItemPageVO;
import com.netease.yanxuan.flowx.sdk.meta.controller.communal.AjaxResponse;
......@@ -42,6 +49,9 @@ public class SupplierController {
private FlowRpcSupplierSendService supplierSendService;
@Autowired
private FlowRpcSupplierInfoService flowRpcSupplierInfoService;
@Autowired
private AppConfig appConfig;
@GetMapping("/query/supplier/infos")
......@@ -71,4 +81,57 @@ public class SupplierController {
itemId, itemName, page, pageSize, skuId));
}
/**
* 根据商品Id列表查询供应商(新)
*
* @param itemIds 商品Id列表(可通过多个同名参数传递,如:?itemId=1&itemId=2&itemId=3)
* @return 供应商信息列表(去重后)
*/
@GetMapping("/query/supplier/by/item/ids")
public AjaxResult<List<SupplierSimpleRsp>> querySupplierByItemIds(@RequestParam(name = "itemId") List<Long> itemIds) {
// 获取元数据
List<ItemSupplierRsp> itemSupplierList = flowRpcSupplierInfoService.querySupplierByItemIds(itemIds);
// 打印元数据日志
log.info("[querySupplierByItemIds] 元数据 itemIds={}, itemSupplierList={}",
JSON.toJSONString(itemIds), JSON.toJSONString(itemSupplierList));
String detail = supplierSendService.queryGoodsPurchase(itemIds);
log.info("[queryGoodsCommander] purchase:{}", detail);
if (CollectionUtils.isEmpty(itemSupplierList)) {
return AjaxResult.success(new ArrayList<>());
}
// 从响应中提取供应商信息,并去重(按supplierId去重)
Set<String> supplierIdSet = new HashSet<>();
List<SupplierSimpleRsp> supplierList = new ArrayList<>();
for (ItemSupplierRsp itemSupplier : itemSupplierList) {
String supplierId = itemSupplier.getSupplierId();
String supplierName = itemSupplier.getSupplierName();
// 如果供应商ID为空,跳过
if (StringUtils.isBlank(supplierId)) {
continue;
}
// 去重:如果该供应商ID已经处理过,跳过
if (supplierIdSet.contains(supplierId)) {
continue;
}
supplierIdSet.add(supplierId);
// 构建供应商信息
SupplierSimpleRsp supplier = new SupplierSimpleRsp();
supplier.setSupplierId(supplierId);
supplier.setSupplierName(StringUtils.isNotBlank(supplierName) ? supplierName : supplierId);
supplierList.add(supplier);
}
log.info("[querySupplierByItemIds] itemIds={}, supplierCount={}", JSON.toJSONString(itemIds), supplierList.size());
return AjaxResult.success(supplierList);
}
}
......@@ -8,7 +8,10 @@ package com.netease.mail.yanxuan.change.web.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import com.netease.mail.yanxuan.change.biz.biz.ChangeSubFlowBiz;
import com.netease.mail.yanxuan.change.biz.service.rpc.FlowService;
import com.netease.mail.yanxuan.change.dal.meta.model.req.ChangeSubFlowCreateReq;
import com.netease.yanxuan.flowx.sdk.meta.dto.base.FlowDataDTO;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
......@@ -16,7 +19,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.netease.mail.yanxuan.change.biz.biz.ChangeFlowBiz;
import com.netease.mail.yanxuan.change.biz.biz.DepartmentLeaderBiz;
import com.netease.mail.yanxuan.change.biz.service.change.ChangeConfigService;
import com.netease.mail.yanxuan.change.biz.service.rpc.QCService;
import com.netease.mail.yanxuan.change.common.bean.AjaxResult;
......@@ -27,8 +30,11 @@ import com.netease.mail.yanxuan.change.integration.flow.supplier.req.SupplierRel
import com.netease.mail.yanxuan.change.integration.flow.supplier.rsp.SupplierUserRsp;
import com.netease.mail.yanxuan.change.integration.qc.meta.QcCategoryVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import javax.validation.Valid;
/**
* @Author zcwang
* @Date 2022/11/28
......@@ -36,36 +42,21 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequestMapping("/test")
@RequiredArgsConstructor
public class TestController {
@Autowired
private ChangeFlowBiz changeFlowBiz;
@Autowired
private ChangeConfigService changeConfigService;
@Autowired
private QCService qcService;
// @PostMapping("/submit")
// public AjaxResult<Void> submit(Long id) {
// ChangeRecord record = changeFlowService.getById(id);
// Assert.notNull(record, "工单不存在");
// if (!record.getFlowNode().equals(ChangeFlowEnum.CHANGE_FLOW_EXE.getNodeId())) {
// throw ExceptionFactory.createBiz(ResponseCode.SUBMIT_FLOW_ERROR, "不是变更执行节点,不可运维提交");
// }
// autoSubmit.progressRecord(record);
// return AjaxResult.success();
// }
private final ChangeConfigService changeConfigService;
private final QCService qcService;
private final ChangeSubFlowBiz changeSubFlowBiz;
private final DepartmentLeaderBiz departmentLeaderBiz;
private final FlowService flowService;
private final FlowRpcSupplierInfoService supplierInfoService;
@GetMapping("/getUid")
public AjaxResult<String> getUid() {
return AjaxResult.success(RequestLocalBean.getUid());
}
@Autowired
private FlowRpcSupplierInfoService supplierInfoService;
@PostMapping("/test")
public List<SupplierUserRsp> test(@RequestBody SupplierRelatedUserReq req) {
return supplierInfoService.querySupplierRelatedUser(req);
......@@ -90,7 +81,7 @@ public class TestController {
@GetMapping("/ccList")
public AjaxResult<List<String>> ccList(@RequestParam List<String> receiver){
return AjaxResult.success(changeFlowBiz.getDepartmentInfo(receiver));
return AjaxResult.success(departmentLeaderBiz.getDepartmentLeaders(receiver));
}
@GetMapping("/getByOneAndTwo")
......@@ -102,4 +93,15 @@ public class TestController {
public AjaxResult<List<QcCategoryVO>> getQc(@RequestParam("itemId") Long itemId) {
return AjaxResult.success(qcService.getQcCategoryListByItemId(itemId));
}
/**
* 查询topo实际节点信息
*
* @param flowId 工单ID
* @return topo实际节点信息
*/
@GetMapping("/flowNodeInfo")
public AjaxResult<FlowDataDTO> getFlowNodeInfo(@RequestParam Long flowId) {
return AjaxResult.success(flowService.flowDetail(flowId.toString()));
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment