OWL ITS + 탐지시스템(인터넷 진흥원)
이민희
2021-12-27 70e3ad43e441d07d16c7e4e1af0e093befd62c01
Merge branch 'master' of http://192.168.0.25:9001/r/owl-kisa
7개 파일 변경됨
355 ■■■■■ 파일 변경됨
src/main/java/kr/wisestone/owl/domain/Issue.java 4 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/IssueService.java 2 ●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/IssueServiceImpl.java 307 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/IssueController.java 2 ●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/i18n/ko/global.json 1 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/scripts/app/issue/issueImportExcel.controller.js 22 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/views/issue/issueExcelImport.html 17 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/Issue.java
@@ -217,6 +217,10 @@
        this.issueDepartments = issueDepartments;
    }
    public void addIssueDepartment(IssueDepartment issueDepartment) {
        this.issueDepartments.add(issueDepartment);
    }
    public Set<AttachedFile> getAttachedFiles() {
        return attachedFiles;
    }
src/main/java/kr/wisestone/owl/service/IssueService.java
@@ -87,7 +87,7 @@
    ModelAndView downloadExcelTemplate(HttpServletRequest request, Model model);
    void importExcel(MultipartFile multipartFile) throws Exception;
    void importExcel(IssueForm issueForm, MultipartFile multipartFile) throws Exception;
    List<Long> findByProjectId(Long projectId);
src/main/java/kr/wisestone/owl/service/impl/IssueServiceImpl.java
@@ -187,9 +187,6 @@
    private UserDepartmentService userDepartmentService;
    @Autowired
    private WorkflowTransitionService workflowTransitionService;
    @Autowired
    private UserDepartmentRepository userDepartmentRepository;
    @Autowired
@@ -197,9 +194,6 @@
    @Autowired
    private WorkflowDepartmentRepository workflowDepartmentRepository;
    @Autowired
    WorkflowService workflowService;
    @Override
    protected JpaRepository<Issue, Long> getRepository() {
@@ -240,6 +234,7 @@
        }
        Workflow workflow = issueType.getWorkflow();
        if (issueApiForm.getApiType().equals(IssueApiForm.ApiType.add)) {
            // 이슈 상태가 지정되어 있지 않을 경우 워크플로우 대기 상태 값으로 지정
            List<Long> departmentIds = this.workflowDepartmentService.findFirstDepartmentIds(workflow);
@@ -248,15 +243,8 @@
                    issueForm.addDepartmentId(departmentId);
                }
            }
        } else {
            if (issueApiForm.getIssueStatusId() == null){
                throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_ISSUE_STATUS_IS_NULL));
            }
            // 워크플로우에서 사용 중인 이슈 상태인지 체크
            else if (!this.workflowTransitionService.contains(issueApiForm.getIssueStatusId(), workflow.getId())) {
                throw new OwlRuntimeException(
                        this.messageAccessor.getMessage(MsgConstants.API_ISSUE_STATUS_NOT_IN_WORKFLOW));
            }
        } else if (issueApiForm.getIssueStatusId() == null){
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_ISSUE_STATUS_NOT_EXIST));
        }
        // 프로젝트 입력
