package kr.wisestone.owl.service.impl; import com.google.common.collect.Lists; import kr.wisestone.owl.common.ExcelConditionCheck; import kr.wisestone.owl.constant.Constants; import kr.wisestone.owl.constant.MsgConstants; import kr.wisestone.owl.domain.*; import kr.wisestone.owl.domain.enumType.IssueStatusType; import kr.wisestone.owl.exception.OwlRuntimeException; import kr.wisestone.owl.mapper.IssueStatusMapper; import kr.wisestone.owl.repository.IssueStatusRepository; import kr.wisestone.owl.service.*; import kr.wisestone.owl.util.ConvertUtil; import kr.wisestone.owl.util.MapUtil; import kr.wisestone.owl.vo.*; import kr.wisestone.owl.web.condition.IssueStatusCondition; import kr.wisestone.owl.web.form.IssueStatusForm; import kr.wisestone.owl.web.view.ExcelView; import org.apache.commons.lang3.StringUtils; 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.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.ui.Model; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import java.util.*; @Service public class IssueStatusServiceImpl extends AbstractServiceImpl> implements IssueStatusService { private static final Logger log = LoggerFactory.getLogger(IssueStatusServiceImpl.class); @Autowired private IssueStatusRepository issueStatusRepository; @Autowired private WorkspaceService workspaceService; @Autowired private WorkflowTransitionService workflowTransitionService; @Autowired private WorkflowDepartmentService workflowDepartmentService; @Autowired private IssueTypeService issueTypeService; @Autowired private IssueStatusMapper issueStatusMapper; @Autowired private IssueService issueService; @Autowired private UserService userService; @Autowired private ExcelView excelView; @Autowired private ExcelConditionCheck excelConditionCheck; @Override protected JpaRepository getRepository() { return this.issueStatusRepository; } // 워크스페이스 생성시 생성되는 이슈 상태 - 프로젝트 유형에 따라 생성되는 이슈 상태가 달라진다. @Override @Transactional public List addDefaultIssueStatus(Workspace workspace) { List issueStatuses = Lists.newArrayList(); issueStatuses.add(new IssueStatus(workspace, this.messageAccessor.message("common.create"), Boolean.TRUE, "#665fff", IssueStatusType.READY, 1L)); // 생성 issueStatuses.add(new IssueStatus(workspace, this.messageAccessor.message("common.progress"), Boolean.TRUE, "#98c220", IssueStatusType.OPEN, 2L)); // 진행 issueStatuses.add(new IssueStatus(workspace, this.messageAccessor.message("common.reProgress"), Boolean.TRUE, "#c940ea", IssueStatusType.OPEN, 3L)); // 재진행 issueStatuses.add(new IssueStatus(workspace, this.messageAccessor.message("common.check"), Boolean.TRUE, "#febd35", IssueStatusType.OPEN, 4L)); // 확인 issueStatuses.add(new IssueStatus(workspace, this.messageAccessor.message("common.end"), Boolean.TRUE, "#888888", IssueStatusType.CLOSE, 5L)); // 종료 issueStatuses.add(new IssueStatus(workspace, this.messageAccessor.message("common.noApproval"), Boolean.TRUE, "#ff5f99", IssueStatusType.CLOSE, 6L)); // 승인 불가 issueStatuses.add(new IssueStatus(workspace, this.messageAccessor.message("common.approval"), Boolean.TRUE, "#3598fe", IssueStatusType.CLOSE, 7L)); // 승인 return this.issueStatusRepository.saveAll(issueStatuses); } // 워크스페이스에 존재하는 이슈 상태 조회 @Override @Transactional(readOnly = true) public List findByWorkspaceId(Long workspaceId) { return this.issueStatusRepository.findByWorkspaceId(workspaceId); } // 이슈 상태를 생성한다. @Override @Transactional public IssueStatus addIssueStatus(IssueStatusForm issueStatusForm) { // 사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다. this.workspaceService.checkUseWorkspace(); // 이름 유효성 체크 this.verifyName(issueStatusForm.getName(), null); // 색상 체크 this.verifyColor(issueStatusForm.getColor()); IssueStatus issueStatus = ConvertUtil.copyProperties(issueStatusForm, IssueStatus.class, "issueStatusType"); issueStatus.setIssueStatusType(IssueStatusType.valueOf(issueStatusForm.getIssueStatusType())); issueStatus.setWorkspace(this.workspaceService.getWorkspace(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId())); issueStatus.setPosition(0L); return this.issueStatusRepository.saveAndFlush(issueStatus); } // 이름 유효성 체크 private void verifyName(String name, Long id) { if (StringUtils.isEmpty(name)) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_NOT_NAME)); } if (name.length() > 20) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_NAME_MAX_LENGTH_OUT)); } IssueStatus issueStatus; Long workspaceId = this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId(); if (id == null) { issueStatus = this.issueStatusRepository.findByNameAndWorkspaceId(name, workspaceId); } else { issueStatus = this.issueStatusRepository.findByNameAndWorkspaceIdAndIdNot(name, workspaceId, id); } if (issueStatus != null) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_USED_NAME)); } } // 색상 체크 private void verifyColor(String color) { if (StringUtils.isEmpty(color)) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_NOT_COLOR)); } } // 이슈 상태 목록을 조회한다. @Override @Transactional(readOnly = true) public List findIssueStatus(Map resJsonData, IssueStatusCondition condition, Pageable pageable) { condition.setPage(pageable.getPageNumber() * pageable.getPageSize()); condition.setPageSize(pageable.getPageSize()); condition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId()); List> results = this.issueStatusMapper.find(condition); Long totalCount = this.issueStatusMapper.count(condition); int totalPage = (int) Math.ceil((totalCount - 1) / pageable.getPageSize()) + 1; List issueStatusVos = ConvertUtil.convertListToListClass(results, IssueStatusVo.class); // 이슈 상태가 워크플로우에 사용되고 있는지 여부를 체크하고 이슈 상태 안에 사용되는 워크플로우 정보를 셋팅한다. if (condition.getDeep() != null) { this.setUseIssueStatusByWorkflow(issueStatusVos); } resJsonData.put(Constants.RES_KEY_CONTENTS, issueStatusVos); resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(), totalPage, totalCount)); return issueStatusVos; } // 이슈 상태 목록을 조회한다 - 페이징 사용x 워크플로우 생성, 수정에서 사용 @Override @Transactional(readOnly = true) public List findIssueStatus(Map resJsonData, IssueStatusCondition condition) { condition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId()); List> results = this.issueStatusMapper.find(condition); List issueStatusVos = ConvertUtil.convertListToListClass(results, IssueStatusVo.class); // issueStatusType 별로 정렬 Collections.sort(issueStatusVos); // ready 상태가 앞으로 오도록 다시 정렬 Collections.reverse(issueStatusVos); resJsonData.put(Constants.RES_KEY_CONTENTS, issueStatusVos); return issueStatusVos; } // 이슈 상태를 사용하고 있는 워크플로우 정보를 셋팅한다. private void setUseIssueStatusByWorkflow(List issueStatusVos) { for (IssueStatusVo issueStatusVo : issueStatusVos) { Map workflows = new HashMap<>(); IssueStatus issueStatus = this.getIssueStatus(issueStatusVo.getId()); this.findUseIssueStatusByWorkflow(issueStatus.getSourceWorkflowTransitions().iterator(), workflows); this.findUseIssueStatusByWorkflow(issueStatus.getTargetWorkflowTransitions().iterator(), workflows); for (String key : workflows.keySet()) { issueStatusVo.addWorkflowVos((WorkflowVo) workflows.get(key)); } } } // 이슈 상태를 사용하고 있는 워크플로우를 찾는다. private void findUseIssueStatusByWorkflow(Iterator iterator, Map workflows) { while (iterator.hasNext()) { WorkflowTransition workflowTransition = iterator.next(); workflows.put(workflowTransition.getWorkflow().getId().toString(), ConvertUtil.copyProperties(workflowTransition.getWorkflow(), WorkflowVo.class)); } } // 이슈의 현재 상태에서 이동 가능한 다음 상태 정보 목록을 찾는다. @Override @Transactional(readOnly = true) public void findNextIssueStatus(Map resJsonData, IssueStatusCondition condition) { IssueType issueType = this.issueTypeService.getIssueType(condition.getIssueTypeId()); IssueStatus issueStatus = this.getIssueStatus(condition.getId()); Workflow workflow = issueType.getWorkflow(); List workflowTransitionVos = this.workflowTransitionService.findBySourceIssueStatusIdAndWorkflowId(issueStatus.getId(), workflow.getId()); List issueStatusVos = Lists.newArrayList(); for (WorkflowTransitionVo workflowTransitionVo : workflowTransitionVos) { IssueStatusVo issueStatusVo = new IssueStatusVo(); issueStatusVo.setId(workflowTransitionVo.getTargetStatusId()); issueStatusVo.setName(workflowTransitionVo.getTargetStatusName()); issueStatusVos.add(issueStatusVo); } resJsonData.put(Constants.RES_KEY_CONTENTS, issueStatusVos); } // 이슈 상태 상세 정보를 조회한다. @Override @Transactional(readOnly = true) public void detailIssueStatus(Map resJsonData, IssueStatusCondition issueStatusCondition) { IssueStatusVo issueStatusVo = new IssueStatusVo(); if (issueStatusCondition.getId() != null) { IssueStatus issueStatus = this.getIssueStatus(issueStatusCondition.getId()); issueStatusVo = ConvertUtil.copyProperties(issueStatus, IssueStatusVo.class); issueStatusVo.setIssueStatusType(issueStatus.getIssueStatusType().toString()); if (issueStatusCondition.getDeep().equals("01")) { int useCount = issueStatus.getSourceWorkflowTransitions().size() + issueStatus.getTargetWorkflowTransitions().size(); if (useCount > 0) { issueStatusVo.setUseYn(true); } } } resJsonData.put(Constants.RES_KEY_CONTENTS, issueStatusVo); } // 이슈 상태 정보를 수정한다. @Override @Transactional public IssueStatus modifyIssueStatus(IssueStatusForm issueStatusForm) { // 사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다. this.workspaceService.checkUseWorkspace(); // 이름 유효성 체크 this.verifyName(issueStatusForm.getName(), issueStatusForm.getId()); // 색상 체크 this.verifyColor(issueStatusForm.getColor()); IssueStatus issueStatus = this.getIssueStatus(issueStatusForm.getId()); ConvertUtil.copyProperties(issueStatusForm, issueStatus, "id", "issueStatusType"); issueStatus.setIssueStatusType(IssueStatusType.valueOf(issueStatusForm.getIssueStatusType())); return this.issueStatusRepository.saveAndFlush(issueStatus); } // 이슈 상태 아이디로 이슈 상태를 조회한다. @Override @Transactional(readOnly = true) public IssueStatus getIssueStatus(Long id) { if (id == null) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_NOT_EXIST)); } IssueStatus issueStatus = this.findOne(id); if (issueStatus == null) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_NOT_EXIST)); } return issueStatus; } // 이슈 상태를 삭제한다. @Override @Transactional public void removeIssueStatus(IssueStatusForm issueStatusForm) { // 사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다. this.workspaceService.checkUseWorkspace(); if (issueStatusForm.getRemoveIds().size() < 1) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_REMOVE_NOT_SELECT)); } for (Long issueStatusId : issueStatusForm.getRemoveIds()) { this.removeIssueStatuses(issueStatusId); } this.issueStatusRepository.flush(); } private void removeIssueStatuses(Long issueStatusId) { IssueStatus issueStatus = this.getIssueStatus(issueStatusId); // 삭제할 이슈 상태가 이슈 에서 사용되고 있는지 확인한다. this.checkUseIssueStatus(issueStatus); // 삭제할 이슈 상태가 워크플로우 에서 사용되고 있는지 확인한다. this.checkUseWorkflow(issueStatus); // 기본으로 제공되는 이슈 상태는 삭제 금지 if (issueStatus.getDefaultYn()) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.DEFAULT_ISSUE_STATUS_NOT_REMOVE)); } this.issueStatusRepository.delete(issueStatus); } // 삭제할 이슈 상태가 이슈 에서 사용되고 있는지 확인한다. private void checkUseIssueStatus(IssueStatus issueStatus) { // 이슈 유형을 사용하는 이슈 갯수를 조회한다. long issueCount = this.issueService.countByIssueStatus(issueStatus.getId()); if (issueCount > 0) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_USE_ISSUES)); } } // 삭제할 이슈 상태가 워크플로우 에서 사용되고 있는지 확인한다. private void checkUseWorkflow(IssueStatus issueStatus) { List issueStatusVos = Lists.newArrayList(ConvertUtil.copyProperties(issueStatus, IssueStatusVo.class)); // 이슈 상태를 사용하고 있는 워크플로우 정보를 셋팅한다. this.setUseIssueStatusByWorkflow(issueStatusVos); for (IssueStatusVo issueStatusVo : issueStatusVos) { if (issueStatusVo.getWorkflowVos().size() > 0) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_USE_WORKFLOW)); } } } // 워크플로우에 있는 이슈 상태를 조회한다. @Override @Transactional(readOnly = true) public List findByWorkflowId(Long workflowId) { List workflowTransitions = this.workflowTransitionService.findByWorkflowId(workflowId); Map issueStatuses = new HashMap<>(); List issueStatusVos = Lists.newArrayList(); for (WorkflowTransition workflowTransition : workflowTransitions) { IssueStatus source = workflowTransition.getSourceIssueStatus(); IssueStatus target = workflowTransition.getTargetIssueStatus(); // 대상 이슈 상태가 이미 저장되어 있는지 확인한다. - source 항목 if (source != null) { this.setIssueStatusLocation(issueStatuses, source, workflowTransition.getSourceX(), workflowTransition.getSourceY()); } // 대상 이슈 상태가 이미 저장되어 있는지 확인한다. - target 항목 if (target != null) { this.setIssueStatusLocation(issueStatuses, target, workflowTransition.getTargetX(), workflowTransition.getTargetY()); } } Iterator iterator = issueStatuses.keySet().iterator(); while (iterator.hasNext()) { IssueStatusVo issueStatusVo = (IssueStatusVo) issueStatuses.get(iterator.next()); issueStatusVo.setWorkflowTransitionVos(this.workflowTransitionService.findBySourceIssueStatusIdAndWorkflowId(issueStatusVo.getId(), workflowId)); issueStatusVos.add(issueStatusVo); } return issueStatusVos; } private void setIssueStatusLocation(Map issueStatuses, IssueStatus issueStatus, Long xLocation, Long yLocation) { Object targetIssueStatus = MapUtil.getObject(issueStatuses, issueStatus.getId().toString()); if (targetIssueStatus == null) { IssueStatusVo issueStatusVo = ConvertUtil.copyProperties(issueStatus, IssueStatusVo.class); issueStatusVo.setxLocation(xLocation); issueStatusVo.setyLocation(yLocation); issueStatusVo.setIssueStatusType(issueStatus.getIssueStatusType().toString()); issueStatuses.put(issueStatus.getId().toString(), issueStatusVo); } } // 이슈 상태 속성이 대기인 이슈 상태를 가져온다. @Override @Transactional(readOnly = true) public IssueStatus findByIssueStatusTypeIsReady(Workflow workflow) { List issueStatusVos = this.findByWorkflowId(workflow.getId()); IssueStatus issueStatus = null; for (IssueStatusVo issueStatusVo : issueStatusVos) { if (IssueStatusType.READY.equals(IssueStatusType.valueOf(issueStatusVo.getIssueStatusType()))) { issueStatus = this.getIssueStatus(issueStatusVo.getId()); break; } } if (issueStatus == null) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.READY_ISSUE_STATUS_NOT_EXIST)); } return issueStatus; } // 이슈 상태를 변경할 때 선택한 이슈 상태로 변경할 수 있는지 확인한다. @Override @Transactional(readOnly = true) public void checkNextIssueStatus(Issue issue, IssueStatus nextIssueStatus) { Workflow workflow = issue.getIssueType().getWorkflow(); boolean passNextIssueStatus = false; List workflowTransitionVos = this.workflowTransitionService.findBySourceIssueStatusIdAndWorkflowId(issue.getIssueStatus().getId(), workflow.getId()); for (WorkflowTransitionVo workflowTransitionVo : workflowTransitionVos) { if (workflowTransitionVo.getTargetStatusId().equals(nextIssueStatus.getId())) { passNextIssueStatus = true; break; } } if (!passNextIssueStatus) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_CHANGE_NOT_TARGET)); } } // 이슈 상태 목록을 엑셀로 다운로드 한다. @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; } IssueStatusCondition issueStatusCondition = IssueStatusCondition.make(conditions); issueStatusCondition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId()); List> results = this.issueStatusMapper.find(issueStatusCondition); List issueStatusVos = ConvertUtil.convertListToListClass(results, IssueStatusVo.class); // 이슈 상태가 워크플로우에 사용되고 있는지 여부를 체크하고 이슈 상태 안에 사용되는 워크플로우 정보를 셋팅한다. if (issueStatusCondition.getDeep() != null) { this.setUseIssueStatusByWorkflow(issueStatusVos); } ExportExcelVo excelInfo = new ExportExcelVo(); excelInfo.setFileName(this.messageAccessor.message("common.issueStatusList")); // 이슈 상태 목록 excelInfo.addAttrInfos(new ExportExcelAttrVo("name", this.messageAccessor.message("common.issueStatus"), 10, ExportExcelAttrVo.ALIGN_LEFT)); // 이슈 상태 excelInfo.addAttrInfos(new ExportExcelAttrVo("issueStatusType", this.messageAccessor.message("common.statusProperties"), 6, ExportExcelAttrVo.ALIGN_CENTER)); // 상태 속성 excelInfo.addAttrInfos(new ExportExcelAttrVo("color", this.messageAccessor.message("common.color"), 6, ExportExcelAttrVo.ALIGN_CENTER)); // 색상 excelInfo.addAttrInfos(new ExportExcelAttrVo("workflowNames", this.messageAccessor.message("common.workflow"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 워크플로우 // 엑셀에 넣을 데이터 - issueStatusVos 데이터를 엑셀에서 표시할 수 있는 데이터로 변경한다. excelInfo.setDatas(this.convertExcelViewToIssueStatusVos(issueStatusVos)); model.addAttribute(Constants.EXCEL, excelInfo); return new ModelAndView(this.excelView); } // IssueStatusVo 데이터를 엑셀에서 표시할 수 있는 데이터로 변경한다. private List> convertExcelViewToIssueStatusVos(List issueStatusVos) { List> results = Lists.newArrayList(); for (IssueStatusVo issueStatusVo : issueStatusVos) { Map result = new HashMap<>(); result.put("name", issueStatusVo.getName()); String issueStatusType = ""; switch (issueStatusVo.getIssueStatusType()) { case "READY": issueStatusType = this.messageAccessor.message("common.wait"); // 대기 break; case "OPEN": issueStatusType = this.messageAccessor.message("common.progress"); // 진행 break; case "CLOSE": issueStatusType = this.messageAccessor.message("common.end"); // 종료 break; } result.put("issueStatusType", issueStatusType); result.put("color", issueStatusVo.getColor()); StringBuilder stringBuilder = new StringBuilder(); for (WorkflowVo workflowVo : issueStatusVo.getWorkflowVos()) { stringBuilder.append(workflowVo.getName()); stringBuilder.append("\n"); } result.put("workflowNames", stringBuilder.toString()); results.add(result); } return results; } // 여러건의 이슈의 현재 상태에서 이동 가능한 다음 상태 정보 목록을 찾는다. @Override @Transactional(readOnly = true) public void findNextMultiIssueStatus(Map resJsonData, IssueStatusCondition issueStatusCondition) { List issueStatusVos = Lists.newArrayList(); int count = 0; // 워크플로우에서 이동 가능한 상태 추출 for (Long issueId : issueStatusCondition.getIssueIds()) { Issue issue = this.issueService.getIssue(issueId); Workflow workflow = issue.getIssueType().getWorkflow(); List workflowTransitionVos = this.workflowTransitionService.findBySourceIssueStatusIdAndWorkflowId(issue.getIssueStatus().getId(), workflow.getId()); List tempIssueStatusVos = Lists.newArrayList(); for (WorkflowTransitionVo workflowTransitionVo : workflowTransitionVos) { // 첫번째 이슈에서 이동 가능한 상태 정보를 저장한다. if (count < 1) { IssueStatusVo issueStatusVo = new IssueStatusVo(workflowTransitionVo.getTargetStatusId(), workflowTransitionVo.getTargetStatusName()); issueStatusVos.add(issueStatusVo); List workflowDepartments = this.workflowDepartmentService.find(workflow.getId(), issueStatusVo.getId()); issueStatusVo.setWorkflowDepartmentVos(workflowDepartments); } else { // 두번째 이슈부터 첫번째 이슈에서 이동 가능했던 상태 중 없는 대상을 찾는다. for (IssueStatusVo issueStatusVo : issueStatusVos) { if (issueStatusVo.getId().equals(workflowTransitionVo.getTargetStatusId())) { tempIssueStatusVos.add(issueStatusVo); List workflowDepartments = this.workflowDepartmentService.find(workflow.getId(), issueStatusVo.getId()); issueStatusVo.setWorkflowDepartmentVos(workflowDepartments); } } } } if (count > 0) { // 비교한 후 존재하는 이슈 상태만 저장 issueStatusVos = tempIssueStatusVos; } count++; } resJsonData.put(Constants.RES_KEY_CONTENTS, issueStatusVos); } }