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.CustomField; import kr.wisestone.owl.domain.IssueCustomFieldValue; import kr.wisestone.owl.domain.Workspace; import kr.wisestone.owl.domain.enumType.CustomFieldType; import kr.wisestone.owl.exception.OwlRuntimeException; import kr.wisestone.owl.mapper.CustomFieldMapper; import kr.wisestone.owl.repository.CustomFieldRepository; import kr.wisestone.owl.repository.IssueTypeCustomFieldRepository; 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.CustomFieldCondition; import kr.wisestone.owl.web.form.CustomFieldForm; 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.HashMap; import java.util.List; import java.util.Map; @Service public class CustomFieldServiceImpl extends AbstractServiceImpl> implements CustomFieldService { private static final Logger log = LoggerFactory.getLogger(CustomFieldServiceImpl.class); public enum UseType { Y, N, } @Autowired private CustomFieldRepository customFieldRepository; @Autowired private IssueTypeCustomFieldRepository issueTypeCustomFieldRepository; @Autowired private CustomFieldMapper customFieldMapper; @Autowired private WorkspaceService workspaceService; @Autowired private CustomFieldValueService customFieldValueService; @Autowired private IssueCustomFieldValueService issueCustomFieldValueService; @Autowired private UserService userService; @Autowired private ExcelView excelView; @Autowired private ExcelConditionCheck excelConditionCheck; @Override protected JpaRepository getRepository() { return this.customFieldRepository; } // 사용자 정의 필드를 생성한다. @Override @Transactional public CustomField addCustomField(CustomFieldForm customFieldForm) { // 사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다. this.workspaceService.checkUseWorkspace(); // 이름 유효성 체크 this.verifyName(customFieldForm.getName(), null); // 옵션 값 유효 체크 this.verifyOptions(customFieldForm); CustomField customField = ConvertUtil.copyProperties(customFieldForm, CustomField.class, "customFieldType"); customField.setUse("Y"); Workspace workspace = this.workspaceService.getWorkspace(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId()); customField.setWorkspace(workspace); customField.setCustomFieldType(CustomFieldType.valueOf(customFieldForm.getCustomFieldType())); // 사용자 정의 필드 유형을 셋팅한다. this.customFieldRepository.saveAndFlush(customField); this.customFieldValueService.addCustomFieldValues(customField, customFieldForm.getOptions(), null); // 사용자 정의 필드 값 추가 return customField; } // 이름 유효성 체크 private void verifyName(String name, Long id) { if (StringUtils.isEmpty(name)) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_NOT_NAME)); } if (name.length() > 15) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_NAME_MAX_LENGTH_OUT)); } CustomField customField; Long workspaceId = this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId(); if (id == null) { customField = this.customFieldRepository.findByNameAndWorkspaceId(name, workspaceId); } else { customField = this.customFieldRepository.findByNameAndWorkspaceIdAndIdNot(name, workspaceId, id); } if (customField != null) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_USED_NAME)); } } // 옵션 값 유효 체크 private void verifyOptions(CustomFieldForm customFieldForm) { // 사용자 정의 필드 유형이 텍스트 일때는 옵션 값이 존재하면 안된다. => 단일선택 또는 다중선택 필드가 아닐 경우 옵션 값 존재 하면 안되는 것으로 변경 if (!CustomFieldType.valueOf(customFieldForm.getCustomFieldType()).equals(CustomFieldType.MULTI_SELECT) && !CustomFieldType.valueOf(customFieldForm.getCustomFieldType()).equals(CustomFieldType.SINGLE_SELECT)) { if (customFieldForm.getOptions().size() > 0) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_OPTIONS_NOT_USE_INPUT_FIELD)); } if(customFieldForm.getDefaultValue().length() > 100) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_DEFAULT_VALUE_MAX_LENGTH_OUT)); } } if (customFieldForm.getOptions().size() > 0) { // 사용자 정의 필드 옵션 값이 null 이거나 공백인 경우를 찾는다. for (String option : customFieldForm.getOptions()) { if (StringUtils.isEmpty(option)) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_OPTIONS_NOT_EMPTY_VALUE)); } // 옵션 값 최대 길이 체크 if (option.length() > 15) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_OPTION_VALUE_MAX_LENGTH_OUT)); } } // 사용자 정의 필드 기본 값이 옵션에 존재하지 않을 경우를 찾는다. if (!StringUtils.isEmpty(customFieldForm.getDefaultValue())) { switch (CustomFieldType.valueOf(customFieldForm.getCustomFieldType())) { case SINGLE_SELECT: if (customFieldForm.getDefaultValue().split("#").length > 2) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_OPTIONS_NOT_USE_MULTI_DEFAULT_VALUE)); } // 기본 값으로 지정한 값이 옵션에 있는지 확인한다. this.verifyOptionsDefaultValue(customFieldForm); break; case MULTI_SELECT: // 기본 값으로 지정한 값이 옵션에 있는지 확인한다. this.verifyOptionsDefaultValue(customFieldForm); break; } } } } // 기본 값으로 지정한 값이 옵션에 있는지 확인한다. private void verifyOptionsDefaultValue(CustomFieldForm customFieldForm) { // 중복된 옵션 값 찾기 Map optionValueDictionary = new HashMap<>(); for (String defaultValue : customFieldForm.getDefaultValue().split("#")) { if (!StringUtils.isEmpty(defaultValue)) { if (!customFieldForm.getOptions().contains(defaultValue)) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_OPTIONS_NOT_EXIST_DEFAULT_VALUE)); } if (MapUtil.getBoolean(optionValueDictionary, defaultValue) == null) { optionValueDictionary.put(defaultValue, true); } else { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_OPTIONS_USED_EXIST_DEFAULT_VALUE)); } } } } // 사용자 정의 필드 목록을 조회한다. @Override @Transactional(readOnly = true) public List findCustomField(Map resJsonData, CustomFieldCondition 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.customFieldMapper.find(condition); Long totalCount = this.customFieldMapper.count(condition); int totalPage = (int) Math.ceil((totalCount - 1) / pageable.getPageSize()) + 1; // 사용자 정의 필드 조회 결과를 CustomFieldVo 로 변환한다. List customFieldVos = this.makeCustomFieldVos(results); resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(), totalPage, totalCount)); resJsonData.put(Constants.RES_KEY_CONTENTS, customFieldVos); return customFieldVos; } // 사용자 정의 필드 조회 결과를 CustomFieldVo 로 변환한다. private List makeCustomFieldVos(List> results) { List customFieldVos = Lists.newArrayList(); for (Map result : results) { CustomFieldVo customFieldVo = ConvertUtil.convertMapToClass(result, CustomFieldVo.class); CustomField customField = this.getCustomField(customFieldVo.getId()); customFieldVo.setCustomFieldValueVos(ConvertUtil.convertObjectsToClasses(customField.getCustomFieldValues(), CustomFieldValueVo.class)); customFieldVos.add(customFieldVo); } return customFieldVos; } // 사용자 정의 필드 상세 정보를 조회한다. @Override @Transactional(readOnly = true) public void detailCustomField(Map resJsonData, CustomFieldCondition customFieldCondition) { CustomFieldVo customFieldVo = new CustomFieldVo(); if (customFieldCondition.getId() != null) { CustomField customField = this.getCustomField(customFieldCondition.getId()); customFieldVo = ConvertUtil.copyProperties(customField, CustomFieldVo.class); customFieldVo.setCustomFieldType(customField.getCustomFieldType().toString()); switch (customFieldCondition.getDeep()) { case "01": // 사용자 정의 필드 옵션 값을 가져온다. customFieldVo.setCustomFieldValueVos(ConvertUtil.convertObjectsToClasses(customField.getCustomFieldValues(), CustomFieldValueVo.class)); // 사용자 정의 필드 옵션 값이 이슈에서 사용되고 있는지 확인한 후 플래그 값을 설정한다. List issueCustomFieldValues = this.issueCustomFieldValueService.findByCustomFieldId(customField); if (issueCustomFieldValues.size() > 0) { customFieldVo.setUseCustomFieldValue(true); } break; } } resJsonData.put(Constants.RES_KEY_CONTENTS, customFieldVo); } // 사용자 정의 필드를 수정한다. @Override @Transactional public CustomField modifyCustomField(CustomFieldForm customFieldForm) { // 사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다. this.workspaceService.checkUseWorkspace(); CustomField customField = this.getCustomField(customFieldForm.getId()); CustomFieldType oldCustomFieldType = customField.getCustomFieldType(); // 이름 유효성 체크 this.verifyName(customFieldForm.getName(), customFieldForm.getId()); // 옵션 값 유효 체크 this.verifyOptions(customFieldForm); // 사용자 정의 필드 유형이 단일, 다중 선택에서 문자열로 변경될 경우 사용자 정의 필드 값을 초기화한다. this.checkChangeCustomFieldType(customFieldForm, customField); ConvertUtil.copyProperties(customFieldForm, customField, "id", "customFieldType"); customField.setCustomFieldType(CustomFieldType.valueOf(customFieldForm.getCustomFieldType())); this.customFieldRepository.saveAndFlush(customField); this.customFieldValueService.addCustomFieldValues(customField, customFieldForm.getOptions(), oldCustomFieldType); // 사용자 정의 필드 옵션 값 추가 return customField; } // 사용자 정의 필드 유형이 단일, 다중 선택에서 문자열로 변경될 경우 사용자 정의 필드 값을 초기화한다. private void checkChangeCustomFieldType(CustomFieldForm customFieldForm, CustomField customField) { if (customField.getCustomFieldType().equals(CustomFieldType.MULTI_SELECT) || customField.getCustomFieldType().equals(CustomFieldType.SINGLE_SELECT)) { // 단일, 다중 선택에서 문자열 필드로 변경된 경우 if (!CustomFieldType.valueOf(customFieldForm.getCustomFieldType()).equals(CustomFieldType.MULTI_SELECT) && !CustomFieldType.valueOf(customFieldForm.getCustomFieldType()).equals(CustomFieldType.SINGLE_SELECT)) { customField.getCustomFieldValues().clear(); } } } @Override @Transactional(readOnly = true) public CustomField getCustomField(Long id) { if (id == null) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_NOT_EXIST)); } CustomField customField = this.findOne(id); if (customField == null) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_NOT_EXIST)); } return customField; } // 사용자 정의 필드를 삭제한다. @Override @Transactional public void removeCustomFields(CustomFieldForm customFieldForm) { // 사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다. this.workspaceService.checkUseWorkspace(); List removeIds = customFieldForm.getRemoveIds(); if (removeIds.size() < 1) { throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_REMOVE_NOT_SELECT)); }else { for (Long id : removeIds) { Long count = this.issueTypeCustomFieldRepository.countByCustomFieldId(id); if (count > 0){ throw new OwlRuntimeException( this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_USED_DATA_REMOVE)); } } } for (Long projectId : customFieldForm.getRemoveIds()) { this.removeCustomFields(projectId); } // this.customFieldRepository.flush(); this.customFieldRepository.flush(); } private void removeCustomFields(Long customFieldId) { CustomField customField = this.getCustomField(customFieldId); customField.setUse(UseType.N.toString()); this.customFieldRepository.saveAndFlush(customField); // this.customFieldRepository.delete(customField); this.customFieldRepository.delete(customField); } // 이슈 엑셀 import 에서 사용한다. @Override @Transactional(readOnly = true) public CustomField findByName(String name) { return this.customFieldRepository.findByNameAndWorkspaceId(name, this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId()); } // 사용자 정의 필드 목록을 엑셀로 다운로드 한다. @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; } CustomFieldCondition customFieldCondition = CustomFieldCondition.make(conditions); customFieldCondition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId()); List> results = this.customFieldMapper.find(customFieldCondition); for (Map result : results) { String customFieldType = MapUtil.getString(result, "customFieldType"); String customFieldTypeName = ""; if (customFieldType != null) { switch(customFieldType) { case "INPUT" : customFieldTypeName = this.messageAccessor.message("common.stringField"); // 문자열 필드 break; case "SINGLE_SELECT" : customFieldTypeName = this.messageAccessor.message("common.singleSelectionField"); // 단일 선택 필드 break; case "MULTI_SELECT" : customFieldTypeName = this.messageAccessor.message("common.multipleSelectionField"); // 다중 선택 필드 break; case "NUMBER" : customFieldTypeName = this.messageAccessor.message("common.numberField"); // 숫자 필드 break; case "DATETIME" : customFieldTypeName = this.messageAccessor.message("common.datetimeField"); // 날짜 필드 break; case "IP_ADDRESS" : customFieldTypeName = this.messageAccessor.message("common.ipAddressField"); // IP 주소 필드 break; case "EMAIL" : customFieldTypeName = this.messageAccessor.message("common.emailField"); // 이메일 필드 break; case "SITE" : customFieldTypeName = this.messageAccessor.message("common.siteField"); // 홈페이지 주소 필드 break; case "TEL" : customFieldTypeName = this.messageAccessor.message("common.telField"); // 전화번호 필드 break; } } result.put("customFieldTypeName", customFieldTypeName); } ExportExcelVo excelInfo = new ExportExcelVo(); excelInfo.setFileName(this.messageAccessor.message("common.customFieldList")); // 사용자 정의 필드 목록 excelInfo.addAttrInfos(new ExportExcelAttrVo("name", this.messageAccessor.message("common.customField"), 15, ExportExcelAttrVo.ALIGN_LEFT)); // 사용자 정의 필드 excelInfo.addAttrInfos(new ExportExcelAttrVo("customFieldTypeName", this.messageAccessor.message("common.fieldType"), 6, ExportExcelAttrVo.ALIGN_CENTER)); // 필드 유형 // 엑셀에 넣을 데이터 excelInfo.setDatas(results); model.addAttribute(Constants.EXCEL, excelInfo); return new ModelAndView(this.excelView); } // 업무 공간에 있는 모든 사용자 정의 필드을 조회한다. 이슈 엑셀 import 에서 사용 @Override @Transactional(readOnly = true) public List findByWorkspaceId() { return this.customFieldRepository.findByWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId()); } }