package kr.wisestone.owl.service.impl; import com.google.common.collect.Lists; import com.sun.org.apache.bcel.internal.generic.NEW; import kr.wisestone.owl.common.ExcelConditionCheck; import kr.wisestone.owl.config.CommonConfiguration; import kr.wisestone.owl.constant.Constants; import kr.wisestone.owl.constant.ElasticSearchConstants; import kr.wisestone.owl.constant.MsgConstants; import kr.wisestone.owl.constant.UsePartner; import kr.wisestone.owl.data.CheckIssueData; import kr.wisestone.owl.domain.*; import kr.wisestone.owl.domain.enumType.CustomFieldType; import kr.wisestone.owl.domain.enumType.EmailType; import kr.wisestone.owl.domain.enumType.IssueHistoryType; import kr.wisestone.owl.domain.enumType.IssueStatusType; import kr.wisestone.owl.exception.OwlRuntimeException; import kr.wisestone.owl.mapper.IssueMapper; import kr.wisestone.owl.mapper.ProjectMapper; import kr.wisestone.owl.repository.IssueRelationRepository; import kr.wisestone.owl.repository.IssueRepository; import kr.wisestone.owl.repository.UserDepartmentRepository; import kr.wisestone.owl.repository.WorkflowDepartmentRepository; import kr.wisestone.owl.service.*; import kr.wisestone.owl.util.*; import kr.wisestone.owl.util.DateUtil; import kr.wisestone.owl.vo.*; import kr.wisestone.owl.web.condition.*; import kr.wisestone.owl.web.form.*; import kr.wisestone.owl.web.view.ExcelView; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.StopWatch; import org.apache.poi.ss.usermodel.*; import org.jsoup.Jsoup; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.ui.Model; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.*; @Service public class IssueServiceImpl extends AbstractServiceImpl> implements IssueService { private static final Logger log = LoggerFactory.getLogger(IssueServiceImpl.class); @Autowired private IssueRepository issueRepository; @Autowired private ProjectService projectService; @Autowired private IssueStatusService issueStatusService; @Autowired private IssueTypeService issueTypeService; @Autowired private PriorityService priorityService; @Autowired private SeverityService severityService; @Autowired private CustomFieldApiOverlapService customFieldApiOverlapService; @Autowired private IssueApiDefaultService issueApiDefaultService; @Autowired private ApiTokenService apiTokenService; @Autowired private CommonConfiguration configuration; @Autowired private IssueNumberGeneratorService issueNumberGeneratorService; @Autowired private AttachedFileService attachedFileService; @Autowired private IssueCustomFieldValueService issueCustomFieldValueService; @Autowired private IssueCompanyService issueCompanyService; @Autowired private IssueIspService issueIspService; @Autowired private IssueHostingService issueHostingService; @Autowired private IssueUserService issueUserService; @Autowired private IssueDepartmentService issueDepartmentService; @Autowired private CustomFieldService customFieldService; @Autowired private IssueTypeCustomFieldService issueTypeCustomFieldService; @Autowired private UserService userService; @Autowired private DepartmentService departmentService; @Autowired private IssueCommentService issueCommentService; @Autowired private IssueHistoryService issueHistoryService; @Autowired private ProjectRoleUserService projectRoleUserService; @Autowired private ProjectRoleDepartmentService projectRoleDepartmentService; @Autowired private IssueRiskService issueRiskService; @Autowired private WorkspaceService workspaceService; @Autowired private SystemEmailService systemEmailService; @Autowired private IssueVersionService issueVersionService; @Autowired private IssueReservationService issueReservationService; @Autowired private UserWorkspaceService userWorkspaceService; @Autowired private WorkflowDepartmentService workflowDepartmentService; @Autowired private IssueRelationService issueRelationService; @Autowired private IssueRelationRepository issueRelationRepository; @Autowired private ExcelView excelView; @Autowired private IssueMapper issueMapper; @Autowired private ExcelConditionCheck excelConditionCheck; @Autowired private SimpMessagingTemplate simpMessagingTemplate; @Autowired private UserDepartmentRepository userDepartmentRepository; @Autowired private WorkflowDepartmentRepository workflowDepartmentRepository; @Override protected JpaRepository getRepository() { return this.issueRepository; } private static final int EXCEL_DOWNLOAD_MAX_ROWS = 10000; // excel download 제한 private static final int EXCEL_IMPORT_MAX_ROWS = 10000; // excel import 제한 @Override @Transactional public void addIssueVersion(Long id) { Issue issue = this.getIssue(id); // 이슈 버전 생성 this.issueVersionService.addIssueVersion(issue); } private IssueForm convertToIssueForm(IssueApiForm issueApiForm, User user) { if (issueApiForm.getIssueTypeId() == null) { throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_PARAMETER_ISSUE_TYPE_ERROR)); } IssueForm issueForm = ConvertUtil.copyProperties(issueApiForm, IssueForm.class); // issueForm.setFiles(issueApiForm.getFiles()); IssueType issueType = this.issueTypeService.getIssueType(issueApiForm.getIssueTypeId()); if (issueType == null){ throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_PARAMETER_ISSUE_TYPE_ERROR)); } Workflow workflow = issueType.getWorkflow(); // 이슈 상태가 지정되어 있지 않을 경우 초기값으로 지정 if (issueApiForm.getIssueStatusId() == null) { List issueStatusVos = issueStatusService.findByWorkflowId(workflow.getId()); IssueStatusVo issueStatusVo = issueStatusVos.get(0); issueApiForm.setIssueStatusId(issueStatusVo.getId()); } // 워크플로우 상태에 따른 담당부서 가져오기 if (issueApiForm.getIssueStatusId() != null) { WorkflowDepartmentCondition workflowDepartmentCondition = new WorkflowDepartmentCondition(); workflowDepartmentCondition.setIssueStatusId(issueApiForm.getIssueStatusId()); workflowDepartmentCondition.setWorkflowId(workflow.getId()); List workflowDepartmentVos = this.workflowDepartmentService.find(workflowDepartmentCondition); for (WorkflowDepartmentVo workflowDepartmentVo : workflowDepartmentVos) { issueForm.addDepartmentId(workflowDepartmentVo.getDepartmentVo().getId()); } } // 프로젝트 입력 Project project = issueType.getProject(); if (project == null){ throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_PARAMETER_PROJECT_ERROR)); } issueForm.setProjectId(project.getId()); if (user != null) { // 기본값 입력하기 IssueApiDefaultForm issueApiDefaultForm = new IssueApiDefaultForm(); issueApiDefaultForm.setUserId(user.getId()); issueApiDefaultForm.setIssueTypeId(issueForm.getIssueTypeId()); IssueApiDefault issueApiDefault = this.issueApiDefaultService.find(issueApiDefaultForm); if (issueApiDefault != null) { ConvertUtil.copyProperties(issueApiDefault, issueForm); issueForm.setId(null); issueForm.setPriorityId(issueApiDefault.getPriority().getId()); issueForm.setSeverityId(issueApiDefault.getSeverity().getId()); } // 중복 값 상위 이슈의 하위 이슈로 처리하기 CustomFieldApiOverlapForm customFieldApiOverlapForm = new CustomFieldApiOverlapForm(); customFieldApiOverlapForm.setUserId(user.getId()); customFieldApiOverlapForm.setIssueTypeId(issueForm.getIssueTypeId()); // 상위일감에 사용할 중복값 설정 List customFieldApiOverlaps = this.customFieldApiOverlapService.find(user.getId(), issueApiForm.getIssueTypeId()); for(int i=0; i < customFieldApiOverlaps.size() ; i++ ){ CustomFieldApiOverlap customFieldApiOverlap = customFieldApiOverlaps.get(i); issueApiForm.addUseIssueCustomFieldId(customFieldApiOverlap.getCustomField().getId()); } List issueVos = this.findIssue(issueApiForm, customFieldApiOverlaps, user.getId()); int size = issueVos.size(); if (size == 1) { issueForm.setParentIssueId(issueVos.get(0).getId()); } else if (size > 1) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.API_OVERLAP_ERROR)); } issueForm.setIsApi(Issue.IS_API_YES); // 사용자 정의 필드 설정 issueForm.setIssueCustomFields(issueApiForm.getCustomFieldValues()); // api 입력값 적용 ConvertUtil.copyProperties(issueApiForm, issueForm); return issueForm; } else { throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_USER_ERROR)); } } private User convertToUser(String token) { // 토큰으로 유저 정보 가져오기 UserVo userVo = this.apiTokenService.certification(token); // 해당 유저 정보가 현재 db에 있는지 확인 return this.userService.getUser(userVo.getId()); } // API 를 통해 이슈 추가. @Override @Transactional public List addApiIssue(IssueApiForm issueApiForm) throws CloneNotSupportedException { User user = convertToUser(issueApiForm.getToken()); IssueForm issueForm = this.convertToIssueForm(issueApiForm, user); List issues = Lists.newArrayList(); if (issueForm.getParentIssueId() != null) { issues.add(addIssue(user, issueForm, issueApiForm.getMultipartFiles())); } else { // 상위 이슈 추가 IssueForm parentIssueForm = issueForm.clone(); parentIssueForm.setUseIssueCustomFields(issueApiForm.getUseIssueCustomFieldIds()); Issue issue = addIssue(user, parentIssueForm, null); issues.add(issue); // 하위 이슈 추가 issueForm.setParentIssueId(issue.getId()); issues.add(addIssue(user, issueForm, issueApiForm.getMultipartFiles())); } return issues; } // 상위 이슈 가져오기 private IssueVo getParentIssueVo(Long parentIssueId) { if (parentIssueId != null) { Issue parentIssue = this.getIssue(parentIssueId); return ConvertUtil.copyProperties(parentIssue, IssueVo.class); } return null; } // 중복된 상위 이슈 검색 private List findIssue(IssueApiForm issueApiForm, List customFieldApiOverlaps, Long userId) { List resultIssueVos = new ArrayList<>(); if (customFieldApiOverlaps != null && customFieldApiOverlaps.size() > 0) { for (CustomFieldApiOverlap customFieldApiOverlap : customFieldApiOverlaps) { for (IssueCustomFieldValueForm issueCustomFieldValue : issueApiForm.getIssueCustomFieldValues()) { IssueCustomFieldValueCondition issueCustomFieldValueCondition = new IssueCustomFieldValueCondition(); issueCustomFieldValueCondition.setUseParentIssueId(true); if (customFieldApiOverlap.getCustomField().getId().equals(issueCustomFieldValue.getCustomFieldId())) { issueCustomFieldValueCondition.setUseValue(issueCustomFieldValue.getUseValue()); List> results = this.issueMapper.findByCustomFieldValue(issueCustomFieldValueCondition); if (results != null && results.size() > 0) { List findIssueVos = new ArrayList<>(); Collections.copy(resultIssueVos, findIssueVos); resultIssueVos.clear(); for (Map result : results) { IssueVo issueVo = ConvertUtil.convertMapToClass(result, IssueVo.class); if (findIssueVos.size() == 0) { resultIssueVos.add(issueVo); } else { IssueVo findIssueVo = findIssueVo(findIssueVos, issueVo.getId()); if (findIssueVo != null) { resultIssueVos.add(findIssueVo); } } } } else { resultIssueVos.clear(); return resultIssueVos; } } } } } return resultIssueVos; } // 이슈를 생성한다. @Override @Transactional public Issue addIssue(IssueForm issueForm, List multipartFiles) { User user = this.webAppUtil.getLoginUserObject(); return addIssue(user, issueForm, multipartFiles); } // 이슈를 생성한다. @Override @Transactional public Issue addIssue(User user, IssueForm issueForm, List multipartFiles) { // 사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다. Workspace workspace = this.workspaceService.checkUseWorkspace(user, user.getLastWorkspaceId()); // 프로젝트 유효성 체크 Project project = this.projectService.getProject(issueForm.getProjectId()); // 이슈 유형 유효성 체크 IssueType issueType = this.issueTypeService.getIssueType(issueForm.getIssueTypeId()); // 우선순위 유효성 체크 Priority priority = this.priorityService.getPriority(issueForm.getPriorityId()); // 중요도 유효성 체크 Severity severity = this.severityService.getSeverity(issueForm.getSeverityId()); // 제목 유효성 체크 this.verifyTitle(issueForm.getTitle()); // 날짜 유효성 체크 this.checkStartCompleteDate(issueForm.getStartDate(), issueForm.getCompleteDate()); // 담당 부서 유효성 체크 //this.verifyIssueDepartment(project, issueForm); // 이슈 상태 유형이 '대기' 인 이슈 상태 가져오기 IssueStatus issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(issueType.getWorkflow()); Issue issue = ConvertUtil.copyProperties(issueForm, Issue.class); issue.setProject(project); issue.setIssueStatus(issueStatus); issue.setIssueType(issueType); issue.setPriority(priority); issue.setSeverity(severity); if (issueForm.getParentIssueId() != null){ Issue parentIssue = this.getIssue(issueForm.getParentIssueId()); issue.setParentIssue(parentIssue); } issue.setIssueNumber(this.issueNumberGeneratorService.generateIssueNumber(project)); // 각 프로젝트의 고유 이슈 번호 생성 issue = this.issueRepository.saveAndFlush(issue); issue.setReverseIndex(issue.getId() * -1); // 쿼리 속도 개선을 위해 리버스 인덱스 생성 // 담당자 지정 //this.issueUserService.modifyIssueUser(issue, project.getWorkspace(), issueForm.getUserIds()); // 담당부서 지정 this.issueDepartmentService.modifyIssueDepartment(issue, user, project.getWorkspace(), issueForm.getDepartmentIds()); // 업체 정보 저장 this.issueCompanyService.modifyIssueCompanyField(issue, issueForm.getIssueCompanyFields()); // ISP 정보 저장 this.issueIspService.modifyIssueIspField(issue, issueForm.getIssueIspFields()); // HOSTING 정보 저장 this.issueHostingService.modifyIssueHostingField(issue, issueForm.getIssueHostingFields()); // 첨부 파일 저장 // multipartFile 을 file Map List 객체로 변경한다. List> convertFileMaps = this.convertMultipartFileToFile(multipartFiles); this.attachedFileService.addAttachedFile(convertFileMaps, issue, user.getAccount()); // 텍스트 에디터에 첨부한 파일을 이슈와 연결 this.checkNotHaveIssueIdAttachedFile(issue, issueForm); // 사용자 정의 필드 저장 this.issueCustomFieldValueService.modifyIssueCustomFieldValue(issue, issueForm.getIssueCustomFields()); // 이슈 이력 생성 this.issueHistoryService.addIssueHistory(issue, user, IssueHistoryType.ADD, null); // 이슈 위험 관리 생성 this.issueRiskService.addIssueRisk(issue, project.getWorkspace()); // 영속성 컨텍스트 비우기 this.clear(); // 이슈 생성, 삭제시 예약 이메일에 등록해놓는다. this.reservationIssueEmail(issue, EmailType.ISSUE_ADD); // 사용자 시스템 기능 사용 정보 수집 UserVo userVo = ConvertUtil.copyProperties(user, UserVo.class); log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(userVo, ElasticSearchConstants.ISSUE_ADD)); return issue; } // 연관이슈를 생성한다. @Override @Transactional public Issue addRelIssue(IssueForm issueForm, List multipartFiles) { User user = this.webAppUtil.getLoginUserObject(); return addRelIssue(user, issueForm, multipartFiles); } // 연관이슈를 생성한다. @Override @Transactional public Issue addRelIssue(User user, IssueForm issueForm, List multipartFiles) { // 사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다. Workspace workspace = this.workspaceService.checkUseWorkspace(user, user.getLastWorkspaceId()); // 프로젝트 유효성 체크 Project project = this.projectService.getProject(issueForm.getProjectId()); // 이슈 유형 유효성 체크 IssueType issueType = this.issueTypeService.getIssueType(issueForm.getIssueTypeId()); // 우선순위 유효성 체크 Priority priority = this.priorityService.getPriority(issueForm.getPriorityId()); // 중요도 유효성 체크 Severity severity = this.severityService.getSeverity(issueForm.getSeverityId()); // 제목 유효성 체크 this.verifyTitle(issueForm.getTitle()); // 날짜 유효성 체크 this.checkStartCompleteDate(issueForm.getStartDate(), issueForm.getCompleteDate()); // 담당 부서 유효성 체크 //this.verifyIssueDepartment(project, issueForm); // 이슈 상태 유형이 '대기' 인 이슈 상태 가져오기 IssueStatus issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(issueType.getWorkflow()); Issue issue = ConvertUtil.copyProperties(issueForm, Issue.class); issue.setProject(project); issue.setIssueStatus(issueStatus); issue.setIssueType(issueType); issue.setPriority(priority); issue.setSeverity(severity); if (issueForm.getParentIssueId() != null){ Issue parentIssue = this.getIssue(issueForm.getParentIssueId()); issue.setParentIssue(parentIssue); } issue.setIssueNumber(this.issueNumberGeneratorService.generateIssueNumber(project)); // 각 프로젝트의 고유 이슈 번호 생성 issue = this.issueRepository.saveAndFlush(issue); issue.setReverseIndex(issue.getId() * -1); // 쿼리 속도 개선을 위해 리버스 인덱스 생성 // 담당자 지정 //this.issueUserService.modifyIssueUser(issue, project.getWorkspace(), issueForm.getUserIds()); // 담당부서 지정 this.issueDepartmentService.modifyIssueDepartment(issue, user, project.getWorkspace(), issueForm.getDepartmentIds()); // 업체 정보 저장 this.issueCompanyService.modifyIssueCompanyField(issue, issueForm.getIssueCompanyFields()); // ISP 정보 저장 this.issueIspService.modifyIssueIspField(issue, issueForm.getIssueIspFields()); // HOSTING 정보 저장 this.issueHostingService.modifyIssueHostingField(issue, issueForm.getIssueHostingFields()); // 첨부 파일 저장 // multipartFile 을 file Map List 객체로 변경한다. List> convertFileMaps = this.convertMultipartFileToFile(multipartFiles); this.attachedFileService.addAttachedFile(convertFileMaps, issue, user.getAccount()); // 텍스트 에디터에 첨부한 파일을 이슈와 연결 this.checkNotHaveIssueIdAttachedFile(issue, issueForm); // 사용자 정의 필드 저장 this.issueCustomFieldValueService.modifyIssueCustomFieldValue(issue, issueForm.getIssueCustomFields()); // 이슈 이력 생성 this.issueHistoryService.addIssueHistory(issue, user, IssueHistoryType.ADD, null); // 이슈 위험 관리 생성 this.issueRiskService.addIssueRisk(issue, project.getWorkspace()); // 영속성 컨텍스트 비우기 this.clear(); // 이슈 생성, 삭제시 예약 이메일에 등록해놓는다. this.reservationIssueEmail(issue, EmailType.ISSUE_ADD); // 사용자 시스템 기능 사용 정보 수집 UserVo userVo = ConvertUtil.copyProperties(user, UserVo.class); log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(userVo, ElasticSearchConstants.ISSUE_ADD)); return issue; } // 이슈 생성, 삭제시 예약 이메일에 등록해놓는다. private void reservationIssueEmail(Issue issue, EmailType emailType) { Map issueMap = new HashMap<>(); // 이슈 정보를 이메일 전송에 사용하기 위해 Map 형태로 변환한다. this.makeIssueMapToIssue(issue, issueMap); Map projectRoleUserMap = new HashMap<>(); projectRoleUserMap.put("id", issue.getProject().getId()); projectRoleUserMap.put("statuses", Lists.newArrayList("02")); // 관리자 조회 // 관리자 정보 셋팅 List> projectRoleUsers = this.projectRoleUserService.findProjectRoleUser(projectRoleUserMap); if (projectRoleUsers != null && !projectRoleUsers.isEmpty()) { for (Map projectRoleUser : projectRoleUsers) { UserVo userVo = ConvertUtil.convertMapToClass(projectRoleUser, UserVo.class); // 이슈 생성 알림 메일 전송 this.systemEmailService.reservationEmail(new String[]{userVo.getAccount()}, emailType, issueMap); } } } // 이슈 정보를 이메일 전송에 사용하기 위해 Map 형태로 변환한다. private void makeIssueMapToIssue(Issue issue, Map issueMap) { issueMap.put("title", issue.getTitle()); issueMap.put("issueNumber", issue.getIssueNumber()); issueMap.put("issueTypeName", issue.getIssueType().getName()); issueMap.put("issueStatusName", issue.getIssueStatus().getName()); // 담당자 StringBuilder assigneeBuilder = new StringBuilder(); for (IssueUser issueUser : issue.getIssueUsers()) { assigneeBuilder.append(issueUser.getUser().getName()); assigneeBuilder.append("("); assigneeBuilder.append(CommonUtil.decryptAES128(issueUser.getUser().getAccount())); assigneeBuilder.append(")"); assigneeBuilder.append("\n"); } issueMap.put("assignees", assigneeBuilder.toString()); // 담당부서 StringBuilder departsBuilder = new StringBuilder(); for (IssueDepartment issueDepartment : issue.getIssueDepartments()) { departsBuilder.append(issueDepartment.getDepartment().getDepartmentName()); departsBuilder.append("\n"); } issueMap.put("departments", departsBuilder.toString()); // 기간 if (!StringUtils.isEmpty(issue.getStartDate())) { issueMap.put("period", issue.getStartDate() + " ~ " + issue.getCompleteDate()); } issueMap.put("severityName", issue.getSeverity().getName()); issueMap.put("priorityName", issue.getPriority().getName()); issueMap.put("projectName", issue.getProject().getName()); issueMap.put("projectKey", issue.getProject().getProjectKey()); User user = this.userService.getUser(issue.getRegisterId()); StringBuilder registerBuilder = new StringBuilder(); registerBuilder.append(user.getName()); registerBuilder.append("("); registerBuilder.append(CommonUtil.decryptAES128(user.getAccount())); registerBuilder.append(")"); issueMap.put("register", registerBuilder.toString()); Map customField = new HashMap<>(); List issueCustomFieldValueVos = this.issueCustomFieldValueService.findByIssueId(issue.getId()); for (IssueCustomFieldValueVo issueCustomFieldValueVo : issueCustomFieldValueVos) { // 이미 데이터가 존재 if (customField.get(issueCustomFieldValueVo.getCustomFieldVo().getName()) != null) { List useValues = (List) customField.get(issueCustomFieldValueVo.getCustomFieldVo().getName()); useValues.add(issueCustomFieldValueVo.getUseValue()); customField.put(issueCustomFieldValueVo.getCustomFieldVo().getName(), useValues); } else { if (issueCustomFieldValueVo.getCustomFieldVo().getCustomFieldType().equals(CustomFieldType.INPUT.toString())) { customField.put(issueCustomFieldValueVo.getCustomFieldVo().getName(), issueCustomFieldValueVo.getUseValue()); } else { customField.put(issueCustomFieldValueVo.getCustomFieldVo().getName(), Lists.newArrayList(issueCustomFieldValueVo.getUseValue())); } } } List> customFields = Lists.newArrayList(); Iterator iterator = customField.keySet().iterator(); while (iterator.hasNext()) { String key = iterator.next(); Map result = new HashMap<>(); result.put("name", key); result.put("useValue", customField.get(key)); customFields.add(result); } issueMap.put("customFields", customFields); issueMap.put("description", issue.getDescription()); //업체,ISP,HOSTING 추가 StringBuilder attachedFileBuilder = new StringBuilder(); List attachedFiles = this.attachedFileService.findByIssueId(issue.getId()); for (AttachedFile attachedFile : attachedFiles) { attachedFileBuilder.append(""); attachedFileBuilder.append(attachedFile.getName()); attachedFileBuilder.append(""); attachedFileBuilder.append("\n"); } issueMap.put("attachedFiles", attachedFileBuilder.toString()); } // 제목 유효성 체크 private void verifyTitle(String title) { if (StringUtils.isEmpty(title)) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.ISSUE_NO_TITLE)); } if (title.length() > 300) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.ISSUE_TITLE_MAX_LENGTH_OUT)); } } // 날짜 유효성 체크 private void checkStartCompleteDate(String startDate, String completeDate) { if (!StringUtils.isEmpty(startDate) && !StringUtils.isEmpty(completeDate)) { Date start = DateUtil.convertStrToDate(startDate, "yy-MM-dd"); Date end = DateUtil.convertStrToDate(completeDate, "yy-MM-dd"); if (start.getTime() > end.getTime()) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.DATE_PICKER_NOT_AVAILABLE)); } } } // 텍스트 에디터에 첨부한 파일을 이슈와 연결 private void checkNotHaveIssueIdAttachedFile(Issue issue, IssueForm issueForm) { if (!issueForm.getAttachedFileIds().isEmpty()) { this.attachedFileService.connectIssueIdAttachedFile(issue, issueForm); } } void SetMyDepartmentId(IssueCondition issueCondition){ Long loginId = issueCondition.getLoginUserId(); List myDepartmentIds = Lists.newArrayList(); List myDepartments = this.userDepartmentRepository.findByUserId(loginId); if(myDepartments != null && myDepartments.size() > 0){ for(UserDepartment myDepartment : myDepartments){ myDepartmentIds.add(myDepartment.getDepartmentId()); } } issueCondition.setMyDepartmentIds(myDepartmentIds); } void SetWorkflowDepartment(List issueVos){ for(IssueVo issueVo : issueVos){ Long issueTypeId = issueVo.getIssueTypeId(); IssueType issueType = this.issueTypeService.getIssueType(issueTypeId); Long workflowId = issueType.getWorkflow().getId(); List workflowDepartmentList = this.workflowDepartmentRepository.findByWorkflowId(workflowId); List workflowDepartmentIds = Lists.newArrayList(); if(workflowDepartmentList != null && workflowDepartmentList.size()>0){ for(WorkflowDepartment workflowDepartment : workflowDepartmentList){ workflowDepartmentIds.add(workflowDepartment.getDepartment().getId()); } } if(issueVo.getIssueTypeId().equals(issueTypeId)){ issueVo.setWorkflowDepartmentIds(workflowDepartmentIds); } } } // 이슈 목록을 조회한다. @Override @Transactional(readOnly = true) public List findIssue(Map resJsonData, IssueCondition issueCondition, Pageable pageable) { // 검색 조건을 만든다 if (!this.makeIssueSearchCondition(issueCondition, Lists.newArrayList("01", "02", "03"), pageable)) { // 이슈 목록을 찾지 못할 경우 기본 정보로 리턴한다. this.notFoundIssueList(resJsonData, pageable); return Lists.newArrayList(); } Set issueIds = new HashSet<>(); // 사용자 정의 필드 검색시 나오는 이슈 아이디 저장 컬렉션 // 사용자 정의 필드를 사용한 이슈를 찾는다. 만약 이슈가 없다면 여기서 이슈 조회가 끝난다. if (!this.searchUseCustomFields(issueCondition, issueIds, resJsonData, pageable)) { // 이슈 목록을 찾지 못할 경우 기본 정보로 리턴한다. this.notFoundIssueList(resJsonData, pageable); return Lists.newArrayList(); } // 튜닝 전 - 1.3 / 1.2 / 1.1 // 튜닝 후 (단일/다중 검색 조건 3개 기준) - 0.49 / 0.41 / 0.47 / 0.41 List issueVos = Lists.newArrayList(); // 이슈 목록 데이터 저장 컬렉션 // 사용자 정의 필드로 검색한 이슈 아이디 값 List issueKeys = Lists.newArrayList(issueIds); issueCondition.setIssueIds(issueKeys); issueCondition.setLoginUserId(this.webAppUtil.getLoginId()); issueCondition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId()); User user = this.webAppUtil.getLoginUserObject(); List> results = Lists.newArrayList(); Long totalCount = 0L; if (this.userWorkspaceService.checkWorkspaceManager(user)) { results = this.issueMapper.find(issueCondition); totalCount = this.issueMapper.count(issueCondition); } else{ this.SetMyDepartmentId(issueCondition); results = this.issueMapper.findByDepartment(issueCondition); totalCount = this.issueMapper.countByDepartment(issueCondition); } // 튜닝 전 - 0.8, 0.9, 0.9, 0.9, 0.9 /*StopWatch serviceStart = new StopWatch(); serviceStart.start();*/ // 튜닝 전 - 1.1, 1.1, 1.3, 1.2 /*serviceStart.stop(); log.debug("serviceENd1 : " + serviceStart.getTime());*/ int totalPage = (int) Math.ceil((totalCount - 1) / pageable.getPageSize()) + 1; // 이슈 아이디 초기화 issueCondition.setIssueIds(Lists.newArrayList()); // Map 에 있는 데이터를 IssueVo 데이터로 변환한다. this.setMapToIssueVo(results, issueVos, issueCondition, user); this.setCountDownIssues(results, issueVos); this.SetWorkflowDepartment(issueVos); //워크플로우에 설정한 담당부서 가져오기 resJsonData.put(Constants.RES_KEY_CONTENTS, issueVos); resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(), totalPage, totalCount)); // 사용자 시스템 기능 사용 정보 수집 log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_FIND)); return issueVos; } @Override @Transactional(readOnly = true) public void setCountDownIssues(List> results, List issueVos) { for (Map result : results){ List downIssues = this.issueRepository.findByParentIssueId((Long) result.get("id")); //하위이슈 가져오기 if(downIssues != null && downIssues.size() > 0){ //상위이슈 가지고 있는 애들이 있으면 int downIssueAllCount = 0;// 하위이슈 전체 카운트 int downIssueCount = 0;// 하위이슈 미완료 카운트 for(Issue downIssue : downIssues){ downIssueAllCount ++; Long parentIssueId = downIssue.getParentIssue().getId(); Issue parentIssue = this.getIssue(parentIssueId); IssueVo parentIssueVo = ConvertUtil.copyProperties(parentIssue, IssueVo.class); parentIssueVo.setDownIssueAllCount(downIssueAllCount); IssueStatus downIssueStatus = this.issueStatusService.getIssueStatus(downIssue.getIssueStatus().getId()); IssueVo downIssueVo = ConvertUtil.copyProperties(downIssue, IssueVo.class); downIssueVo.setIssueStatusType(downIssueStatus.getIssueStatusType().toString()); if(!downIssueVo.getIssueStatusType().equals("CLOSE")){ //미완료 하위이슈 체크 downIssueCount ++; } for(IssueVo issueVo : issueVos){ if(issueVo.getId().equals(parentIssueVo.getId())){ issueVo.setDownIssueCount(downIssueCount); issueVo.setDownIssueAllCount(parentIssueVo.getDownIssueAllCount()); } } } } } } // 이슈 목록을 조회한다(차트용 - 연관일감포함) @Override @Transactional(readOnly = true) public void findApiIssue(ApiMonitorCondition apiMonitorCondition, Map resJsonData) { IssueTypeCondition issueTypeCondition = new IssueTypeCondition(); List issueTypes = this.issueTypeService.findIssueType(issueTypeCondition); // 값이 없을 경우 초기값 입력 if (StringUtils.isEmpty(apiMonitorCondition.getSearchPeriod())) { apiMonitorCondition.setSearchPeriod(DateUtil.LAST_SEVEN_DAYS); } // 검색 일자를 구한다. List searchDates = CommonUtil.findSearchPeriod(apiMonitorCondition.getSearchPeriod()); // 날짜가 검색되지 않았으면 오류 if (searchDates.size() < 1) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.WIDGET_SEARCH_DATE_NOT_FOUND)); } Long index = 1L; List apiMonitorVos = Lists.newArrayList(); for (Date date : searchDates) { String onlyDate = DateUtil.convertDateToYYYYMMDD(date); issueTypeCondition.setStartDate(onlyDate + " 00:00:00"); issueTypeCondition.setEndDate(onlyDate + " 23:59:59"); ApiMonitorVo apiMonitorVo = new ApiMonitorVo(); apiMonitorVo.setId(index); for (IssueTypeVo issueTypeVo : issueTypes) { issueTypeCondition.setId(issueTypeVo.getId()); apiMonitorVo.addIssueTypeCount(this.issueMapper.countByIssueTypeIdAndDate(issueTypeCondition)); apiMonitorVo.setIsApi(Issue.IS_API_YES); apiMonitorVos.add(apiMonitorVo); } index++; } Map data = new HashMap<>(); data.put("issueTypeVos", issueTypes); data.put("apiMonitorVos", apiMonitorVos); resJsonData.put(Constants.RES_KEY_CONTENTS, data); } // 이슈 목록을 조회한다(차트용 - 연관일감포함) @Override @Transactional(readOnly = true) public List findChartIssue(Map resJsonData, IssueCondition issueCondition, Pageable pageable) { // 검색 조건을 만든다 if (!this.makeIssueSearchCondition(issueCondition,Lists.newArrayList("01", "02", "03"), pageable)) { // 이슈 목록을 찾지 못할 경우 기본 정보로 리턴한다. this.notFoundIssueList(resJsonData, pageable); return Lists.newArrayList(); } Set issueIds = new HashSet<>(); // 사용자 정의 필드 검색시 나오는 이슈 아이디 저장 컬렉션 // 사용자 정의 필드를 사용한 이슈를 찾는다. 만약 이슈가 없다면 여기서 이슈 조회가 끝난다. if (!this.searchUseCustomFields(issueCondition, issueIds, resJsonData, pageable)) { // 이슈 목록을 찾지 못할 경우 기본 정보로 리턴한다. this.notFoundIssueList(resJsonData, pageable); return Lists.newArrayList(); } // 튜닝 전 - 1.3 / 1.2 / 1.1 // 튜닝 후 (단일/다중 검색 조건 3개 기준) - 0.49 / 0.41 / 0.47 / 0.41 List issueVos = Lists.newArrayList(); // 이슈 목록 데이터 저장 컬렉션 // 사용자 정의 필드로 검색한 이슈 아이디 값 List issueKeys = Lists.newArrayList(issueIds); issueCondition.setIssueIds(issueKeys); List> results = this.issueMapper.find(issueCondition); int totalCount = results.size(); int totalPage = (int) Math.ceil((totalCount - 1) / pageable.getPageSize()) + 1; // 이슈 아이디 초기화 issueCondition.setIssueIds(Lists.newArrayList()); // Map 에 있는 데이터를 IssueVo 데이터로 변환한다. this.setMapToIssueVoForChart(results, issueVos, issueCondition); resJsonData.put(Constants.RES_KEY_CONTENTS, issueVos); resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(), totalPage, totalCount)); // 사용자 시스템 기능 사용 정보 수집 log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_FIND)); return issueVos; } // 이슈 목록을 조회한다(차트용 - 연관일감) @Override @Transactional(readOnly = true) public List findChartIssue(Map resJsonData, ProjectCondition projectCondition, Pageable pageable) { IssueCondition issueCondition = new IssueCondition(); // 검색 조건을 만든다 User user = this.webAppUtil.getLoginUserObject(); if (!this.makeIssueSearchCondition(user, issueCondition, projectCondition, pageable)) { // 이슈 목록을 찾지 못할 경우 기본 정보로 리턴한다. this.notFoundIssueList(resJsonData, pageable); return Lists.newArrayList(); } Set issueIds = new HashSet<>(); // 사용자 정의 필드 검색시 나오는 이슈 아이디 저장 컬렉션 List issueVos = Lists.newArrayList(); // 이슈 목록 데이터 저장 컬렉션 // 사용자 정의 필드로 검색한 이슈 아이디 값 List issueKeys = Lists.newArrayList(issueIds); issueCondition.setIssueIds(issueKeys); List> results = this.issueMapper.find(issueCondition); int totalCount = results.size(); int totalPage = (int) Math.ceil((totalCount - 1) / pageable.getPageSize()) + 1; // 이슈 아이디 초기화 issueCondition.setIssueIds(Lists.newArrayList()); // Map 에 있는 데이터를 IssueVo 데이터로 변환한다. this.setMapToIssueVoForChart(results, issueVos, issueCondition); resJsonData.put(Constants.RES_KEY_CONTENTS, issueVos); resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(), totalPage, totalCount)); // 사용자 시스템 기능 사용 정보 수집 log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_FIND)); return issueVos; } // Map 에 있는 데이터를 IssueVo 데이터로 변환한다. 차트용 private void setMapToIssueVoForChart(List> results, List issueVos, IssueCondition issueCondition) { for (Map result : results) { IssueVo issueVo = ConvertUtil.convertMapToClass(result, IssueVo.class); issueVos.add(issueVo); issueCondition.addIssueIds(String.valueOf(issueVo.getId())); } for (IssueVo issueVo : issueVos) { this.setRelationIssue(issueVo, issueVo.getId()); } } // Map 에 있는 데이터를 IssueVo 데이터로 변환한다. private void setMapToIssueVo(List> results, List issueVos, IssueCondition issueCondition, User user) { for (Map result : results) { IssueVo issueVo = ConvertUtil.convertMapToClass(result, IssueVo.class); issueVos.add(issueVo); issueCondition.addIssueIds(String.valueOf(issueVo.getId())); } // 이슈 사용자 정보 추가 //this.setIssueUserList(issueVos, issueCondition); this.setIssueDepartmentList(issueVos, issueCondition, user); // 등록자 정보 추가 this.setRegister(issueVos); // 담당자 정보 셋팅 // 사용자 정의 필드 정보 추가 this.setIssueCustomFieldValue(issueVos, issueCondition); } // 검색 조건을 만든다 private boolean makeIssueSearchCondition(IssueCondition condition, List projectStatues, Pageable pageable) { if (pageable != null) { condition.setPage(pageable.getPageNumber() * pageable.getPageSize()); condition.setPageSize(pageable.getPageSize()); } condition.setLoginUserId(this.webAppUtil.getLoginId()); condition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId()); // 프로젝트 키가 존재할 경우 프로젝트 키에 해당하는 프로젝트를 조회하고 검색 조건에 셋팅한다. if (!this.getProjectByProjectKey(condition.getProjectKey(), condition)) { return false; } // 프로젝트를 선택하지 않았으면 해당 업무 공간에서 참여하고 있는 프로젝트를 찾는다. if (condition.getProjectIds().size() < 1) { List> projects = this.projectService.findByWorkspaceIdAndIncludeProjectAll(projectStatues, condition.getProjectType()); List projectIds = Lists.newArrayList(); for (Map result : projects) { Long projectId = MapUtil.getLong(result, "id"); if (projectId != null) { projectIds.add(projectId); } } condition.setProjectIds(projectIds); if (projectIds.size() < 1) { return false; } } return true; } // 검색 조건을 만든다 private boolean makeIssueSearchCondition(User user, IssueCondition condition, ProjectCondition projectCondition, Pageable pageable) { if (pageable != null) { condition.setPage(pageable.getPageNumber() * pageable.getPageSize()); condition.setPageSize(pageable.getPageSize()); } condition.setLoginUserId(this.webAppUtil.getLoginId()); condition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId()); projectCondition.setWorkspaceId(condition.getWorkspaceId()); // 프로젝트 키가 존재할 경우 프로젝트 키에 해당하는 프로젝트를 조회하고 검색 조건에 셋팅한다. if (!this.getProjectByProjectKey(condition.getProjectKey(), condition)) { return false; } // 프로젝트를 선택하지 않았으면 해당 업무 공간에서 참여하고 있는 프로젝트를 찾는다. if (condition.getProjectIds().size() < 1) { List> projects = null; if (this.userWorkspaceService.checkWorkspaceManager(user)) { projects = this.projectMapper.findByWorkspaceManagerAll(projectCondition); } else { projects = this.projectService.findByWorkspaceIdAndIncludeProjectAll(projectCondition); } List projectIds = Lists.newArrayList(); for (Map result : projects) { Long projectId = MapUtil.getLong(result, "id"); if (projectId != null) { projectIds.add(projectId); } } condition.setProjectIds(projectIds); if (projectIds.size() < 1) { return false; } } return true; } // 프로젝트 키가 존재할 경우 프로젝트 키에 해당하는 프로젝트를 조회하고 검색 조건에 셋팅한다. private boolean getProjectByProjectKey(String projectKey, IssueCondition condition) { if (!StringUtils.isEmpty(projectKey)) { Project project = this.projectService.findByProjectKey(projectKey); if (project != null) { // 이미 프로젝트를 선택했을 경우에 프로젝트키로 검색한 프로젝트가 포함되어 있지 않으면 false if (condition.getProjectIds().size() > 0) { if (condition.getProjectIds().contains(project.getId())) { condition.setProjectIds(Lists.newArrayList()); } else { return false; } } condition.addProjectIds(project.getId()); } else { return false; } } return true; } // 이슈 목록을 찾지 못할 경우 기본 정보로 리턴한다. private void notFoundIssueList(Map resJsonData, Pageable pageable) { resJsonData.put(Constants.RES_KEY_CONTENTS, Lists.newArrayList()); resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(), 0, 0)); } // 사용자 정의 필드를 사용한 이슈를 찾는다. 만약 이슈가 없다면 여기서 이슈 조회가 끝난다. private boolean searchUseCustomFields(IssueCondition condition, Set issueIds, Map resJsonData, Pageable pageable) { // 사용자 정의 필드 값이 검색 옵션으로 있었을 때 조회된 이슈가 없으면 조회를 더 이상 할 필요가 없다. // 사용자 정의 필드를 사용한 이슈를 찾는다. boolean customFieldSearch = this.issueCustomFieldValueService.find(condition, issueIds); // 사용자 정의 필드 값이 존재하여 검색을 했을 때 검색된 이슈가 없으면 여기서 종료한다. if (customFieldSearch && issueIds.size() < 1) { resJsonData.put(Constants.RES_KEY_CONTENTS, Lists.newArrayList()); if (pageable != null) { resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(), 0, 0)); } return false; } return true; } // 이슈 담당자 정보를 셋팅한다. private void setIssueUserList(List issueVos, IssueCondition issueCondition, User user) { if (issueVos.size() < 1) { return; } List> issueUsers = this.issueMapper.findIssueUser(issueCondition); Map issueConverterUsers = new HashMap<>(); // 이슈에 해당하는 이슈 담당자 정보 셋팅 for (Map issueUser : issueUsers) { String issueId = MapUtil.getString(issueUser, "issueId"); if (MapUtil.getObject(issueConverterUsers, issueId) != null) { List users = (List) MapUtil.getObject(issueConverterUsers, issueId); users.add(new UserVo(MapUtil.getLong(issueUser, "id"), MapUtil.getString(issueUser, "name"), CommonUtil.decryptAES128(MapUtil.getString(issueUser, "account")), MapUtil.getString(issueUser, "profile"))); } else { List users = Lists.newArrayList(new UserVo(MapUtil.getLong(issueUser, "id"), MapUtil.getString(issueUser, "name"), CommonUtil.decryptAES128(MapUtil.getString(issueUser, "account")), MapUtil.getString(issueUser, "profile"))); issueConverterUsers.put(issueId, users); } } // 이슈Vo에 담당자 정보를 셋팅 for (IssueVo issueVo : issueVos) { if (MapUtil.getObject(issueConverterUsers, String.valueOf(issueVo.getId())) != null) { List userVos = (List) MapUtil.getObject(issueConverterUsers, String.valueOf(issueVo.getId())); issueVo.setUserVos(userVos); } // 이슈 수정 권한을 갖고 있는지 확인 if (this.checkHasPermission(issueVo, issueVo.getUserVos(), user)) { issueVo.setModifyPermissionCheck(Boolean.TRUE); } } } // 이슈 담당부서 정보를 셋팅한다. private void setIssueDepartmentList(List issueVos, IssueCondition issueCondition, User user) { if (issueVos.size() < 1) { return; } List> issueDepartments = this.issueMapper.findIssueDepartment(issueCondition); Map issueConverterDepartments = new HashMap<>(); // 이슈에 해당하는 이슈 담당부서 정보 셋팅 for (Map issueDepartment : issueDepartments) { String issueId = MapUtil.getString(issueDepartment, "issueId"); if (MapUtil.getObject(issueConverterDepartments, issueId) != null) { List departments = (List) MapUtil.getObject(issueConverterDepartments, issueId); departments.add(new DepartmentVo(MapUtil.getLong(issueDepartment, "id"), MapUtil.getString(issueDepartment, "departmentName"), MapUtil.getString(issueDepartment, "departmentDescription"))); } else { List departments = Lists.newArrayList(new DepartmentVo(MapUtil.getLong(issueDepartment, "id"), MapUtil.getString(issueDepartment, "departmentName"), MapUtil.getString(issueDepartment, "departmentDescription"))); issueConverterDepartments.put(issueId, departments); } } // 이슈Vo에 담당부서 정보를 셋팅 for (IssueVo issueVo : issueVos) { if (MapUtil.getObject(issueConverterDepartments, String.valueOf(issueVo.getId())) != null) { List departmentVos = (List) MapUtil.getObject(issueConverterDepartments, String.valueOf(issueVo.getId())); issueVo.setDepartmentVos(departmentVos); } // 이슈 수정 권한을 갖고 있는지 확인 if (this.checkHasPermission(issueVo, issueVo.getUserVos(), user)) { issueVo.setModifyPermissionCheck(Boolean.TRUE); } } } // 이슈 상세 정보를 조회한다. @Override @Transactional(readOnly = true) public void detailIssue(Map resJsonData, IssueCondition issueCondition) { IssueVo issueVo = new IssueVo(); if (issueCondition.getId() != null) { Issue issue = this.getIssue(issueCondition.getId()); issueVo = ConvertUtil.copyProperties(issue, IssueVo.class); switch (issueCondition.getDeep()) { case "01": // 프로젝트, 이슈 유형, 이슈 상태, 우선순위, 중요도, 담당부서, 첨부파일, 사용자 정의 필드 정보를 셋팅한다. issueVo.setProjectVo(ConvertUtil.copyProperties(issue.getProject(), ProjectVo.class)); issueVo.setIssueTypeVo(ConvertUtil.copyProperties(issue.getIssueType(), IssueTypeVo.class)); issueVo.setIssueStatusVo(ConvertUtil.copyProperties(issue.getIssueStatus(), IssueStatusVo.class)); issueVo.setPriorityVo(ConvertUtil.copyProperties(issue.getPriority(), PriorityVo.class)); issueVo.setSeverityVo(ConvertUtil.copyProperties(issue.getSeverity(), SeverityVo.class)); this.setRegister(issue, issueVo); // 등록자 정보 셋팅 //this.setIssueUser(issue, issueVo); // 담당자 정보 셋팅 this.setIssueDepartment(issue, issueVo); // 담당부서 정보 셋팅 this.setAttachedFiles(issue, issueVo); // 첨부 파일 정보 셋팅 this.setIssueCustomFields(issue, issueVo); // 사용자 정의 필드 값 정보 셋팅 this.setRelationIssue(issue, issueVo); //연관 일감 셋팅 this.setDownIssues(issue, issueVo); //하위 이슈 세팅 break; case "02": // 프로젝트, 이슈 유형, 이슈 상태, 우선순위, 중요도, 담당자, 첨부파일, 사용자 정의 필드 정보, 댓글, 기록을 셋팅한다. this.setIssueDetail(issueVo, issue); // 이슈 상세 정보를 셋팅한다. issueVo.setProjectVo(ConvertUtil.copyProperties(issue.getProject(), ProjectVo.class)); break; } } // 사용자 시스템 기능 사용 정보 수집 log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_DETAIL)); resJsonData.put(Constants.RES_KEY_CONTENTS, issueVo); } // 하위 이슈 정보를 셋팅한다 private void setDownIssues(Issue issue, IssueVo issueVo) { List downIssues = this.issueRepository.findByParentIssueId(issue.getId()); if(downIssues != null && downIssues.size()>0){ List resultList = new ArrayList<>(); for(Issue downIssue : downIssues){ IssueVo downIssueVo = ConvertUtil.copyProperties(downIssue, IssueVo.class); downIssueVo.setIssueTypeVo(ConvertUtil.copyProperties(downIssue.getIssueType(), IssueTypeVo.class)); downIssueVo.setPriorityVo(ConvertUtil.copyProperties(downIssue.getPriority(), PriorityVo.class)); downIssueVo.setSeverityVo(ConvertUtil.copyProperties(downIssue.getSeverity(), SeverityVo.class)); //이슈 상태 추가 IssueStatusVo issueStatusVo = ConvertUtil.copyProperties(downIssue.getIssueStatus(), IssueStatusVo.class, "issueStatusType"); issueStatusVo.setIssueStatusType(downIssue.getIssueStatus().getIssueStatusType().toString()); downIssueVo.setIssueStatusVo(issueStatusVo); this.setRegister(downIssue, downIssueVo); // 등록자 this.setIssueDepartment(downIssue, downIssueVo); // 담당부서 정보 셋팅 this.setIssueCustomFields(downIssue, downIssueVo); // 사용자정의필드 정보 세팅 resultList.add(downIssueVo); } issueVo.setIssueDownVos(resultList); } } // 이슈 상세 정보를 셋팅한다. @Override @Transactional(readOnly = true) public void setIssueDetail(IssueVo issueVo, Issue issue) { issueVo.setProjectVo(ConvertUtil.copyProperties(issue.getProject(), ProjectVo.class)); issueVo.setIssueTypeVo(ConvertUtil.copyProperties(issue.getIssueType(), IssueTypeVo.class)); IssueStatusVo issueStatusVo = ConvertUtil.copyProperties(issue.getIssueStatus(), IssueStatusVo.class, "issueStatusType"); issueStatusVo.setIssueStatusType(issue.getIssueStatus().getIssueStatusType().toString()); issueVo.setIssueStatusVo(issueStatusVo); issueVo.setPriorityVo(ConvertUtil.copyProperties(issue.getPriority(), PriorityVo.class)); issueVo.setSeverityVo(ConvertUtil.copyProperties(issue.getSeverity(), SeverityVo.class)); this.setRegister(issue, issueVo); // 등록자 정보 셋팅 //this.setIssueUser(issue, issueVo); // 담당자 정보 셋팅 this.setIssueDepartment(issue, issueVo); // 담당부서 정보 셋팅 this.setAttachedFiles(issue, issueVo); // 첨부 파일 정보 셋팅 this.setIssueCustomFields(issue, issueVo); // 사용자 정의 필드 값 정보 셋팅 this.setIssueComments(issue, issueVo); // 댓글 정보 셋팅 this.setIssueHistory(issue, issueVo); // 이슈 기록 정보 셋팅 this.setRelationIssue(issue, issueVo); //연관 일감 셋팅 this.setDownIssues(issue, issueVo); //하위 일감 세팅 IssueType issueType = this.issueTypeService.getIssueType(issueVo.getIssueTypeVo().getId()); // 이슈의 이슈유형 객체 Integer using = issueType.getUsePartner() != null ? issueType.getUsePartner().intValue() : 0; // 이슈유형별로 사용중인 업체/ISP/호스팅 값 List usePartnerVos = Lists.newArrayList(); for (Integer usePartner : UsePartner.partners) { //1(업체), 2(ISP), 4(호스팅) UsePartnerVo usePartnerVo = UsePartner.checkUsePartner(using, usePartner); if (usePartnerVo != null) { usePartnerVos.add(usePartnerVo); //useCompanyVo.setValues(); } issueVo.setUsePartnerVos(usePartnerVos); } this.setIssueCompanyField(issue, issueVo); //업체 정보 세팅 this.setIssueIspField(issue, issueVo); //ISP 정보 세팅 this.setIssueHostingField(issue, issueVo); //HOSTING 정보 세팅 this.setParentIssue(issue,issueVo); //상위 이슈 정보 세팅 } // 상위일감 정보 추가 private void setParentIssue(Issue issue, IssueVo issueVo) { if(issue.getParentIssue() != null){ issueVo.setParentIssueVo(ConvertUtil.copyProperties(issue.getParentIssue(), IssueVo.class)); } } // 등록자 정보 추가 private void setRegister(List issueVos) { for (IssueVo issueVo : issueVos) { // 사용자 패스워드를 삭제하고 사용자 계정을 복호화한다. issueVo.setRegisterVo(this.userService.removeSensitiveUser(issueVo.getRegisterId())); } } // 이슈 등록자 정보를 셋팅한다. private void setRegister(Issue issue, IssueVo issueVo) { UserVo userVo = this.userService.removeSensitiveUser(issue.getRegisterId()); issueVo.setRegisterVo(userVo); // 등록자는 항상 수정 가능. if (userVo.getId().equals(this.webAppUtil.getLoginId())) { issueVo.setModifyPermissionCheck(Boolean.TRUE); } } // 연관 이슈 정보를 셋팅한다 private void setRelationIssue(Issue issue, IssueVo issueVo) { Set issueRelations = issue.getIssueRelations(); if (issue != null && issueVo != null && issueRelations.size() > 0) { for (IssueRelation issueRelation : issueRelations) { IssueRelationVo issueRelationVo = ConvertUtil.copyProperties(issueRelation, IssueRelationVo.class); Issue relationIssue = issueRelation.getRelationIssue(); IssueVo relIssueVo = ConvertUtil.copyProperties(relationIssue, IssueVo.class); Project project = this.projectService.getProject(relationIssue.getProject().getId()); relIssueVo.setProjectId(project.getId()); relIssueVo.setProjectKey(project.getProjectKey()); relIssueVo.setIssueNumber(relationIssue.getIssueNumber()); issueRelationVo.setIssueRelation(relIssueVo); issueRelationVo.setTitle(relationIssue.getTitle()); issueRelationVo.setIssueTypeVo(ConvertUtil.copyProperties(relationIssue.getIssueType(), IssueTypeVo.class)); issueRelationVo.setPriorityVo(ConvertUtil.copyProperties(relationIssue.getPriority(), PriorityVo.class)); issueRelationVo.setSeverityVo(ConvertUtil.copyProperties(relationIssue.getSeverity(), SeverityVo.class)); //이슈 상태 추가 IssueStatusVo issueStatusVo = ConvertUtil.copyProperties(relationIssue.getIssueStatus(), IssueStatusVo.class, "issueStatusType"); issueStatusVo.setIssueStatusType(relationIssue.getIssueStatus().getIssueStatusType().toString()); issueRelationVo.setIssueStatusVo(issueStatusVo); this.setRegister(relationIssue, relIssueVo); // 등록자 this.setIssueDepartment(relationIssue, relIssueVo); // 담당부서 정보 셋팅 this.setIssueCustomFields(relationIssue, relIssueVo); // 사용자정의필드 정보 세팅 issueVo.addIssueRelationVo(issueRelationVo); } } else { issue.clearIssueRelations(); } } // 이슈 담당자 정보를 셋팅한다. /*private void setIssueUser(Issue issue, IssueVo issueVo) { List userVos = Lists.newArrayList(); for (IssueUser issueUser : issue.getIssueUsers()) { UserVo userVo = ConvertUtil.copyProperties(issueUser.getUser(), UserVo.class, "password"); userVo.setByName(userVo.getName() + "(" + CommonUtil.decryptAES128(userVo.getAccount()) + ")"); userVo.setAccount(CommonUtil.decryptAES128(userVo.getAccount())); userVos.add(userVo); // 담당자가 있을 경우 담당자만 수정 가능. if (userVo.getId().equals(this.webAppUtil.getLoginId())) { issueVo.setModifyPermissionCheck(Boolean.TRUE); } } // 스케쥴러에서 실행될 경우 오류 발생하므로 권한 체크하지 않는다. if (this.webAppUtil.getLoginId() != null) { // 업무 공간 관리자일 경우 수정 권한을 갖는다. if (this.userWorkspaceService.checkWorkspaceManager()) { issueVo.setModifyPermissionCheck(Boolean.TRUE); } // 프로젝트 관리자일 경우 해당 프로젝트에 등록된 이슈는 수정 권한을 갖는다. if (this.projectRoleUserService.checkProjectManager(issue.getProject())) { issueVo.setModifyPermissionCheck(Boolean.TRUE); } } // 담당자가 없으면 모든 사용자가 수정 가능. if (issue.getIssueUsers().size() < 1) { issueVo.setModifyPermissionCheck(Boolean.TRUE); } issueVo.setUserVos(userVos); }*/ // 이슈 담당부서 정보를 셋팅한다. private void setIssueDepartment(Issue issue, IssueVo issueVo) { List departmentVos = Lists.newArrayList(); for (IssueDepartment issueDepartment : issue.getIssueDepartments()) { DepartmentVo departmentVo = ConvertUtil.copyProperties(issueDepartment.getDepartment(), DepartmentVo.class); departmentVo.setByName(departmentVo.getDepartmentName()); departmentVos.add(departmentVo); } issueVo.setDepartmentVos(departmentVos); } // 이슈 첨부파일 정보를 셋팅한다. private void setAttachedFiles(Issue issue, IssueVo issueVo) { List attachedFileVos = Lists.newArrayList(); for (AttachedFile attachedFile : issue.getAttachedFiles()) { AttachedFileVo attachedFileVo = ConvertUtil.copyProperties(attachedFile, AttachedFileVo.class, "fileType"); attachedFileVo.setFileType(attachedFile.getFileType().toString()); attachedFileVos.add(attachedFileVo); } issueVo.setAttachedFileVos(attachedFileVos); } // 이슈(이슈 유형)에 연결된 사용자 정의 필드 정보를 셋팅한다. private IssueVo setIssueCustomFields(Issue issue, IssueVo issueVo) { // 해당 프로젝트의 이슈 유형에 연결된 사용자 정의 필드 정보를 가져온다. IssueTypeCustomFieldCondition issueTypeCustomFieldCondition = new IssueTypeCustomFieldCondition(); issueTypeCustomFieldCondition.setProjectId(issue.getProject().getId()); issueTypeCustomFieldCondition.setIssueTypeId(issue.getIssueType().getId()); List issueTypeCustomFieldVos = this.issueTypeCustomFieldService.findIssueTypeCustomField(new HashMap<>(), issueTypeCustomFieldCondition); issueVo.setIssueTypeCustomFieldVos(issueTypeCustomFieldVos); // 이슈에서 사용된 사용자 정의 필드 값을 가져온다. List issueCustomFieldValueVos = this.issueCustomFieldValueService.findByIssueId(issue.getId()); issueVo.setIssueCustomFieldValueVos(issueCustomFieldValueVos); return issueVo; } // 이슈에 등록된 댓글 정보를 셋팅한다. private void setIssueComments(Issue issue, IssueVo issueVo) { issueVo.setIssueCommentVos(this.issueCommentService.findIssueComment(issue.getId())); } // 이슈 기록 정보를 셋팅한다. private void setIssueHistory(Issue issue, IssueVo issueVo) { issueVo.setIssueHistoryVos(this.issueHistoryService.findIssueHistory(issue.getId())); } // 사용자 정의 필드 값이 같은 이슈 찾기 @Override @Transactional public List findIssue(IssueApiForm issueApiform) { List issueCustomFieldValueForms = issueApiform.getIssueCustomFieldValues(); List resultIssueVos = new ArrayList<>(); IssueCustomFieldValueCondition issueCustomFieldValueCondition = new IssueCustomFieldValueCondition(); issueCustomFieldValueCondition.setUseParentIssueId(false); if (issueCustomFieldValueForms.size() > 0) { for (IssueCustomFieldValueForm issueCustomFieldValueForm : issueCustomFieldValueForms) { CustomField customField = this.customFieldService.getCustomField(issueCustomFieldValueForm.getCustomFieldId()); CustomFieldType customFieldType = CustomFieldType.DATETIME; if (customFieldType.equals(customField.getCustomFieldType())) { continue; } // issueCustomFieldValueCondition.addUseValue(issueCustomFieldValueForm.getUseValue()); issueCustomFieldValueCondition.setUseValue(issueCustomFieldValueForm.getUseValue()); List> results = this.issueMapper.findByCustomFieldValue(issueCustomFieldValueCondition); if (results != null && results.size() > 0) { List findIssueVos = new ArrayList<>(); Collections.copy(resultIssueVos, findIssueVos); resultIssueVos.clear(); for (Map result : results) { IssueVo issueVo = ConvertUtil.convertMapToClass(result, IssueVo.class); issueVo.setParentIssueVo(this.getParentIssueVo(MapUtil.getLong(result, "parentIssueId"))); if (findIssueVos.size() == 0) { resultIssueVos.add(issueVo); } else { IssueVo findIssueVo = findIssueVo(findIssueVos, issueVo.getId()); if (findIssueVo != null) { resultIssueVos.add(findIssueVo); } } } } else { resultIssueVos.clear(); return resultIssueVos; } } } return resultIssueVos; } // 리스트에서 해당 아이디를 가지고 있는 이슈 검색 private IssueVo findIssueVo(List list, Long id) { for (IssueVo issueVo : list) { if (id.equals(issueVo.getId())) { return issueVo; } } return null; } // 하위 이슈가 모두 종료 처리 되었을 경우 상위이슈도 완료 처리 private void setParentIssueComplete(Issue parentIssue) { if (parentIssue != null) { this.issueMapper.findNotCompleteByParentIssueId(parentIssue.getId()); } } // 이슈를 수정한다(api용) @Override @Transactional public List modifyIssue(IssueApiForm issueApiForm, List files) { User user = this.convertToUser(issueApiForm.getToken()); IssueForm issueForm = this.convertToIssueForm(issueApiForm, user); List issueVos = this.findIssue(issueApiForm); if (issueVos != null && issueVos.size() > 0) { List issue = Lists.newArrayList(); for (IssueVo issueVo : issueVos) { IssueVo parentIssueVo = issueVo.getParentIssueVo(); if (parentIssueVo != null) { issueForm.setId(parentIssueVo.getId()); } else { issueForm.setId(issueVo.getId()); } Issue modifyIssue = this.modifyIssueForApi(user, issueForm, files); Issue parentIssue = modifyIssue.getParentIssue(); if (parentIssue != null) { List> results = this.issueMapper.findNotCompleteByParentIssueId(parentIssue.getId()); } issue.add(modifyIssue); } return issue; } else { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.API_ISSUE_NOT_EXIST)); } } // 이슈를 수정한다. @Override @Transactional public Issue modifyIssue(IssueForm issueForm, List multipartFiles) { User user = this.webAppUtil.getLoginUserObject(); return modifyIssue(user, issueForm, multipartFiles); } // 수정 데이터가 유효한지 확인 private CheckIssueData checkIssue(User user, IssueForm issueForm) { // 사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다. this.workspaceService.checkUseWorkspace(user, user.getLastWorkspaceId()); Issue issue = this.getIssue(issueForm.getId()); IssueStatus oldIssueStatus = issue.getIssueStatus(); // 이슈 수정 권한 체크 this.verifyIssueModifyPermission(issue, user); // 프로젝트 유효성 체크 Project project = this.projectService.getProject(issueForm.getProjectId()); // 이슈 상태 유효성 체크 IssueStatus issueStatus = this.issueStatusService.getIssueStatus(issueForm.getIssueStatusId()); // 이슈 유형 유효성 체크 IssueType issueType = this.issueTypeService.getIssueType(issueForm.getIssueTypeId()); // 우선순위 유효성 체크 Priority priority = this.priorityService.getPriority(issueForm.getPriorityId()); // 중요도 유효성 체크 Severity severity = this.severityService.getSeverity(issueForm.getSeverityId()); // 제목 유효성 체크 this.verifyTitle(issueForm.getTitle()); // 날짜 유효성 체크 this.checkStartCompleteDate(issueForm.getStartDate(), issueForm.getCompleteDate()); // 담당자 유효성 체크 //this.verifyIssueAssignee(project, issueForm); // 담당부서 유효성 체크 //this.verifyIssueDepartment(project, issueForm); CheckIssueData checkIssueData = new CheckIssueData(); checkIssueData.setIssue(issue); checkIssueData.setProject(project); checkIssueData.setOldIssueStatus(oldIssueStatus); checkIssueData.setNewIssueStatus(issueStatus); checkIssueData.setIssueType(issueType); checkIssueData.setPriority(priority); checkIssueData.setSeverity(severity); return checkIssueData; } // 이슈 수정(API용) private Issue modifyIssueForApi(User user, IssueForm issueForm, List multipartFiles) { CheckIssueData checkIssueData = this.checkIssue(user, issueForm); Issue issue = checkIssueData.getIssue(); Project project = checkIssueData.getProject(); IssueType issueType = checkIssueData.getIssueType(); IssueStatus oldIssueStatus = checkIssueData.getOldIssueStatus(); IssueStatus issueStatus = checkIssueData.getNewIssueStatus(); // 변경 이력 정보 추출 StringBuilder detectIssueChange = this.issueHistoryService.detectIssueChange(issueForm, checkIssueData, multipartFiles); // 프로젝트가 변경되면 이슈 넘버를 새로 따야 한다. this.checkChangeProject(checkIssueData.getProject(), issue); // 이슈 유형이 변경되었는지 확인하고 변경되었다면 이슈 상태 속성이 '대기' 인 이슈 상태로 교체한다. if (this.checkChangeIssueType(issueType, issueStatus, issue)) { issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(issueType.getWorkflow()); // 이슈 상태 변경 이력 남기기 - 이력을 남기기 위해 issueForm 에 issueStatus Id 값을 저장. issueForm.setIssueStatusId(issueStatus.getId()); this.issueHistoryService.detectIssueStatus(issue, issueForm, detectIssueChange, oldIssueStatus, issueStatus); } issue = this.saveIssue(issueForm, checkIssueData); // 이슈 이력 등록 if (!StringUtils.isEmpty(detectIssueChange.toString())) { this.issueHistoryService.addIssueHistory(issue, user, IssueHistoryType.MODIFY, detectIssueChange.toString()); } // 사용자 시스템 기능 사용 정보 수집 UserVo userVo = ConvertUtil.copyProperties(user, UserVo.class); log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(userVo, ElasticSearchConstants.ISSUE_MODIFY)); return issue; } private Issue saveIssue(IssueForm issueForm, CheckIssueData checkIssueData) { Issue issue = checkIssueData.getIssue(); ConvertUtil.copyProperties(issueForm, issue, "id"); issue.setProject(checkIssueData.getProject()); issue.setIssueStatus(checkIssueData.getNewIssueStatus()); issue.setIssueType(checkIssueData.getIssueType()); issue.setPriority(checkIssueData.getPriority()); issue.setSeverity(checkIssueData.getSeverity()); issue.setStartDate(issueForm.getStartDate()); issue.setCompleteDate(issueForm.getCompleteDate()); return this.issueRepository.saveAndFlush(issue); } // 이슈를 수정한다. @Override @Transactional public Issue modifyIssue(User user, IssueForm issueForm, List multipartFiles) { CheckIssueData checkIssueData = this.checkIssue(user, issueForm); Issue issue = checkIssueData.getIssue(); IssueStatus oldIssueStatus = checkIssueData.getOldIssueStatus(); Project project = checkIssueData.getProject(); IssueStatus issueStatus = checkIssueData.getNewIssueStatus(); IssueType issueType = checkIssueData.getIssueType(); // 변경 이력 정보 추출 StringBuilder detectIssueChange = this.issueHistoryService.detectIssueChange(issueForm, checkIssueData, multipartFiles); // 프로젝트가 변경되면 이슈 넘버를 새로 따야 한다. this.checkChangeProject(project, issue); // 이슈 유형이 변경되었는지 확인하고 변경되었다면 이슈 상태 속성이 '대기' 인 이슈 상태로 교체한다. if (this.checkChangeIssueType(issueType, issueStatus, issue)) { issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(issueType.getWorkflow()); // 이슈 상태 변경 이력 남기기 - 이력을 남기기 위해 issueForm 에 issueStatus Id 값을 저장. issueForm.setIssueStatusId(issueStatus.getId()); this.issueHistoryService.detectIssueStatus(issue, issueForm, detectIssueChange, oldIssueStatus, issueStatus); } issue = this.saveIssue(issueForm, checkIssueData); //this.issueUserService.modifyIssueUser(issue, project.getWorkspace(), issueForm.getUserIds()); // 담당부서 지정 if(issueForm.getDepartmentIds().size()>0){ this.issueDepartmentService.modifyIssueDepartment(issue, user, project.getWorkspace(), issueForm.getDepartmentIds()); } // multipartFile 을 file Map List 객체로 변경한다. List> convertFileMaps = this.convertMultipartFileToFile(multipartFiles); // 첨부 파일 저장 - 비동기로 작동 this.attachedFileService.addAttachedFile(convertFileMaps, issue, user.getAccount()); // 삭제된 첨부파일 처리 this.attachedFileService.removeAttachedFiles(issueForm.getRemoveFiles()); // 텍스트 에디터에 첨부한 파일을 이슈와 연결 this.checkNotHaveIssueIdAttachedFile(issue, issueForm); // 사용자 정의 필드 저장 this.issueCustomFieldValueService.modifyIssueCustomFieldValue(issue, issueForm.getIssueCustomFields()); // 이슈 이력 등록 if (!StringUtils.isEmpty(detectIssueChange.toString())) { this.issueHistoryService.addIssueHistory(issue, user, IssueHistoryType.MODIFY, detectIssueChange.toString()); } // 사용자 시스템 기능 사용 정보 수집 UserVo userVo = ConvertUtil.copyProperties(user, UserVo.class); log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(userVo, ElasticSearchConstants.ISSUE_MODIFY)); // 업체 정보 저장 this.issueCompanyService.modifyIssueCompanyField(issue, issueForm.getIssueCompanyFields()); // ISP 정보 저장 this.issueIspService.modifyIssueIspField(issue, issueForm.getIssueIspFields()); // HOSTING 정보 저장 this.issueHostingService.modifyIssueHostingField(issue, issueForm.getIssueHostingFields()); return issue; } // multipartFile 을 file Map 객체로 변경한다. private List> convertMultipartFileToFile(List multipartFiles) { List> convertFileMaps = Lists.newArrayList(); if (multipartFiles != null && multipartFiles.size() > 0) { for (MultipartFile multipartFile : multipartFiles) { try { Map fileMap = CommonUtil.makeFileMap(multipartFile); convertFileMaps.add(fileMap); } catch (Exception e) { log.debug("multipartFile -> file 변환 오류" + e.getMessage()); } } } return convertFileMaps; } // 프로젝트가 변경되었는지 확인한다. private void checkChangeProject(Project newProject, Issue issue) { if (!issue.getProject().getId().equals(newProject.getId())) { // 각 프로젝트의 고유 이슈 번호 생성 issue.setIssueNumber(this.issueNumberGeneratorService.generateIssueNumber(newProject)); } } // 이슈 유형이 변경되었는지 확인하고 변경되었으면 이슈 상태를 대기 속성인 이슈 상태로 셋팅한다. private Boolean checkChangeIssueType(IssueType newIssueType, IssueStatus issueStatus, Issue issue) { if (!issue.getIssueType().getId().equals(newIssueType.getId())) { // 이슈 상태를 선택하지 않았을 때 if (issueStatus == null) { return true; } // 이슈 상태의 속성이 '대기' 가 아닌 경우 if (!issueStatus.getIssueStatusType().equals(IssueStatusType.READY)) { return true; } else { // 변경하는 이슈 유형의 워크플로우에 존재하는 상태 속성 '대기'와 동일한 상태인지 확인 IssueStatus newReadyIssueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(newIssueType.getWorkflow()); if (!newReadyIssueStatus.getId().equals(issueStatus.getId())) { return true; } } } return false; } // 이슈 담당부서로 지정될 부서가 해당 프로젝트에 참여 하고 있는 부서인지 확인 private void verifyIssueDepartment(Project project, IssueForm issueForm) { if (issueForm.getDepartmentIds().size() > 0) { List trustDepartmentIds = Lists.newArrayList(); // 참여 확인된 부서 for (Long departmentId : issueForm.getDepartmentIds()) { boolean includeProject = false; for (ProjectRole projectRole : project.getProjectRoles()) { ProjectRoleDepartment projectRoleDepartment = this.projectRoleDepartmentService.findByProjectRoleIdAndDepartmentId(projectRole.getId(), departmentId); if (projectRoleDepartment != null) { includeProject = true; trustDepartmentIds.add(departmentId); break; } } // 데이터 보정 작업 - 프로젝트에서 제외된 사용자는 담당자에서 제외 될 수 있도록 처리 if (!includeProject) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.PROJECT_NOT_INCLUDE_DEPARTMENT)); } } // 참여 확인된 부서로 담당부서 변경 issueForm.setDepartmentIds(trustDepartmentIds); } } // 이슈 담당자로 지정될 사용자가 해당 프로젝트에 참여 하고 있는 사용자 인지 확인 private void verifyIssueAssignee(Project project, IssueForm issueForm) { if (issueForm.getUserIds().size() > 0) { List trustUserIds = Lists.newArrayList(); // 참여 확인된 사용자 for (Long userId : issueForm.getUserIds()) { boolean includeProject = false; for (ProjectRole projectRole : project.getProjectRoles()) { ProjectRoleUser projectRoleUser = this.projectRoleUserService.findByProjectRoleIdAndUserId(projectRole.getId(), userId); if (projectRoleUser != null) { includeProject = true; trustUserIds.add(userId); break; } } // 데이터 보정 작업 - 프로젝트에서 제외된 사용자는 담당자에서 제외 될 수 있도록 처리 if (!includeProject) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.PROJECT_NOT_INCLUDE_USER)); } } // 참여 확인된 사용자로 담당자 변경 issueForm.setUserIds(trustUserIds); } } // 이슈 수정 권한 체크 private void verifyIssueModifyPermission(Issue issue, User user) { // 이슈 수정 권한을 갖고 있는지 확인 if (!this.checkHasPermission(ConvertUtil.copyProperties(issue, IssueVo.class), this.getIssueUserVos(issue), user)) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_MODIFY_PERMISSION)); } } // 이슈에서 담당자 정보를 추출한다. private List getIssueUserVos(Issue issue) { List userVos = Lists.newArrayList(); Set issueUsers = issue.getIssueUsers(); try { for (IssueUser issueUser : issueUsers) { User user = issueUser.getUser(); UserVo userVo = ConvertUtil.copyProperties(user, UserVo.class, "password"); userVos.add(userVo); } } catch (Exception ex) { } return userVos; } // 이슈 수정 권한을 갖고 있는지 확인 private boolean checkHasPermission(IssueVo issueVo, List issueUserVos, User user) { boolean hasPermission = false; // 업무 공간 관리자일 경우 수정 권한을 갖는다. hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.WORKSPACE_MANAGER, issueVo, null, null, user); // 프로젝트 관리자일 경우 해당 프로젝트에 등록된 이슈는 수정 권한을 갖는다. hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.PROJECT_MANAGER, issueVo, null, null, user); // 이슈 등록자일 경우 수정 권한을 갖는다. hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.REGISTER, issueVo, null, null, user); // 이슈 담당자일 경우 수정 권한을 갖는다. => 담당부서로 수정 - 체크 //hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.ASSIGNEE, issueVo, issueUserVos); // 담당자가 없으면 모든 사용자가 수정 권한을 갖는다. return hasPermission; } // 이슈 수정 권한을 확인한다. private boolean checkIssueModifyPermission(Boolean hasPermission, String checkType, IssueVo issueVo, List issueUserVos, List issueDepartmentVos, User user) { if (!hasPermission) { switch (checkType) { case Issue.WORKSPACE_MANAGER: // 업무 공간 관리자 // 업무 공간 관리자일 경우 수정 권한을 갖는다. hasPermission = this.userWorkspaceService.checkWorkspaceManager(user); break; case Issue.PROJECT_MANAGER: // 프로젝트 관리자 Issue issue = this.getIssue(issueVo.getId()); // 프로젝트 관리자일 경우 해당 프로젝트에 등록된 이슈는 수정 권한을 갖는다. hasPermission = this.projectRoleUserService.checkProjectManager(issue.getProject(), user); break; case Issue.REGISTER: // 이슈 등록자 hasPermission = issueVo.getRegisterId().equals(user.getId()); break; case Issue.ASSIGNEE: // 담당자가 없으면 모든 사용자가 수정 권한을 갖는다. if (issueUserVos.size() < 1) { hasPermission = true; break; } // 이슈 담당자 여부 확인 for (UserVo issueUserVo : issueUserVos) { if (issueUserVo.getId().equals(user.getId())) { hasPermission = true; break; } } break; case Issue.DEPARTMENT: // 담당부서가 없으면 모든 사용자가 수정 권한을 갖는다. if (issueDepartmentVos.size() < 1) { hasPermission = true; break; } // 이슈 담당부서 여부 확인 /*for (DepartmentVo issueDepartmentVo : issueDepartmentVos) { if (issueDepartmentVo.getId().equals()) { hasPermission = true; break; } }*/ break; } } return hasPermission; } // 이슈 상태 변경 @Override @Transactional public void modifyIssueStatus(IssueForm issueForm, User user) { // 사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다. this.workspaceService.checkUseWorkspace(); // 변경 이력 정보 추출 StringBuilder detectIssueChange = new StringBuilder(); // 이슈 수정 권한 체크 Issue issue = this.getIssue(issueForm.getId()); IssueStatus oldIssueStatus = issue.getIssueStatus(); this.verifyIssueModifyPermission(issue, user); IssueStatus issueStatus = this.issueStatusService.getIssueStatus(issueForm.getIssueStatusId()); // 이슈 상태를 변경할 때 선택한 이슈 상태로 변경할 수 있는지 확인한다. this.issueStatusService.checkNextIssueStatus(issue, issueStatus); // 변경 이력 정보 추출 this.issueHistoryService.detectIssueStatus(issue, issueForm, detectIssueChange, oldIssueStatus, issueStatus); issue.setIssueStatus(issueStatus); this.issueRepository.saveAndFlush(issue); // 코멘트 등록 if (!StringUtils.isEmpty(issueForm.getComment())) { IssueCommentForm issueCommentForm = new IssueCommentForm(); issueCommentForm.setIssueId(issue.getId()); issueCommentForm.setDescription(issueForm.getComment()); this.issueCommentService.addIssueComment(issueCommentForm); } // 이슈 이력 등록 if (!StringUtils.isEmpty(detectIssueChange.toString())) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("
    "); stringBuilder.append(detectIssueChange.toString()); stringBuilder.append("
"); this.issueHistoryService.addIssueHistory(issue, IssueHistoryType.MODIFY, stringBuilder.toString()); } // 이슈 버전 생성 this.issueVersionService.addIssueVersion(issue); // 사용자 시스템 기능 사용 정보 수집 log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_STATUS_CHANGE)); } // 이슈 담당자 변경 @Override @Transactional public void modifyIssueUser(IssueForm issueForm) { User user = this.webAppUtil.getLoginUserObject(); // 사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다. this.workspaceService.checkUseWorkspace(); // 변경 이력 정보 추출 StringBuilder detectIssueChange = new StringBuilder(); // 이슈 수정 권한 체크 Issue issue = this.getIssue(issueForm.getId()); this.verifyIssueModifyPermission(issue, user); issue.setProject(this.projectService.getProject(issueForm.getProjectId())); // 변경 이력 정보 추출 this.issueHistoryService.detectIssueManager(issue, issueForm, detectIssueChange); this.issueUserService.modifyIssueUser(issue, issue.getProject().getWorkspace(), issueForm.getDepartmentIds()); //getUserIds -> getDepartmentIds this.issueRepository.saveAndFlush(issue); // 이슈 이력 등록 if (!StringUtils.isEmpty(detectIssueChange.toString())) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("
    "); stringBuilder.append(detectIssueChange.toString()); stringBuilder.append("
"); this.issueHistoryService.addIssueHistory(issue, IssueHistoryType.MODIFY, stringBuilder.toString()); } // 이슈 버전 생성 this.issueVersionService.addIssueVersion(issue); // 사용자 시스템 기능 사용 정보 수집 log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_USER_CHANGE)); } @Override @Transactional public void modifyIssueDepartment(IssueForm issueForm) { User user = this.webAppUtil.getLoginUserObject(); // 사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다. this.workspaceService.checkUseWorkspace(); // 변경 이력 정보 추출 StringBuilder detectIssueChange = new StringBuilder(); // 이슈 수정 권한 체크 Issue issue = this.getIssue(issueForm.getId()); this.verifyIssueModifyPermission(issue, user); issue.setProject(this.projectService.getProject(issueForm.getProjectId())); // 변경 이력 정보 추출 this.issueHistoryService.detectIssueDepartment(issue, issueForm, detectIssueChange); //this.issueUserService.modifyIssueUser(issue, issue.getProject().getWorkspace(), issueForm.getUserIds()); this.issueDepartmentService.modifyIssueDepartment(issue, issue.getProject().getWorkspace(), issueForm.getDepartmentIds()); this.issueRepository.saveAndFlush(issue); // 이슈 이력 등록 if (!StringUtils.isEmpty(detectIssueChange.toString())) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("
    "); stringBuilder.append(detectIssueChange.toString()); stringBuilder.append("
"); this.issueHistoryService.addIssueHistory(issue, IssueHistoryType.MODIFY, stringBuilder.toString()); } // 이슈 버전 생성 this.issueVersionService.addIssueVersion(issue); // 사용자 시스템 기능 사용 정보 수집 log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_USER_CHANGE)); } // 이슈를 삭제한다. @Override @Transactional public void removeIssues(IssueForm issueForm) { // 사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다. User user = this.webAppUtil.getLoginUserObject(); this.workspaceService.checkUseWorkspace(); if (issueForm.getRemoveIds().size() < 1) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.ISSUE_REMOVE_NOT_SELECT)); } List removeIssues = Lists.newArrayList(); for (Long issueId : issueForm.getRemoveIds()) { Issue issue = this.issueRemoves(issueId, user); removeIssues.add(issue); } if (removeIssues.size() > 0) { //this.issueRepository.deleteAll(removeIssues); } // 사용자 시스템 기능 사용 정보 수집 log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_REMOVE)); } private Issue issueRemoves(Long issueId, User user) { Issue issue = this.getIssue(issueId); // 이슈 수정 권한을 갖고 있는지 확인 this.verifyIssueModifyPermission(issue, user); // 이슈 첨부 파일을 삭제한다. if (issue.getAttachedFiles().size() > 0) { List attachedFileIds = Lists.newArrayList(); for (AttachedFile attachedFile : issue.getAttachedFiles()) { attachedFileIds.add(attachedFile.getId()); } // 첨부파일 삭제 this.attachedFileService.removeAttachedFiles(attachedFileIds); } // 지울 이슈가 연관이슈인지 체크 후 연관이슈 테이블에서도 삭제한다. List issueRelationList = this.issueRelationRepository.findByRelationIssueId(issueId); if (issueRelationList != null && issueRelationList.size() > 0) { for(IssueRelation issueRelation : issueRelationList){ StringBuilder sb = new StringBuilder(); issueHistoryService.detectRelationIssue(IssueHistoryType.DELETE, issueRelation, sb); issueHistoryService.addIssueHistory(issueRelation.getIssue(), IssueHistoryType.MODIFY, sb.toString()); this.issueRelationRepository.delete(issueRelation); } } // 이슈 생성, 삭제시 예약 이메일에 등록해놓는다. this.reservationIssueEmail(issue, EmailType.ISSUE_REMOVE); // 이슈 삭제 this.issueRepository.delete(issue); return issue; } @Override @Transactional(readOnly = true) public Issue getIssue(Long id) { if (id == null) { throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_EXIST)); } Issue issue = this.findOne(id); if (issue == null) { throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_EXIST)); } return issue; } // 이슈 유형을 사용하는 이슈 갯수를 조회한다. @Override @Transactional(readOnly = true) public long countByIssueTypeId(Long issueTypeId) { return this.issueMapper.countByIssueTypeId(issueTypeId); } // 이슈 상태를 사용하는 이슈 갯수를 조회한다. @Override @Transactional(readOnly = true) public long countByIssueStatus(Long issueStatusId) { return this.issueMapper.countByIssueStatusId(issueStatusId); } // 이슈 유형에서 워크플로우가 변경되었을 때 해당 워크플로우에 현재 이슈 상태가 존재하지 않으면 대기(생성) 로 변경한다. @Override @Transactional public void changeWorkflows(Workflow workflow, IssueType issueType) { List> issueMaps = this.issueMapper.findByIssueTypeId(issueType.getId()); List issueStatusVos = this.issueStatusService.findByWorkflowId(workflow.getId()); IssueStatus readyIssueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(workflow); List updateIssues = Lists.newArrayList(); for (Map issueMap : issueMaps) { boolean exist = false; for (IssueStatusVo issueStatusVo : issueStatusVos) { Long issueStatusId = MapUtil.getLong(issueMap, "issueStatusId"); if (issueStatusId == null) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_NOT_EXIST)); } if (issueStatusId.equals(issueStatusVo.getId())) { exist = true; break; } } // 존재하지 않는 대상은 if (!exist) { StringBuilder stringBuilder = new StringBuilder(); String issueStatusName = MapUtil.getString(issueMap, "issueStatusName"); if (issueStatusName == null) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_NOT_EXIST)); } Issue issue = this.getIssue(MapUtil.getLong(issueMap, "issueId")); // 워크플로우에서 이슈 상태가 삭제되어 상태가 변경된 정보를 기록한다. this.issueHistoryService.recordRemoveWorkflowToIssueStatus(issueStatusName, readyIssueStatus.getName(), stringBuilder); this.issueHistoryService.addIssueHistory(issue, IssueHistoryType.MODIFY, stringBuilder.toString()); issue.setIssueStatus(readyIssueStatus); updateIssues.add(issue); } } if (updateIssues.size() > 0) { this.issueRepository.saveAll(updateIssues); for (Issue issue : updateIssues) { // 이슈 버전 생성 this.issueVersionService.addIssueVersion(issue); } } } // 이슈 목록을 엑셀로 다운로드 한다. @Override @Transactional public ModelAndView downloadExcel(HttpServletRequest request, Model model) { // 사용 공간에서 로그인한 사용자가 비활성인지 확인하고 비활성일 경우 엑셀 다운로드를 금지한다. ModelAndView modelAndView = this.workspaceService.checkUseExcelDownload(model); if (modelAndView != null) { return modelAndView; } Map conditions = new HashMap<>(); // 엑셀 다운로드에 필요한 검색 조건 정보를 추출하고 검색 조건 추출에 오류가 발생하면 경고를 표시해준다. modelAndView = this.excelConditionCheck.checkCondition(conditions, request, model); if (modelAndView != null) { return modelAndView; } IssueCondition issueCondition = IssueCondition.make(conditions); // 검색 조건을 만든다 this.makeIssueSearchCondition(issueCondition, Lists.newArrayList("01", "02", "03"), null); Set issueIds = new HashSet<>(); // 사용자 정의 필드 검색시 나오는 이슈 아이디 저장 컬렉션 Map resJsonData = new HashMap<>(); ExportExcelVo excelInfo = new ExportExcelVo(); excelInfo.setFileName(this.messageAccessor.message("common.issueList")); // 이슈 목록 excelInfo.addAttrInfos(new ExportExcelAttrVo("issueStatusName", this.messageAccessor.message("common.status"), 6, ExportExcelAttrVo.ALIGN_CENTER)); // 상태 excelInfo.addAttrInfos(new ExportExcelAttrVo("issueKey", this.messageAccessor.message("common.issueKey"), 6, ExportExcelAttrVo.ALIGN_CENTER)); // 이슈 번호 excelInfo.addAttrInfos(new ExportExcelAttrVo("title", this.messageAccessor.message("common.issueTitle"), 40, ExportExcelAttrVo.ALIGN_LEFT)); // 이슈 제목 excelInfo.addAttrInfos(new ExportExcelAttrVo("description", this.messageAccessor.message("common.content"), 60, ExportExcelAttrVo.ALIGN_LEFT)); // 내용 excelInfo.addAttrInfos(new ExportExcelAttrVo("issueTypeName", this.messageAccessor.message("common.issueType"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 이슈 타입 excelInfo.addAttrInfos(new ExportExcelAttrVo("departments", this.messageAccessor.message("common.department"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 담당부서 excelInfo.addAttrInfos(new ExportExcelAttrVo("priorityName", this.messageAccessor.message("common.priority"), 6, ExportExcelAttrVo.ALIGN_CENTER)); // 우선순위 excelInfo.addAttrInfos(new ExportExcelAttrVo("severityName", this.messageAccessor.message("common.importance"), 6, ExportExcelAttrVo.ALIGN_CENTER)); // 중요도 excelInfo.addAttrInfos(new ExportExcelAttrVo("register", this.messageAccessor.message("common.register"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 등록자 excelInfo.addAttrInfos(new ExportExcelAttrVo("period", this.messageAccessor.message("common.period"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 기간 excelInfo.addAttrInfos(new ExportExcelAttrVo("modifyDate", this.messageAccessor.message("common.modifyDate"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 최종 변경일 // 사용자 정의 필드를 사용한 이슈를 찾는다. 만약 이슈가 없다면 여기서 이슈 조회가 끝난다. if (!this.searchUseCustomFields(issueCondition, issueIds, resJsonData, null)) { model.addAttribute(Constants.EXCEL, excelInfo); return new ModelAndView(this.excelView); } List issueVos = Lists.newArrayList(); // 이슈 목록 데이터 저장 컬렉션 // 사용자 정의 필드로 검색한 이슈 아이디 값 List issueKeys = Lists.newArrayList(issueIds); issueCondition.setIssueIds(issueKeys); List> results = this.issueMapper.find(issueCondition); // 엑셀 다운로드를 진행할 때 전체 사용자 정의 필드를 표시한다. this.makeIssueExcelDownloadCustomFields(excelInfo); // 이슈 아이디 초기화 issueCondition.setIssueIds(Lists.newArrayList()); // Map 에 있는 데이터를 IssueVo 데이터로 변환한다. User user = this.webAppUtil.getLoginUserObject(); this.setMapToIssueVo(results, issueVos, issueCondition, user); // IssueVos 데이터를 엑셀에서 표시할 수 있는 데이터로 변경한다. List> convertExcelViewToIssueMaps = this.convertExcelViewToIssueVos(issueVos); if (results.size() > EXCEL_DOWNLOAD_MAX_ROWS) { // 엑셀 1만건 초과 알림 this.simpMessagingTemplate.convertAndSendToUser(this.webAppUtil.getLoginUser().getAccount(), "/notification/system-alert", this.messageAccessor.getMessage(MsgConstants.EXCEL_DOWNLOAD_MAX_ROWS_OVER)); // 1만 건만 출력해준다. excelInfo.setDatas(convertExcelViewToIssueMaps.subList(0, EXCEL_DOWNLOAD_MAX_ROWS)); model.addAttribute(Constants.EXCEL, excelInfo); return new ModelAndView(this.excelView); } // 엑셀에 넣을 데이터 - IssueVos 데이터를 엑셀에서 표시할 수 있는 데이터로 변경한다. excelInfo.setDatas(convertExcelViewToIssueMaps); model.addAttribute(Constants.EXCEL, excelInfo); return new ModelAndView(this.excelView); } // 엑셀 다운로드를 진행할 때 전체 사용자 정의 필드를 표시한다. private void makeIssueExcelDownloadCustomFields(ExportExcelVo excelInfo) { List customFields = this.customFieldService.findByWorkspaceId(); for (CustomField customField : customFields) { excelInfo.addAttrInfos(new ExportExcelAttrVo("customField_" + customField.getId(), customField.getName(), 10, ExportExcelAttrVo.ALIGN_CENTER)); } } // 사용자 정의 필드 정보 추가 private void setIssueCustomFieldValue(List issueVos, IssueCondition issueCondition) { // 이슈에서 저장한 사용자 정의 필드 값을 조회한다. List> issueCustomFieldValues = this.issueCustomFieldValueService.findInIssueIds(issueCondition); for (IssueVo issueVo : issueVos) { for (Map issueCustomFieldValue : issueCustomFieldValues) { if (issueVo.getId().equals(MapUtil.getLong(issueCustomFieldValue, "issueId"))) { IssueCustomFieldValueVo issueCustomFieldValueVo = new IssueCustomFieldValueVo(); issueCustomFieldValueVo.setUseValue(MapUtil.getString(issueCustomFieldValue, "useValue")); CustomFieldVo customFieldVo = new CustomFieldVo(); customFieldVo.setId(MapUtil.getLong(issueCustomFieldValue, "customFieldId")); issueCustomFieldValueVo.setCustomFieldVo(customFieldVo); issueVo.addIssueCustomFieldValueVo(issueCustomFieldValueVo); } } } } // 업체 정보 추가 private void setIssueCompanyField(Issue issue, IssueVo issueVo) { List issueCompanyVos = Lists.newArrayList(); for(IssueCompany issueCompany : issue.getIssueCompanies()){ IssueCompanyVo issueCompanyVo = ConvertUtil.copyProperties(issueCompany, IssueCompanyVo.class); issueCompanyVo.setId(issueCompany.getId()); CompanyField companyField = issueCompany.getCompanyField(); if (companyField != null) { issueCompanyVo.setCompanyId(issueCompany.getCompanyField().getId()); } issueCompanyVos.add(issueCompanyVo); } issueVo.setIssueCompanyVos(issueCompanyVos); } // Isp 정보 추가 private void setIssueIspField(Issue issue, IssueVo issueVo) { List issueIspVos = Lists.newArrayList(); for(IssueIsp issueIsp : issue.getIssueIspFields()){ IssueIspVo issueIspVo = ConvertUtil.copyProperties(issueIsp, IssueIspVo.class); issueIspVo.setId(issueIsp.getId()); IspField ispField = issueIsp.getIspField(); if (ispField != null) { issueIspVo.setIspId(ispField.getId()); } issueIspVos.add(issueIspVo); } issueVo.setIssueIspVos(issueIspVos); } // Hosting 정보 추가 private void setIssueHostingField(Issue issue, IssueVo issueVo) { List issueHostingVos = Lists.newArrayList(); for(IssueHosting issueHosting : issue.getIssueHostingFields()){ IssueHostingVo issueHostingVo = ConvertUtil.copyProperties(issueHosting, IssueHostingVo.class); issueHostingVo.setId(issueHosting.getId()); HostingField hostingField = issueHosting.getHostingField(); if (hostingField != null) { issueHostingVo.setHostingId(hostingField.getId()); } issueHostingVos.add(issueHostingVo); } issueVo.setIssueHostingVos(issueHostingVos); } // 연관일감 정보 추가 private void setRelationIssue(IssueVo issueVo, Long issueId) { List relationIssues = this.issueRelationService.findRelationIssue(issueId); issueVo.setIssueRelationIssueVos(relationIssues); } // IssueVos 데이터를 엑셀에서 표시할 수 있는 데이터로 변경한다. private List> convertExcelViewToIssueVos(List issueVos) { List> results = Lists.newArrayList(); for (IssueVo issueVo : issueVos) { try { Map result = new HashMap<>(); result.put("issueStatusName", issueVo.getIssueStatusName()); result.put("issueKey", issueVo.getProjectKey() + "-" + issueVo.getIssueNumber()); result.put("title", issueVo.getTitle()); String description = ""; if (issueVo.getDescription() != null) { description = Jsoup.parse(issueVo.getDescription()).text(); // HTML 태그 제거 description = description.replaceAll("\\<.*?>", ""); // 공백 제거 } result.put("description", description); result.put("issueTypeName", issueVo.getIssueTypeName()); result.put("assignees", CommonUtil.convertUserVosToString(issueVo.getUserVos())); result.put("departments", CommonUtil.convertDepartmentVosToString(issueVo.getDepartmentVos())); result.put("priorityName", issueVo.getPriorityName()); result.put("severityName", issueVo.getSeverityName()); UserVo register = this.userService.removeSensitiveUser(issueVo.getRegisterId()); // 등록자 result.put("register", register.getByName()); // 최종 변경일 result.put("modifyDate", issueVo.getModifyDate()); if (StringUtils.isEmpty(issueVo.getStartDate())) { result.put("period", ""); } else { result.put("period", issueVo.getStartDate() + " ~ " + issueVo.getCompleteDate()); } for (IssueCustomFieldValueVo issueCustomFieldValueVo : issueVo.getIssueCustomFieldValueVos()) { // 사용자 정의 필드 값 저장이 아직 안되어 있는 경우 if (result.get("customField_" + issueCustomFieldValueVo.getCustomFieldVo().getId()) == null) { result.put("customField_" + issueCustomFieldValueVo.getCustomFieldVo().getId().toString(), issueCustomFieldValueVo.getUseValue()); } else { // 이미 저장되어 있으면 다중 선택 사용자 정의 필드 값이다. String useValue = result.get("customField_" + issueCustomFieldValueVo.getCustomFieldVo().getId()); result.put("customField_" + issueCustomFieldValueVo.getCustomFieldVo().getId().toString(), useValue + ", " + issueCustomFieldValueVo.getUseValue()); } } results.add(result); } catch (Exception e) { log.error("엑셀 다운로드 오류 발생"); } } return results; } // 이슈 다중 상태 변경 @Override @Transactional public void modifyMultiIssueStatus(IssueForm issueForm) { // 사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다. this.workspaceService.checkUseWorkspace(); User user = this.webAppUtil.getLoginUserObject(); for (Long issueId : issueForm.getIds()) { issueForm.setId(issueId); // 이슈 상태 변경 this.modifyIssueStatus(issueForm, user); } // 담당 부서 수정 if (issueForm.getDepartmentIds().size() > 0) { Issue issue = this.getIssue(issueForm.getId()); Project project = this.projectService.getProject(issueForm.getProjectId()); this.issueDepartmentService.modifyIssueDepartment(issue, project.getWorkspace(), issueForm.getDepartmentIds()); } } // 이슈 Import 용 엑셀 템플릿 다운로드 @Override @Transactional public ModelAndView downloadExcelTemplate(HttpServletRequest request, Model model) { Map conditions; try { // 엑셀 다운로드에 필요한 검색 조건 정보를 추출한다. conditions = CommonUtil.getSearchConditions(request); } catch (IOException e) { throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.EXCEL_CONDITIONS_NOT_EXIST)); } ExportExcelVo excelInfo = new ExportExcelVo(); excelInfo.setHideCount(true); excelInfo.setFileName(this.messageAccessor.message("common.registerExcelIssue")); // 엑셀로 이슈 등록하기 excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.title"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 제목 excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.content"), 40, ExportExcelAttrVo.ALIGN_CENTER)); // 내용 excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.projectKey"), 10, ExportExcelAttrVo.ALIGN_LEFT)); // 프로젝트 키 excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.issueType"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 이슈 타입 excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.priority"), 5, ExportExcelAttrVo.ALIGN_CENTER)); // 우선순위 excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.importance"), 5, ExportExcelAttrVo.ALIGN_CENTER)); // 중요도 excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.department"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 담당부서 excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.startDate"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 시작일 excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.endDate"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 종료일 // 프로젝트에 연결된 사용자 정의 필드 정보를 추출하여 엑셀 download 템플릿을 만든다. this.makeIssueExcelTemplateCustomFields(excelInfo, conditions); // 엑셀에 넣을 데이터 - IssueVos 데이터를 엑셀에서 표시할 수 있는 데이터로 변경한다. excelInfo.setDatas(Lists.newArrayList(new IssueVo())); model.addAttribute(Constants.EXCEL, excelInfo); return new ModelAndView(this.excelView); } // 프로젝트에 연결된 사용자 정의 필드 정보를 추출하여 엑셀 download 템플릿을 만든다. private void makeIssueExcelTemplateCustomFields(ExportExcelVo excelInfo, Map conditions) { List issueTypeCustomFields = this.issueTypeCustomFieldService.findByProjectIdAndIssueTypeId(MapUtil.getLong(conditions, "projectId"), MapUtil.getLong(conditions, "issueTypeId")); for (IssueTypeCustomField issueTypeCustomField : issueTypeCustomFields) { excelInfo.addAttrInfos(new ExportExcelAttrVo("id", issueTypeCustomField.getCustomField().getName(), 10, ExportExcelAttrVo.ALIGN_CENTER)); } } // 엑셀 import 로 이슈를 등록한다. @Override @Transactional public void importExcel(MultipartFile multipartFile) throws Exception { /*StopWatch serviceStart = new StopWatch(); serviceStart.start();*/ // 사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다. this.workspaceService.checkUseWorkspace(); if (multipartFile != null) { // 업로드 파일 확장자 체크 this.verifyMultipartFileExtension(multipartFile); Map projectMaps = new HashMap<>(); // 프로젝트 모음 Map issueTypeMaps = new HashMap<>(); // 이슈 타입 모음 Map priorityMaps = new HashMap<>(); // 우선 순위 모음 Map severityMaps = new HashMap<>(); // 중요도 모음 Map userMaps = new HashMap<>(); // 사용자 모음 Map departmentMaps = new HashMap<>(); // 부서 모음 Map customFieldMaps = new HashMap<>(); Map issueStatusReadyMaps = new HashMap<>(); // 상태 속성 '대기'인 이슈 상태 Map issueNumberMaps = new HashMap<>(); // 이슈 번호 모음 Map issueTypeCustomFieldMaps = new HashMap<>(); // 이슈 타입 + 사용자 정의 필드 연결 정보 Workspace workspace = this.workspaceService.getWorkspace(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId()); // 이슈를 넣으려는 업무 공간 // 이슈의 주요 속성을 map 에 저장하여 엑셀 import 에서 지정한 대상(이슈 속성)을 빠르게 찾을 수 있게 한다. this.IssueAttributeMapToList(projectMaps, issueTypeMaps, priorityMaps, severityMaps, userMaps, departmentMaps, customFieldMaps, issueNumberMaps, issueTypeCustomFieldMaps, issueStatusReadyMaps); // 0.237 - 0.230 List issueForms = Lists.newArrayList(); List headers = Lists.newArrayList(); Workbook workbook; workbook = WorkbookFactory.create(multipartFile.getInputStream()); Sheet sheet = workbook.getSheetAt(0); int lastRowNum = sheet.getLastRowNum() + 1; // 2건 - 제목, 헤더 - 성능을 위해 최대 1만건으로 제한 if (lastRowNum > (EXCEL_IMPORT_MAX_ROWS + 2)) { throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_MAX_ROWS_OVER)); } for (int rowIndex = 0; rowIndex < lastRowNum; rowIndex++) { // 0번은 헤더는 무시한다. Row row = sheet.getRow(rowIndex); // 헤더 정보를 추출한다 - 사용자 정의 필드 정보를 가져오기 위해 if (rowIndex == 1) { for (int cellIndex = 0; cellIndex < row.getLastCellNum(); cellIndex++) { Cell cell = row.getCell(cellIndex); if (cell == null) { throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.EXCEL_EMPTY_CELL)); } // 엑셀 import 데이터에서 cell 값을 문자열로 변환한다. String cellValue = CommonUtil.convertExcelStringToCell(cell); if (StringUtils.isEmpty(cellValue)) { throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.EXCEL_HEADER_EMPTY_CELL)); } headers.add(cellValue); } } // 1번 헤더부터 데이터 영역 if (rowIndex > 1) { // 이슈로 등록하기 위해 IssueForm 에 데이터를 셋팅한다. issueForms.add(this.setIssueFormToExcelField(row, (rowIndex + 1), issueStatusReadyMaps, projectMaps, issueTypeMaps, priorityMaps, severityMaps, userMaps, customFieldMaps, issueNumberMaps, headers)); } } if (issueForms.size() < 1) { return; } // 1.176 // 이슈 등록 this.issueMapper.insertBatch(issueForms); // 0.416 - 0.439 // 1.373 ~ 1.394 // TODO - 이슈 이력 벌크 등록을 할 경우 프로필 이력 조회에서 부하가 심해서 넣을지 고민해야함. // this.bulkInsertIssueHistory(issueForms); // 이슈 담당자 벌크 등록 this.bulkInsertIssueAssignee(issueForms, workspace); // 0.361 - 0.705 // 1.816 /*StopWatch serviceStart = new StopWatch(); serviceStart.start();*/ // 이슈 사용자 정의 값 필드 벌크 등록 this.bulkInsertIssueCustomFieldValue(issueForms, issueTypeCustomFieldMaps); // 3.628 - 3.445 // 업체,ISP,호스팅 추가 /*serviceStart.stop(); log.debug("2차 저장 시간 : " + serviceStart.getTime());*/ // 이슈 리스크 벌크 등록 this.bulkInsertIssueRisk(issueForms, workspace); // reverse index 업데이트 this.issueMapper.updateBatch(issueForms); // 증가된 이슈 번호를 업데이트 한다. this.issueNumberGeneratorService.updateIssueNumber(issueNumberMaps); } } // 업로드 파일 확장자 체크 private void verifyMultipartFileExtension(MultipartFile multipartFile) { multipartFile.getOriginalFilename(); int pos = multipartFile.getOriginalFilename().lastIndexOf("."); String ext = multipartFile.getOriginalFilename().substring(pos + 1); if (!ext.equals("xlsx")) { throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.EXCEL_NOT_EXTENSION)); } } // 이슈 이력 벌크 등록 private void bulkInsertIssueHistory(List issueForms) { List> issueHistoryMaps = Lists.newArrayList(); for (IssueForm issueForm : issueForms) { Map issueHistoryMap = new HashMap<>(); issueHistoryMap.put("issueId", issueForm.getId()); issueHistoryMap.put("projectId", issueForm.getProjectId()); issueHistoryMap.put("registerId", this.webAppUtil.getLoginId()); issueHistoryMap.put("issueHistoryType", IssueHistoryType.ADD.toString()); StringBuilder description = new StringBuilder(); // 이력 테스트 추출 this.issueHistoryService.makeDescription(description, IssueHistoryType.ADD, null); issueHistoryMap.put("description", description.toString()); issueHistoryMaps.add(issueHistoryMap); } // 이슈 이력 벌크 등록 this.issueMapper.insertHistoryBatch(issueHistoryMaps); } // 이슈 담당자 벌크 등록 private void bulkInsertIssueAssignee(List issueForms, Workspace workspace) { List> issueAssigneeMaps = Lists.newArrayList(); for (IssueForm issueForm : issueForms) { for (Long departmentId : issueForm.getDepartmentIds()) { Map issueAssigneeMap = new HashMap<>(); issueAssigneeMap.put("issueId", issueForm.getId()); //issueAssigneeMap.put("userId", userId); issueAssigneeMap.put("departmentId", departmentId); issueAssigneeMap.put("workspaceId", workspace.getId()); issueAssigneeMap.put("registerId", this.webAppUtil.getLoginId()); issueAssigneeMaps.add(issueAssigneeMap); } } if (issueAssigneeMaps.size() > 0) { // 이슈 담당자 벌크 등록 //this.issueUserService.insertIssueUser(issueAssigneeMaps); this.issueDepartmentService.insertIssueDepartment(issueAssigneeMaps); } } // 이슈 리스크 벌크 등록 private void bulkInsertIssueRisk(List issueForms, Workspace workspace) { List> issueRiskMaps = Lists.newArrayList(); for (IssueForm issueForm : issueForms) { Map issueRiskMap = new HashMap<>(); issueRiskMap.put("issueId", issueForm.getId()); issueRiskMap.put("changeAssigneeCount", 0L); issueRiskMap.put("changeDepartmentCount", 0L); issueRiskMap.put("changeIssueStatusCount", 0L); issueRiskMap.put("workspaceId", workspace.getId()); issueRiskMap.put("issueStatusIds", issueForm.getIssueStatusId()); issueRiskMap.put("registerId", this.webAppUtil.getLoginId()); issueRiskMaps.add(issueRiskMap); } if (issueRiskMaps.size() > 0) { // 이슈 리스크 벌크 등록 this.issueMapper.insertIssueRiskBatch(issueRiskMaps); } } // 이슈 사용자 정의 필드 선택 값 벌크 등록 private void bulkInsertIssueCustomFieldValue(List issueForms, Map issueTypeCustomFieldMaps) { List> issueCustomFieldValueMaps = Lists.newArrayList(); for (IssueForm issueForm : issueForms) { for (Map issueCustomField : issueForm.getIssueCustomFields()) { String findKey = issueForm.getIssueTypeId().toString() + MapUtil.getString(issueCustomField, "customFieldId"); // 이슈 타입 + 사용자 정의 필드 연결정보가 없다면 저장하지 않는다. if (issueTypeCustomFieldMaps.get(findKey) == null) { continue; } issueCustomField.put("issueTypeCustomFieldId", issueTypeCustomFieldMaps.get(findKey)); issueCustomField.put("issueId", issueForm.getId()); issueCustomField.put("registerId", this.webAppUtil.getLoginId()); issueCustomFieldValueMaps.add(issueCustomField); } } if (issueCustomFieldValueMaps.size() > 0) { this.issueMapper.insertIssueCustomFieldValueBatch(issueCustomFieldValueMaps); } } // 이슈의 주요 속성을 map 에 저장하여 엑셀 import 에서 지정한 대상(이슈 속성)을 빠르게 찾을 수 있게 한다. private void IssueAttributeMapToList(Map projectMaps, Map issueTypeMaps, Map priorityMaps, Map severityMaps, Map userMaps, Map departmentMaps, Map customFieldMaps, Map issueNumberMaps, Map issueTypeCustomFieldMaps, Map issueStatusReadyMaps) { // 프로젝트 키로 바로 찾을 수 있게 준비 List projects = this.projectService.findByWorkspaceId(); List projectIds = Lists.newArrayList(); for (Project project : projects) { projectIds.add(project.getId()); // 해당 프로젝트에서 생성되는 다음 이슈 번호를 생성해온다. issueNumberMaps.put(project.getId(), this.issueNumberGeneratorService.generateIssueNumber(project)); projectMaps.put(project.getProjectKey(), project); for (IssueTypeCustomField issueTypeCustomField : project.getIssueTypeCustomFields()) { // 빠르게 찾기 위해 이슈 타입 아이디 + 사용자 정의 필드 아이디를 키로 한다. String makeKey = issueTypeCustomField.getIssueType().getId().toString() + issueTypeCustomField.getCustomField().getId().toString(); issueTypeCustomFieldMaps.put(makeKey, issueTypeCustomField.getId()); } // 프로젝트에 참여하는 사용자 정보 List> users = this.userService.findProjectMember(project); Map userMap = new HashMap<>(); // 사용자 정보를 Map 에 저장 for (Map user : users) { userMap.put(CommonUtil.decryptAES128(MapUtil.getString(user, "account")), MapUtil.getLong(user, "userId")); } userMaps.put(project.getProjectKey(), userMap); // 프로젝트에 참여하는 부서 정보 List> departments = this.departmentService.findProjectDepartment(project); List departmentList = Lists.newArrayList(); // 부서 정보를 저장 for (Map department : departments) { departmentList.add(MapUtil.getLong(department, "departmentId")); } } // 이슈 유형을 바로 찾을 수 있게 준비 List issueTypes = this.issueTypeService.findByWorkspaceId(); for (IssueType issueType : issueTypes) { issueTypeMaps.put(issueType.getName(), issueType); IssueStatus issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(issueType.getWorkflow()); issueStatusReadyMaps.put(issueType.getId().toString(), issueStatus); } // 우선순위를 바로 찾을 수 있게 준비 List priorities = this.priorityService.findByWorkspaceId(); for (Priority priority : priorities) { priorityMaps.put(priority.getName(), priority); } // 중요도를 바로 찾을 수 있게 준비 List severities = this.severityService.findByWorkspaceId(); for (Severity severity : severities) { severityMaps.put(severity.getName(), severity); } // 사용자 정의 필드를 바로 찾을 수 있게 준비 List customFields = this.customFieldService.findByWorkspaceId(); for (CustomField customField : customFields) { customFieldMaps.put(customField.getName(), customField); } } // 엑셀 필드에 있는 정보를 이슈 form 으로 옮긴다. private IssueForm setIssueFormToExcelField(Row row, int rowIndex, Map issueStatusReadyMaps, Map projectMaps, Map issueTypeMaps, Map priorityMaps, Map severityMaps, Map userMaps, Map customFieldMaps, Map issueNumberMaps, List headers) { IssueForm issueForm = new IssueForm(); issueForm.setRegisterId(this.webAppUtil.getLoginId()); Project project = null; // 제목, 내용, 프로젝트 키, 이슈 타입, 우선순위, 중요도, 담당자, 시작일, 종료일, 사용자 정의 필드 for (int cellIndex = 0; cellIndex < headers.size(); cellIndex++) { Cell cell = row.getCell(cellIndex); switch (cellIndex) { case 0: // 이슈 제목을 IssueForm 에 저장한다. this.setIssueFormTitle(cell, issueForm, rowIndex); break; case 1: // 내용 if (cell != null) { issueForm.setDescription(CommonUtil.convertExcelStringToCell(cell)); } else { // null 입력 방지 issueForm.setDescription(""); } break; case 2: // 프로젝트 키와 이슈 번호 project = this.setIssueFormProjectKeyAndIssueNumber(cell, issueForm, projectMaps, issueNumberMaps, rowIndex); break; case 3: // 이슈 타입을 IssueForm 에 저장한다. this.setIssueFormIssueType(cell, issueTypeMaps, issueForm, rowIndex); // 이슈 타입에 연결된 워크플로우의 상태 속성 '대기' 인 상태를 issueForm 에 저장한다. this.setIssueFormIssueStatus(issueStatusReadyMaps, issueForm, rowIndex); break; case 4: // 우선순위를 IssueForm 에 저장한다. this.setIssueFormPriority(cell, priorityMaps, issueForm, rowIndex); break; case 5: // 중요도를 IssueForm 에 저장한다. this.setIssueFormSeverity(cell, severityMaps, issueForm, rowIndex); break; case 6: // 담당부서를 IssueForm 에 저장한다. this.setIssueFormAssignee(cell, userMaps, issueForm, project); break; case 7: // 시작일을 IssueForm 에 저장한다. this.setIssueFormPeriod(cell, issueForm, true, rowIndex); break; case 8: // 종료일을 IssueForm 에 저장한다. this.setIssueFormPeriod(cell, issueForm, false, rowIndex); break; default: // 8번 이상부터는 사용자 정의 필드. 사용자 정의 필드 정보를 IssueForm 에 저장한다. this.setIssueFormCustomFieldValue(cell, customFieldMaps, issueForm, headers.get(cellIndex), rowIndex); } } return issueForm; } // 이슈 제목을 IssueForm 에 저장한다. private void setIssueFormTitle(Cell cell, IssueForm issueForm, int rowIndex) { if (cell == null) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_ISSUE_TITLE_IS_NULL, rowIndex)); } String title = CommonUtil.convertExcelStringToCell(cell); // 제목 유효성 체크 this.verifyTitle(title); issueForm.setTitle(title); } // 프로젝트 키, 이슈 고유 번호, 담당자를 IssueForm 에 저장한다. private Project setIssueFormProjectKeyAndIssueNumber(Cell cell, IssueForm issueForm, Map projectMaps, Map issueNumberMaps, int rowIndex) { // 프로젝트 아이디를 IssueForm 에 저장한다. Project project = this.setIssueFormProject(cell, projectMaps, issueForm, rowIndex); // 이슈 고유 번호를 IssueForm 에 저장한다. this.setIssueFormIssueNumber(issueForm, issueNumberMaps, project, rowIndex); return project; } // 프로젝트 아이디를 IssueForm 에 저장한다. private Project setIssueFormProject(Cell cell, Map projectMaps, IssueForm issueForm, int rowIndex) { if (cell == null) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_PROJECT_KEY_IS_NULL, rowIndex)); } Project project = projectMaps.get(CommonUtil.convertExcelStringToCell(cell).toUpperCase()); if (project == null) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_PROJECT_NOT_EXIST, rowIndex)); } issueForm.setProjectId(project.getId()); return project; } // 이슈 고유 번호를 IssueForm 에 저장한다. private void setIssueFormIssueNumber(IssueForm issueForm, Map issueNumberMaps, Project project, int rowIndex) { // 이슈 고유 번호 Long issueNumber = issueNumberMaps.get(project.getId()); if (issueNumber == null) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.ISSUE_NUMBER_NOT_EXIST, rowIndex)); } issueForm.setIssueNumber(issueNumber); issueNumberMaps.put(project.getId(), ++issueNumber); // 이슈 번호를 1씩 증가 시킨다. } // 이슈 타입을 IssueForm 에 저장한다. private void setIssueFormIssueType(Cell cell, Map issueTypeMaps, IssueForm issueForm, int rowIndex) { if (cell == null) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_ISSUE_TYPE_IS_NULL, rowIndex)); } IssueType issueType = issueTypeMaps.get(CommonUtil.convertExcelStringToCell(cell)); if (issueType == null) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_ISSUE_TYPE_NOT_EXIST, rowIndex)); } issueForm.setIssueTypeId(issueType.getId()); } // 이슈 타입에 연결된 워크플로우의 상태 속성 '대기' 인 상태를 issueForm 에 저장한다. private void setIssueFormIssueStatus(Map issueStatusReadyMaps, IssueForm issueForm, int rowIndex) { IssueStatus issueStatus = issueStatusReadyMaps.get(issueForm.getIssueTypeId().toString()); if (issueStatus == null) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_ISSUE_STATUS_READY_NOT_EXIST, rowIndex)); } issueForm.setIssueStatusId(issueStatus.getId()); } // 우선순위를 IssueForm 에 저장한다. private void setIssueFormPriority(Cell cell, Map priorityMaps, IssueForm issueForm, int rowIndex) { if (cell == null) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_PRIORITY_IS_NULL, rowIndex)); } Priority priority = priorityMaps.get(CommonUtil.convertExcelStringToCell(cell)); if (priority == null) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_PRIORITY_NOT_EXIST, rowIndex)); } issueForm.setPriorityId(priority.getId()); } // 중요도를 IssueForm 에 저장한다. private void setIssueFormSeverity(Cell cell, Map severityMaps, IssueForm issueForm, int rowIndex) { if (cell == null) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_SEVERITY_IS_NULL, rowIndex)); } Severity severity = severityMaps.get(CommonUtil.convertExcelStringToCell(cell)); if (severity == null) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_SEVERITY_NOT_EXIST, rowIndex)); } issueForm.setSeverityId(severity.getId()); } // 담당부서를 IssueForm 에 저장한다. private void setIssueFormAssignee(Cell cell, Map userMaps, IssueForm issueForm, Project project) { if (cell != null) { String[] splitAssignee = CommonUtil.convertExcelStringToCell(cell).split("#"); Map userMap = (Map) MapUtil.getObject(userMaps, project.getProjectKey()); List departmentIds = Lists.newArrayList(); for (String account : splitAssignee) { if (MapUtil.getLong(userMap, account) != null) { departmentIds.add(MapUtil.getLong(userMap, account)); } } issueForm.setDepartmentIds(departmentIds); } } // 시작일, 종료일을 IssueForm 에 저장한다. private void setIssueFormPeriod(Cell cell, IssueForm issueForm, Boolean checkStartDate, int rowIndex) { if (cell != null && !cell.toString().equals("")) { Date startDate; try { startDate = cell.getDateCellValue(); } catch (Exception e) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_PERIOD_NOT_VALIDITY_EMPTY, rowIndex)); } if (checkStartDate) { issueForm.setStartDate(DateUtil.convertDateToStr(startDate, "yyyy-MM-dd")); } else { issueForm.setCompleteDate(DateUtil.convertDateToStr(startDate, "yyyy-MM-dd")); try { // 날짜 유효성 체크 this.checkStartCompleteDate(issueForm.getStartDate(), issueForm.getCompleteDate()); } catch (OwlRuntimeException e) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_PERIOD_NOT_VALIDITY, rowIndex)); } } } } // 사용자 정의 필드 정보를 IssueForm 에 저장한다. private void setIssueFormCustomFieldValue(Cell cell, Map customFieldMaps, IssueForm issueForm, String customFieldName, int rowIndex) { if (cell != null) { String cellValue = CommonUtil.convertExcelStringToCell(cell); Map issueCustomFieldMap = new HashMap<>(); CustomField customField = customFieldMaps.get(customFieldName); if (customField == null) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_HEADER_CUSTOM_FIELD_NOT_EXIST, rowIndex)); } // 사용자 정의 필드 값이 공백이면 중지 if (StringUtils.isEmpty(cellValue)) { return; } boolean validity = false; switch (customField.getCustomFieldType()) { case INPUT: case NUMBER: case DATETIME: case IP_ADDRESS: case EMAIL: case SITE: case TEL: if (cellValue.length() > 100) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_TEXT_TYPE_MAX_LENGTH_OUT)); } issueCustomFieldMap.put("customFieldId", customField.getId()); issueCustomFieldMap.put("useValue", cellValue); issueForm.addIssueCustomFields(issueCustomFieldMap); break; case SINGLE_SELECT: // 값 유효성 체크 for (CustomFieldValue customFieldValue : customField.getCustomFieldValues()) { if (customFieldValue.getValue().equals(cellValue)) { validity = true; break; } } if (!validity) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.EXCEL_CUSTOM_FIELD_VALUE_NOT_VALIDITY, rowIndex)); } issueCustomFieldMap.put("customFieldId", customField.getId()); issueCustomFieldMap.put("useValue", cellValue); issueForm.addIssueCustomFields(issueCustomFieldMap); break; case MULTI_SELECT: // 값 유효성 체크 String[] useValues = cellValue.split("#"); // 해, 달 for (String useValue : useValues) { for (CustomFieldValue customFieldValue : customField.getCustomFieldValues()) { if (customFieldValue.getValue().equals(useValue)) { validity = true; Map multiValueMap = new HashMap<>(); multiValueMap.put("customFieldId", customField.getId()); multiValueMap.put("useValue", useValue); issueForm.addIssueCustomFields(multiValueMap); } } } if (!validity) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.EXCEL_CUSTOM_FIELD_VALUE_NOT_VALIDITY, rowIndex)); } break; } } } // 프로젝트에 있는 모든 이슈 정보를 조회한다. @Override @Transactional(readOnly = true) public List findByProjectId(Long projectId) { List> issueMaps = this.issueMapper.findByProjectId(projectId); List issueIds = Lists.newArrayList(); for (Map issueMap : issueMaps) { Long issueId = MapUtil.getLong(issueMap, "id"); if (issueId != null) { issueIds.add(issueId); } } return issueIds; } // 이슈를 대상자들에게 메일로 발송한다. @Override @Transactional(readOnly = true) public void sendIssueEmail(IssueForm issueForm) { if (issueForm.getSendEmails().size() < 1) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_SEND_USER)); }else if (issueForm.getTemplate() != null){ throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_SELECT_TEMPLATE)); } Issue issue = this.getIssue(issueForm.getId()); Map issueMap = new HashMap<>(); // 이슈 정보를 이메일 전송에 사용하기 위해 Map 형태로 변환한다. this.makeIssueMapToIssue(issue, issueMap); // 발신자 표시 UserVo toUser = this.webAppUtil.getLoginUser(); issueMap.put("toUser", toUser.getName() + "(" + CommonUtil.decryptAES128(toUser.getAccount()) + ")"); // 이슈 링크 String projectKey = issue.getProject().getProjectKey(); Long IssueNumber = issue.getIssueNumber(); String link = this.configuration.getEmailSendUrl() + "/#/issues/issueList?projectKey=" + projectKey + "&issueNumber=" + IssueNumber.toString(); issueMap.put("issueLink", link); issueMap.put("projectLink", link); // 사용자 시스템 기능 사용 정보 수집 log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_ANOTHER_USER_SEND_EMAIL)); this.systemEmailService.directEmail(issueForm.getSendEmails().toArray(new String[issueForm.getSendEmails().size()]), EmailType.ISSUE_SEND, issueMap, null); } // 이슈를 템플릿에 따라 파트너 담당자에게 메일로 발송한다. @Override @Transactional(readOnly = true) public void sendIssueEmailPartners(IssueForm issueForm) { if (issueForm.getSendEmails().size() < 1) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_SEND_USER)); }else if (issueForm.getTemplate() == null){ throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_SELECT_TEMPLATE)); } Issue issue = this.getIssue(issueForm.getId()); Map issueMap = new HashMap<>(); // 이슈 정보를 이메일 전송에 사용하기 위해 Map 형태로 변환한다. this.makeIssueMapToIssue(issue, issueMap); // 발신자 표시 UserVo toUser = this.webAppUtil.getLoginUser(); issueMap.put("toUser", toUser.getName() + "(" + CommonUtil.decryptAES128(toUser.getAccount()) + ")"); // 이슈 링크 String projectKey = issue.getProject().getProjectKey(); Long IssueNumber = issue.getIssueNumber(); String link = this.configuration.getEmailSendUrl() + "/#/issues/issueList?projectKey=" + projectKey + "&issueNumber=" + IssueNumber.toString(); issueMap.put("issueLink", link); issueMap.put("projectLink", link); // 사용자 시스템 기능 사용 정보 수집 log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_ANOTHER_USER_SEND_EMAIL)); StringBuilder sb = new StringBuilder(); if(issueForm.getTemplate().equals(EmailType.ISSUE_SEND_1.toString())){ this.systemEmailService.directEmail(ConvertUtil.ToArray(issueForm.getSendEmails()), EmailType.ISSUE_SEND_1, issueMap, null); }else if(issueForm.getTemplate().equals(EmailType.ISSUE_SEND_2.toString())){ this.systemEmailService.directEmail(ConvertUtil.ToArray(issueForm.getSendEmails()), EmailType.ISSUE_SEND_2, issueMap, null); }else if(issueForm.getTemplate().equals(EmailType.ISSUE_SEND_3.toString())){ this.systemEmailService.directEmail(ConvertUtil.ToArray(issueForm.getSendEmails()), EmailType.ISSUE_SEND_3, issueMap, null); } //메일 전송 이력 남기기 this.issueHistoryService.detectSendIssueMail(IssueHistoryType.SEND, issueForm, sb); this.issueHistoryService.addIssueHistory(issue, IssueHistoryType.SEND, sb.toString()); } // 예약 발생 이슈를 실행한다 @Override @Transactional public void reservationIssue() { List issueReservations = this.issueReservationService.findByIssueReservationTypeNotNull(); Calendar calendar = Calendar.getInstance(); int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); int month = calendar.get(Calendar.MONTH) + 1; int date = calendar.get(Calendar.DATE); for (IssueReservation issueReservation : issueReservations) { switch (issueReservation.getIssueReservationType()) { case DAY: // 이슈를 다시 생성 상태로 변경시킨다. this.occurReservationIssue(issueReservation.getIssue()); break; case WEEK: if (dayOfWeek == Integer.parseInt(issueReservation.getReservation())) { // 이슈를 다시 생성 상태로 변경시킨다. this.occurReservationIssue(issueReservation.getIssue()); } break; case MONTH: if (date == Integer.parseInt(issueReservation.getReservation())) { // 이슈를 다시 생성 상태로 변경시킨다. this.occurReservationIssue(issueReservation.getIssue()); } break; case YEAR: String[] reservations = issueReservation.getReservation().split("-"); if (reservations.length > 1) { if ((Integer.parseInt(reservations[0]) == month) && Integer.parseInt(reservations[1]) == date) { // 이슈를 다시 생성 상태로 변경시킨다. this.occurReservationIssue(issueReservation.getIssue()); } } break; } } } // 이슈를 다시 생성 상태로 변경시킨다. private void occurReservationIssue(Issue issue) { if (!issue.getIssueStatus().getIssueStatusType().equals(IssueStatusType.READY)) { // 이슈를 생성 상태로 변경시킨다. IssueStatus issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(issue.getIssueType().getWorkflow()); if (issueStatus != null) { StringBuilder detectIssueChange = new StringBuilder(); // 예약 발생으로 이슈 상태 변경된 정보를 기록한다. this.issueHistoryService.detectReservationIssueStatus(issue, detectIssueChange, issueStatus); issue.setIssueStatus(issueStatus); this.issueRepository.saveAndFlush(issue); // 이슈 상태 변경 정보를 이력으로 남긴다. this.issueHistoryService.addIssueHistory(issue, IssueHistoryType.MODIFY, detectIssueChange.toString()); // 이슈 버전 생성 this.issueVersionService.addIssueVersion(issue); } } } @Autowired private ProjectMapper projectMapper; @Override @Transactional(readOnly = true) public Map findTask(IssueCondition taskCondition) { StopWatch serviceStart = new StopWatch(); serviceStart.start(); ProjectCondition projectCondition = new ProjectCondition(this.webAppUtil.getLoginUser().getLastProjectId(), this.webAppUtil.getLoginId()); List> checkProjectRole = this.projectMapper.checkIncludeProject(projectCondition); if (checkProjectRole.size() < 1) { throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.PROJECT_NOT_INCLUDE_USER)); } // 이중으로 역할 체크를 해서 참여하지 않은 사용자가 해당 프로젝트에 접근 못하도록 한다. taskCondition.setLoginUserId(this.webAppUtil.getLoginId()); // List> results = this.taskMapper.find(taskCondition); List> results = this.issueMapper.find(taskCondition); serviceStart.stop(); long executeTime = serviceStart.getTime(); Map tasks = new HashMap<>(); // 워크플로우 상태 id 를 키로 만든다. for (Long workflowStatusId : taskCondition.getStatusIds()) { tasks.put(workflowStatusId.toString(), Lists.newArrayList()); } Map taskUserSave = new HashMap<>(); // 전체 task_id를 모은다. for (Map result : results) { Long taskId = MapUtil.getLong(result, "id"); taskCondition.addIssueIds(taskId.toString()); // 각 task_id 별로 사용자 정보를 저장할 공간을 미리 확보한다. taskUserSave.put(taskId.toString(), Lists.newArrayList()); } List> taskDepartments = Lists.newArrayList(); // task 가 하나도 없을 경우에는 조회를 하지 않는다. if (!taskCondition.getIssueIds().isEmpty()) { //taskUsers = this.issueMapper.getAllTaskUser(taskCondition); taskDepartments = this.issueMapper.getAllTaskUser(taskCondition); } // task_id 에 매칭되는 담당자 정보를 준비한다. /*for (Map taskUser : taskUsers) { Long taskId = MapUtil.getLong(taskUser, "taskId"); List userVos = (List)taskUserSave.get(taskId.toString()); userVos.add(ConvertUtil.convertMapToClass(taskUser, UserVo.class)); }*/ for (Map taskDepartment : taskDepartments) { Long taskId = MapUtil.getLong(taskDepartment, "taskId"); List departmentVos = (List)taskUserSave.get(taskId.toString()); departmentVos.add(ConvertUtil.convertMapToClass(taskDepartment, DepartmentVo.class)); } for (Map result : results) { IssueVo taskVo = ConvertUtil.convertMapToClass(result, IssueVo.class); // 중요도 셋팅 if (MapUtil.getLong(result, "priorityId") != null) { PriorityVo priorityVo = new PriorityVo(); priorityVo.setId(MapUtil.getLong(result, "priorityId")); priorityVo.setName(MapUtil.getString(result, "priorityName")); priorityVo.setColor(MapUtil.getString(result, "priorityColor")); taskVo.setPriorityVo(priorityVo); } // 워크플로우 상태 셋팅 if (MapUtil.getLong(result, "workflowStatusId") != null) { WorkflowStatusVo workflowStatusVo = new WorkflowStatusVo(); workflowStatusVo.setId(MapUtil.getLong(result, "workflowStatusId")); workflowStatusVo.setName(MapUtil.getString(result, "workflowStatusName")); workflowStatusVo.setColor(MapUtil.getString(result, "workflowStatusColor")); taskVo.setWorkflowStatusVo(workflowStatusVo); } // 담당자 셋팅 //List userVos = (List)taskUserSave.get(taskVo.getId().toString()); //taskVo.setUserVos(userVos); // 담당부서 세팅 List departmentVos = (List)taskUserSave.get(taskVo.getId().toString()); taskVo.setDepartmentVos(departmentVos); List taskVos = (List)tasks.get(MapUtil.getString(result, "workflowStatusId")); taskVos.add(taskVo); tasks.put(MapUtil.getString(result, "workflowStatusId"), taskVos); } /*serviceStart.stop(); long executeTime = serviceStart.getTime();*/ return tasks; } @Transactional @Override public void modifyParentIssue(IssueForm issueDownForm) { Issue issue = this.getIssue(issueDownForm.getId()); //하위 이슈 Long newParentIssueId = issueDownForm.getParentIssueId(); //변경할 하위이슈의 상위이슈 StringBuilder sb = new StringBuilder(); Issue parentIssue = issue.getParentIssue(); //변경 전 하위이슈의 상위이슈 if(parentIssue != null && parentIssue.getId().equals(newParentIssueId)){ //변경 전 하위이슈의 상위이슈가 존재 할 경우 this.issueHistoryService.detectDownIssues(IssueHistoryType.DELETE, issue, sb); this.issueHistoryService.addIssueHistory(parentIssue, IssueHistoryType.MODIFY, sb.toString()); } if (newParentIssueId != null) { // 추가 할 경우 parentIssue = this.getIssue(newParentIssueId); //상위이슈(myIssue) issue.setParentIssue(parentIssue); //myIssue를 하위이슈의 상위이슈로 set this.issueHistoryService.detectDownIssues(IssueHistoryType.ADD, issue, sb); //issue = 하위이슈 } else{ // 삭제 할 경우 this.issueHistoryService.detectDownIssues(IssueHistoryType.DELETE, issue, sb); issue.setParentIssue(null); } this.issueHistoryService.addIssueHistory(parentIssue, IssueHistoryType.MODIFY, sb.toString()); //parentIssue = myIssue(기록은 현재 상세페이지에 해야하니까) this.issueRepository.saveAndFlush(issue); } @Override public void findPartner(Map resJsonData, Map params) { Long issueTypeId = MapUtil.getLong(params, "issueTypeId"); IssueType issueType = this.issueTypeService.getIssueType(issueTypeId); // 이슈의 이슈유형 객체 Integer using = issueType.getUsePartner() != null ? issueType.getUsePartner().intValue() : 0; // 이슈유형별로 사용중인 업체/ISP/호스팅 값 List usePartnerVos = Lists.newArrayList(); for (Integer usePartner : UsePartner.partners) { //1(업체), 2(ISP), 4(호스팅) UsePartnerVo usePartnerVo = UsePartner.checkUsePartner(using, usePartner); if (usePartnerVo != null) { usePartnerVos.add(usePartnerVo); } resJsonData.put(Constants.RES_KEY_CONTENTS, usePartnerVos); } } }