@@ -292,20 +280,6 @@
            if (customFieldApiOverlaps != null && customFieldApiOverlaps.size() > 0) {
                for (int i = 0; i < customFieldApiOverlaps.size(); i++) {
                    CustomFieldApiOverlap customFieldApiOverlap = customFieldApiOverlaps.get(i);
                    CustomField customField = customFieldApiOverlap.getCustomField();
                    if (customField.getCustomFieldType() == CustomFieldType.SITE) {
                        for (Map<String, Object> map : issueApiForm.getCustomFieldValues()) {
                            if (customField.getId().equals(MapUtil.getLong(map, "customFieldId"))) {
                                String fullUrl = MapUtil.getString(map, "useValue");
                                String url = CommonUtil.getUrl(fullUrl);
                                if (!url.equals("")) {
                                    issueApiForm.setUrl(fullUrl);
                                    break;
                                }
                            }
                        }
                    }
                    issueApiForm.addUseIssueCustomFieldId(customFieldApiOverlap.getCustomField().getId());
                }
@@ -327,7 +301,7 @@
            // 사용자 정의 필드 설정
            issueForm.setIssueCustomFields(issueApiForm.getCustomFieldValues());
            //  같은 도메인 업체 찾기
            this.findCompanyField(issueForm, issueApiForm.getUrl());
            this.findCompanyField(issueForm);
            // api 입력값 적용
            ConvertUtil.copyProperties(issueApiForm, issueForm);
@@ -339,36 +313,45 @@
        }
    }
    private void findCompanyField(IssueForm issueForm, String url) {
    private void findCompanyField(IssueForm issueForm) {
        if(issueForm.getIssueCustomFields() != null && issueForm.getIssueCustomFields().size() > 0) {
            CompanyFieldCondition condition = new CompanyFieldCondition();
            condition.setUrl(url);
            List<Map<String, Object>> companyFields = this.companyFieldService.find(condition);
            List<Map<String, Object>> issueCompanyFields = Lists.newArrayList();
            List<Map<String, Object>> issueIspFields = Lists.newArrayList();
            List<Map<String, Object>> issueHostingFields = Lists.newArrayList();
            if (companyFields != null && companyFields.size() > 0) {
                Map<String, Object> companyFieldMap = companyFields.get(0);
                companyFieldMap.put("companyId", companyFieldMap.get("id"));
                issueCompanyFields.add(companyFieldMap);
            for (Map<String, Object> issueCustomField : issueForm.getIssueCustomFields()) {
                int customFieldId = (Integer) issueCustomField.get("customFieldId");
                Long customId = (long) customFieldId;
                CustomField customField = this.customFieldService.getCustomField(customId);
                if(customField != null && customField.getCustomFieldType().toString().equals("SITE") && customField.getName().equals("도메인")) {
                    String useValue = issueCustomField.get("useValue").toString();
                    if(companyFields != null && companyFields.size() > 0) {
                        for (Map<String, Object> companyField : companyFields) {
                            CompanyFieldVo companyFieldVo = ConvertUtil.convertMapToClass(companyField, CompanyFieldVo.class);
                            if(useValue.equals(companyFieldVo.getUrl())) {
                                companyField.put("companyId", companyField.get("id"));
                                issueCompanyFields.add(companyField);
                                if(companyFieldVo.getIspId() != null) {
                                    Map<String, Object> ispField = this.ispFieldService.find(companyFieldVo.getIspId());
                                    if (ispField != null) {
                                        ispField.put("ispId", ispField.get("id"));
                                        issueIspFields.add(ispField);
                                    }
                                }
                                if(companyFieldVo.getHostingId() != null) {
                                    Map<String, Object> hostingField = this.hostingFieldService.find(companyFieldVo.getHostingId());
                                    if (hostingField != null) {
                                        hostingField.put("hostingId", hostingField.get("id"));
                                        issueHostingFields.add(hostingField);
                                    }
                                }
                            }
                        }
                    }
                }
                issueForm.setIssueCompanyFields(issueCompanyFields);
                IspField ispField = this.ispFieldService.getIsp(MapUtil.getLong(companyFieldMap, "ispId"));
                if (ispField != null) {
                    Map<String, Object> ispFieldMap = ConvertUtil.convertObjectToMap(ispField);
                    ispFieldMap.put("ispId", ispField.getId());
                    issueIspFields.add(ispFieldMap);
                }
                HostingField hostingField = this.hostingFieldService.getHosting(MapUtil.getLong(companyFieldMap, "hostingId"));
                if (hostingField != null) {
                    Map<String, Object> hostingFieldMap = ConvertUtil.convertObjectToMap(hostingField);
                    hostingFieldMap.put("hostingId", hostingField.getId());
                    issueHostingFields.add(hostingFieldMap);
                }
                issueForm.setIssueIspFields(issueIspFields);
                issueForm.setIssueHostingFields(issueHostingFields);
            }
@@ -945,7 +928,7 @@
//        if (!this.userWorkspaceService.checkWorkspaceManager(user)
//                && !MngPermission.checkMngPermission(userLevel.getPermission(), MngPermission.USER_PERMISSION_MNG_ISSUE)) { //최고관리자 & 프로젝트,이슈 관리자 일 경우 모든 이슈 보기
//            this.SetMyDepartmentId(issueCondition);
        //this.SetAllDepartmentId(issueCondition);
            //this.SetAllDepartmentId(issueCondition);
//        } /*else{
//            results = this.issueMapper.findByDepartment(issueCondition);
//            totalCount = this.issueMapper.countByDepartment(issueCondition);
@@ -1829,14 +1812,15 @@
                Issue parentIssue = modifyIssue.getParentIssue();
                IssueType issueType = modifyIssue.getIssueType();
                Set<IssueTypeApiEndStatus> issueTypeApiEndStatuses = issueType.getIssueTypeApiEndStatuses();
                IssueTypeApiEndStatus issueStatus = null;
                if (issueTypeApiEndStatuses != null && issueTypeApiEndStatuses.size() > 0) {
                    issueStatus = issueTypeApiEndStatuses.iterator().next();
                } else {
                    throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_COMPLETE_ISSUE_STATUS_NOT_EXIST));
                }
                if (parentIssue != null) {
                    Set<IssueTypeApiEndStatus> issueTypeApiEndStatuses = issueType.getIssueTypeApiEndStatuses();
                    IssueTypeApiEndStatus issueStatus = null;
                    if (issueTypeApiEndStatuses != null && issueTypeApiEndStatuses.size() > 0) {
                        issueStatus = issueTypeApiEndStatuses.iterator().next();
                    } else {
                        throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_COMPLETE_ISSUE_STATUS_NOT_EXIST));
                    }
                    IssueCondition issueCondition = new IssueCondition(issueVo.getId(), parentIssue.getId());
                    List<Map<String, Object>> results = this.issueMapper.findNotCompleteByParentIssueId(issueCondition);
@@ -2887,11 +2871,8 @@
        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 템플릿을 만든다.
@@ -2915,7 +2896,7 @@
    //  엑셀 import 로 이슈를 등록한다.
    @Override
    @Transactional
    public void importExcel(MultipartFile multipartFile) throws Exception {
    public void importExcel(IssueForm issueForm, MultipartFile multipartFile) throws Exception {
        /*StopWatch serviceStart = new StopWatch();
        serviceStart.start();*/
@@ -2926,19 +2907,16 @@
            //  업로드 파일 확장자 체크
            this.verifyMultipartFileExtension(multipartFile);
            Map<String, Project> projectMaps = new HashMap<>(); //  프로젝트 모음
            Map<String, IssueType> issueTypeMaps = new HashMap<>(); //  이슈 타입 모음
            Map<String, Priority> priorityMaps = new HashMap<>();   //  우선 순위 모음
            Map<String, Severity> severityMaps = new HashMap<>();   //  중요도 모음
            Map<String, Object> userMaps = new HashMap<>(); //  사용자 모음
            Map<String, DepartmentVo> departmentMaps = new HashMap<>(); //  부서 모음
            Map<String, CustomField> customFieldMaps = new HashMap<>();
            Map<String, IssueStatus> issueStatusReadyMaps = new HashMap<>();   //  상태 속성 '대기'인 이슈 상태
            Map<Long, Long> issueNumberMaps = new HashMap<>();  //  이슈 번호 모음
            Map<String, Long> 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);
            this.IssueAttributeMapToList(priorityMaps, severityMaps, departmentMaps, customFieldMaps, issueTypeCustomFieldMaps);
            //  0.237 - 0.230
            List<IssueForm> issueForms = Lists.newArrayList();
@@ -2981,7 +2959,14 @@
                //  1번 헤더부터 데이터 영역
                if (rowIndex > 1) {
                    //  이슈로 등록하기 위해 IssueForm 에 데이터를 셋팅한다.
                    issueForms.add(this.setIssueFormToExcelField(row, (rowIndex + 1), issueStatusReadyMaps, projectMaps, issueTypeMaps, priorityMaps, severityMaps, userMaps, departmentMaps, customFieldMaps, issueNumberMaps, headers));
                    IssueForm newIssueForm = this.setIssueFormToExcelField(row, (rowIndex + 1), priorityMaps, severityMaps, departmentMaps, customFieldMaps, headers);
                    ConvertUtil.copyProperties(issueForm, newIssueForm);
                    issueForms.add(newIssueForm);
                }
            }
@@ -2992,7 +2977,46 @@
            //  이슈 등록
            this.issueMapper.insertBatch(issueForms);
//            this.issueMapper.insertBatch(issueForms);
            for (IssueForm saveIssueForm : issueForms) {
                Issue issue = new Issue();
                ConvertUtil.copyProperties(saveIssueForm, issue);
                IssueType issueType = this.issueTypeService.getIssueType(saveIssueForm.getIssueTypeId());
                Workflow workflow = issueType.getWorkflow();
                Project project = this.projectService.getProject(saveIssueForm.getProjectId());
                Long issueNumber = this.issueNumberGeneratorService.generateIssueNumber(project);
                IssueStatus issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(workflow);
                issue.setPriority(this.priorityService.getPriority(saveIssueForm.getPriorityId()));
                issue.setSeverity(this.severityService.getSeverity(saveIssueForm.getSeverityId()));
                issue.setIssueStatus(issueStatus);
                issue.setIssueType(issueType);
                issue.setProject(project);
                issue.setIssueNumber(issueNumber);
                issue.setParentIssue(this.getIssue(saveIssueForm.getParentIssueId()));
                issue = this.issueRepository.saveAndFlush(issue);
                saveIssueForm.setId(issue.getId());
                IssueDepartment issueDepartment = new IssueDepartment();
                issueDepartment.setIssue(issue);
                issueDepartment.setWorkspace(workspace);
                List<Long> departmentsIds = this.workflowDepartmentService.findFirstDepartmentIds(workflow);
                for (Long departmentId : departmentsIds) {
                    issueDepartment.setDepartment(this.departmentService.getDepartment(departmentId));
                }
                issue.addIssueDepartment(issueDepartment);
                saveIssueForm.setIssueStatusId(issueStatus.getId());
            }
            //  0.416 - 0.439
            //  1.373 ~ 1.394
@@ -3022,7 +3046,8 @@
            //  reverse index 업데이트
            this.issueMapper.updateBatch(issueForms);
            //  증가된 이슈 번호를 업데이트 한다.
            this.issueNumberGeneratorService.updateIssueNumber(issueNumberMaps);
//            issueNumberMaps.put(issueForm.getProjectId(), issueForm.getProjectId());
//            this.issueNumberGeneratorService.updateIssueNumber(issueNumberMaps);
        }
    }
@@ -3131,49 +3156,8 @@
    }
    //  이슈의 주요 속성을 map 에 저장하여 엑셀 import 에서 지정한 대상(이슈 속성)을 빠르게 찾을 수 있게 한다.
    private void IssueAttributeMapToList(Map<String, Project> projectMaps, Map<String, IssueType> issueTypeMaps, Map<String, Priority> priorityMaps, Map<String, Severity> severityMaps,
                                         Map<String, Object> userMaps, Map<String, DepartmentVo> departmentMaps, Map<String, CustomField> customFieldMaps, Map<Long, Long> issueNumberMaps, Map<String, Long> issueTypeCustomFieldMaps, Map<String, IssueStatus> issueStatusReadyMaps) {
        //  프로젝트 키로 바로 찾을 수 있게 준비
        List<Project> projects = this.projectService.findByWorkspaceId();
        List<Long> 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<Map<String, Object>> users = this.userService.findProjectMember(project);
            Map<String, Object> userMap = new HashMap<>();
            //  사용자 정보를 Map 에 저장
            for (Map<String, Object> user : users) {
                userMap.put(CommonUtil.decryptAES128(MapUtil.getString(user, "account")), MapUtil.getLong(user, "userId"));
            }
            userMaps.put(project.getProjectKey(), userMap);
        }
        //  이슈 유형을 바로 찾을 수 있게 준비
        List<IssueType> 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<DepartmentVo> departments = this.departmentService.findWorkflowDepartment(issueType.getId());
            //  부서 정보를 저장
            for (DepartmentVo department : departments) {
                departmentMaps.put(department.getDepartmentName(), department);
            }
        }
    private void IssueAttributeMapToList(Map<String, Priority> priorityMaps, Map<String, Severity> severityMaps,
                                         Map<String, DepartmentVo> departmentMaps, Map<String, CustomField> customFieldMaps,Map<String, Long> issueTypeCustomFieldMaps) {
        //  우선순위를 바로 찾을 수 있게 준비
        List<Priority> priorities = this.priorityService.findByWorkspaceId();
@@ -3195,8 +3179,9 @@
    }
    //  엑셀 필드에 있는 정보를 이슈 form 으로 옮긴다.
    private IssueForm setIssueFormToExcelField(Row row, int rowIndex, Map<String, IssueStatus> issueStatusReadyMaps, Map<String, Project> projectMaps, Map<String, IssueType> issueTypeMaps, Map<String,
            Priority> priorityMaps, Map<String, Severity> severityMaps, Map<String, Object> userMaps, Map<String, DepartmentVo> departmentMaps, Map<String, CustomField> customFieldMaps, Map<Long, Long> issueNumberMaps, List<String> headers) {
    private IssueForm setIssueFormToExcelField(Row row, int rowIndex, Map<String, Priority> priorityMaps,
                                               Map<String, Severity> severityMaps, Map<String, DepartmentVo> departmentMaps,
                                               Map<String, CustomField> customFieldMaps, List<String> headers) {
        IssueForm issueForm = new IssueForm();
        issueForm.setRegisterId(this.webAppUtil.getLoginId());
        Project project = null;
@@ -3220,23 +3205,12 @@
                    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:
                case 2:
                    //  우선순위를 IssueForm 에 저장한다.
                    this.setIssueFormPriority(cell, priorityMaps, issueForm, rowIndex);
                    break;
                case 5:
                case 3:
                    //  중요도를 IssueForm 에 저장한다.
                    this.setIssueFormSeverity(cell, severityMaps, issueForm, rowIndex);
                    break;
@@ -3244,19 +3218,13 @@
                    //  담당자를 IssueForm 에 저장한다.
                    this.setIssueFormAssignee(cell, userMaps, issueForm, project);
                    break;*/
                case 6:
                    //  담당부서를 IssueForm 에 저장한다.
                    if (cell != null) {
                        this.setIssueFormDepartment(cell, departmentMaps, issueForm, project);
                    }
                    break;
                case 7:
                case 4:
                    //  시작일을 IssueForm 에 저장한다.
                    if (cell != null) {
                        this.setIssueFormPeriod(cell, issueForm, true, rowIndex);
                    }
                    break;
                case 8:
                case 5:
                    //  종료일을 IssueForm 에 저장한다.
                    if (cell != null) {
                        this.setIssueFormPeriod(cell, issueForm, false, rowIndex);
@@ -3283,17 +3251,6 @@
        //  제목 유효성 체크
        this.verifyTitle(title);
        issueForm.setTitle(title);
    }
    //  프로젝트 키, 이슈 고유 번호, 담당자를 IssueForm 에 저장한다.
    private Project setIssueFormProjectKeyAndIssueNumber(Cell cell, IssueForm issueForm, Map<String, Project> projectMaps, Map<Long, Long> issueNumberMaps, int rowIndex) {
        //  프로젝트 아이디를 IssueForm 에 저장한다.
        Project project = this.setIssueFormProject(cell, projectMaps, issueForm, rowIndex);
        //  이슈 고유 번호를 IssueForm 에 저장한다.
        this.setIssueFormIssueNumber(issueForm, issueNumberMaps, project, rowIndex);
        return project;
    }
    //  프로젝트 아이디를 IssueForm 에 저장한다.
@@ -3329,34 +3286,6 @@
        issueNumberMaps.put(project.getId(), ++issueNumber);  //  이슈 번호를 1씩 증가 시킨다.
    }
    //  이슈 타입을 IssueForm 에 저장한다.
    private void setIssueFormIssueType(Cell cell, Map<String, IssueType> 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<String, IssueStatus> 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<String, Priority> priorityMaps, IssueForm issueForm, int rowIndex) {
@@ -3407,28 +3336,6 @@
            issueForm.setUserIds(userIds);
        }
    }
    //  담당부서를 IssueForm 에 저장한다.
    private void setIssueFormDepartment(Cell cell, Map<String, DepartmentVo> departmentMaps, IssueForm issueForm, Project project) {
        if (cell != null) {
            String[] splitDepartment = CommonUtil.convertExcelStringToCell(cell).split("#");
            //  값이 공백이면 중지
            String cellValue = CommonUtil.convertExcelStringToCell(cell);
            if (StringUtils.isEmpty(cellValue)) {
                return;
            }
            List<Long> departmentIds = Lists.newArrayList();
            for (String department : splitDepartment) {
                DepartmentVo departmentVo = departmentMaps.get(department);
                departmentIds.add(departmentVo.getId());
            }
            issueForm.setDepartmentIds(departmentIds);
        }
    }
    //  시작일, 종료일을 IssueForm 에 저장한다.
    private void setIssueFormPeriod(Cell cell, IssueForm issueForm, Boolean checkStartDate, int rowIndex) {
        if (cell != null && !cell.toString().equals("")) {
@@ -3464,7 +3371,7 @@
        }
    }
    //  사용자 정의 필드 정보를 IssueForm 에 저장한다.
    //  사용자 정의 필드 정보를 IssueForm 에 저장한다.-
    private void setIssueFormCustomFieldValue(Cell cell, Map<String, CustomField> customFieldMaps, IssueForm issueForm, String customFieldName, int rowIndex) {
        if (cell != null) {
            String cellValue = CommonUtil.convertExcelStringToCell(cell);
src/main/java/kr/wisestone/owl/web/controller/IssueController.java
@@ -218,7 +218,7 @@
    public @ResponseBody Map<String, Object> importExcel(MultipartHttpServletRequest request) throws Exception {
        Map<String, Object> resJsonData = new HashMap<>();
        this.issueService.importExcel(request.getFile("file"));
        this.issueService.importExcel(IssueForm.make(ConvertUtil.convertJsonToMap(request.getParameter(Constants.REQ_KEY_CONTENT))), request.getFile("file"));
        return this.setSuccessMessage(resJsonData);
    }
src/main/webapp/i18n/ko/global.json
@@ -161,6 +161,7 @@
        "issueContent": "이슈 내용",
        "issueTitle": "이슈 제목",
        "issueInfo": "이슈 정보",
        "parentIssue": "상위 이슈",
        "relationIssue": "연관 이슈",
        "addRelationIssue": "연관 이슈 추가",
        "relationIssueTitle": "연관 이슈 제목",
src/main/webapp/scripts/app/issue/issueImportExcel.controller.js
@@ -17,7 +17,8 @@
                    formCheck : formCheck,  //  폼 체크
                    getIssueTypes : getIssueTypes,  //  이슈 타입 목록 가져오기
                    makeSearchConditions : makeSearchConditions,    //  선택한 조건을 json 으로 만든다.
                    onFileSelect : onFileSelect //  파일 업로드
                    onFileSelect : onFileSelect, //  파일 업로드
                    getIssueListCallBack : getIssueListCallBack // 이슈 autocomplete page 업데이트
                };
                $scope.vm = {
@@ -39,6 +40,11 @@
                };
                angular.extend(this, $controller('autoCompleteController', {$scope : $scope, $injector : $injector}));
                //  이슈 autocomplete page 업데이트
                function getIssueListCallBack(result) {
                    $scope.vm.autoCompletePage.issue.totalPage = result.data.page.totalPage;
                }
                //  이슈 유형 목록
                function getIssueTypes() {
@@ -84,6 +90,14 @@
                //  폼 체크
                function formCheck() {
                    if ($scope.vm.form.projects == null || $scope.vm.form.projects.length === 0) {
                        return false;
                    }
                    if ($scope.vm.form.issues == null || $scope.vm.form.issues.length === 0) {
                        return false;
                    }
                    if ($scope.vm.form.file.length < 1) {
                        return true;
                    }
@@ -100,7 +114,11 @@
                        file : $scope.vm.form.file,
                        //      data 속성으로 별도의 데이터 전송
                        fields : {
                            content : {}
                            content : {
                                projectId : $scope.vm.form.projects[0].id,
                                issueTypeId : $scope.vm.form.issueTypeId,
                                parentIssueId : $scope.vm.form.issues[0].id
                            }
                        },
                        fileFormDataName : "file"
                    })
src/main/webapp/views/issue/issueExcelImport.html
@@ -13,7 +13,7 @@
            <div class="row">
                <div class="col-lg-5">
                    <div class="form-group">
                        <label for=""> <span translate="common.project">프로젝트</span> <code
                        <label><span translate="common.project">프로젝트</span> <code
                                class="highlighter-rouge">*</code></label>
                        <js-autocomplete-single data-input-name="project"
                                                selected-model="vm.form.projects"
@@ -26,7 +26,20 @@
                                                type : '', maxlength : 200, autoResize : true, stopRemoveBodyEvent : true }"></js-autocomplete-single>
                    </div>
                    <div class="form-group">
                    <label><span translate="issue.parentIssue">상위 이슈</span> <code
                            class="highlighter-rouge">*</code></label>
                    <js-autocomplete-single data-input-name="issue"
                                            selected-model="vm.form.issues"
                                            search="vm.issueName"
                                            source="fn.getRelIssueList(vm.issueName, vm.issueTypeId, vm.form.issues, vm.autoCompletePage.issue.page, fn.getIssueListCallBack)"
                                            page="vm.autoCompletePage.issue.page"
                                            total-page="vm.autoCompletePage.issue.totalPage"
                                            input-disabled="false"
                                            translation-texts="{ empty : 'common.emptyIssue' }"
                                            extra-settings="{ displayProp : 'title' , idProp : 'id', imageable : false, imagePathProp : '',
                                            type : '', maxlength : 200, autoResize : true, stopRemoveBodyEvent : true }"></js-autocomplete-single>
                    <div class="form-group mt-5">
                        <label for="issueExcelImportFrom3"> <span translate="issue.issueType">이슈 타입</span> <code
                                class="highlighter-rouge">*</code></label>
                        <select id="issueExcelImportFrom3"