OWL ITS + 탐지시스템(인터넷 진흥원)
- 하위이슈 직접 추가하는 기능
- 이슈 추가 시 ISP,호스팅 정보 추가 안되는 문제 해
2개 파일 추가됨
11개 파일 변경됨
1898 ■■■■■ 파일 변경됨
src/main/java/kr/wisestone/owl/service/IssueService.java 4 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/IssueServiceImpl.java 85 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/IssueController.java 15 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/scripts/app/issue/issue.js 2 ●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/scripts/app/issue/issueAdd.controller.js 62 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/scripts/app/issue/issueAddDown.controller.js 919 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/scripts/app/issue/issueAddRelation.controller.js 2 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/scripts/app/issue/issueList.controller.js 17 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/scripts/app/issue/issueModify.controller.js 2 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/scripts/components/issue/issue.service.js 12 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/scripts/main.js 6 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/views/issue/issueAddDown.html 767 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/views/issue/issueDetail.html 5 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/IssueService.java
@@ -30,8 +30,12 @@
    Issue addRelIssue(IssueForm issueForm, List<MultipartFile> files);
    Issue addDownIssue(IssueForm issueForm, List<MultipartFile> files);
    Issue addRelIssue(User user, IssueForm issueForm, List<MultipartFile> multipartFiles);
    Issue addDownIssue(User user, IssueForm issueForm, List<MultipartFile> multipartFiles);
    List<Issue> addApiIssue(IssueApiForm issueApiForm) throws CloneNotSupportedException;
    List<Issue> modifyIssue(IssueApiForm issueApiForm, List<MultipartFile> files);
src/main/java/kr/wisestone/owl/service/impl/IssueServiceImpl.java
@@ -523,6 +523,91 @@
        return addRelIssue(user, issueForm, multipartFiles);
    }
    //  하위이슈를 생성한다.
    @Override
    @Transactional
    public Issue addDownIssue(IssueForm issueForm, List<MultipartFile> multipartFiles) {
        User user = this.webAppUtil.getLoginUserObject();
        return addDownIssue(user, issueForm, multipartFiles);
    }
    //  하위이슈를 생성한다.
    @Override
    @Transactional
    public Issue addDownIssue(User user, IssueForm issueForm, List<MultipartFile> 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<Map<String, Object>> 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
src/main/java/kr/wisestone/owl/web/controller/IssueController.java
@@ -66,6 +66,21 @@
        return this.setSuccessMessage(resJsonData);
    }
    //  하위이슈 생성
    @RequestMapping(value = "/issue/downIssueAdd", method = RequestMethod.POST)
    public
    @ResponseBody
    Map<String, Object> downIssueAdd(MultipartHttpServletRequest request) {
        Map<String, Object> resJsonData = new HashMap<>();
        //  이슈 생성
        Issue issue = this.issueService.addDownIssue(IssueForm.make(ConvertUtil.convertJsonToMap(request.getParameter(Constants.REQ_KEY_CONTENT))), request.getFiles("file"));
        //  버전 생성
        this.issueService.addIssueVersion(issue.getId());
        resJsonData.put(Constants.RES_KEY_CONTENTS, issue.getId()); //하위이슈 ID
        return this.setSuccessMessage(resJsonData);
    }
    //  이슈 조회
    @RequestMapping(value = "/issue/find", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public
src/main/webapp/scripts/app/issue/issue.js
@@ -33,7 +33,7 @@
                        loadController: ["$q", function ($q) {
                            var deferred = $q.defer();
                            require([
                                'issueListTimelineController', 'issueManagerController', 'issueListController', 'issueAddController', 'issueModifyController', 'issueDetailController', 'issueAddRelationController', 'issueImportExcelController',
                                'issueListTimelineController', 'issueManagerController', 'issueListController', 'issueAddController', 'issueModifyController', 'issueDetailController', 'issueAddRelationController', 'issueAddDownController', 'issueImportExcelController',
                                'chartLoader', 'jsTable', 'jsTree', 'tableColumnGenerator', 'treeColumnGenerator', 'modalFormAutoScroll', 'summerNote', 'summerNote-ko-KR', 'fullScroll', 'workflowService', 'priorityService', 'issueSearchService', 'issueTableConfigService', 'inputRegex',
                                'severityService', 'issueTypeService', 'issueTypeCustomFieldService', 'issueService', 'issueStatusService', 'emailTemplateService','issueUserService','issueDepartmentService','issueModifyUserController', 'issueModifyDepartmentController', 'customFieldService', 'issueSearchFieldKeyViewElement',
                                'issueSearchCustomFieldViewElement', 'tableUserImage', 'fullScroll', 'issueCommentService', 'detectIssueEditor', 'formSubmit', 'issueModifyStatusController', 'downIssueModifyStatusController', 'jsShortCut',
src/main/webapp/scripts/app/issue/issueAdd.controller.js
@@ -399,21 +399,26 @@
                    $scope.vm.companyUrl = result[0].url;
                    $scope.vm.companyMemo = result[0].memo;
                    $scope.vm.ispName = ispFieldVo.name;
                    $scope.vm.ispCode = ispFieldVo.code;
                    $scope.vm.ispManager = ispFieldVo.manager;
                    $scope.vm.ispTel = ispFieldVo.tel;
                    $scope.vm.ispEmail = ispFieldVo.email;
                    $scope.vm.ispUrl = ispFieldVo.url;
                    $scope.vm.ispMemo = ispFieldVo.memo;
                    $scope.vm.hostingName = hostingFieldVo.name;
                    $scope.vm.hostingCode = hostingFieldVo.code;
                    $scope.vm.hostingManager = hostingFieldVo.manager;
                    $scope.vm.hostingTel = hostingFieldVo.tel;
                    $scope.vm.hostingEmail = hostingFieldVo.email;
                    $scope.vm.hostingUrl = hostingFieldVo.url;
                    $scope.vm.hostingMemo = hostingFieldVo.memo;
                    if (ispFieldVo != null){
                        $scope.vm.ispId = ispFieldVo.id;
                        $scope.vm.ispName = ispFieldVo.name;
                        $scope.vm.ispCode = ispFieldVo.code;
                        $scope.vm.ispManager = ispFieldVo.manager;
                        $scope.vm.ispTel = ispFieldVo.tel;
                        $scope.vm.ispEmail = ispFieldVo.email;
                        $scope.vm.ispUrl = ispFieldVo.url;
                        $scope.vm.ispMemo = ispFieldVo.memo;
                    }
                    if (hostingFieldVo != null){
                        $scope.vm.hostingId = hostingFieldVo.id;
                        $scope.vm.hostingName = hostingFieldVo.name;
                        $scope.vm.hostingCode = hostingFieldVo.code;
                        $scope.vm.hostingManager = hostingFieldVo.manager;
                        $scope.vm.hostingTel = hostingFieldVo.tel;
                        $scope.vm.hostingEmail = hostingFieldVo.email;
                        $scope.vm.hostingUrl = hostingFieldVo.url;
                        $scope.vm.hostingMemo = hostingFieldVo.memo;
                    }
                });
                //ISP정보 결과 값 Event 처리(set)
@@ -472,7 +477,11 @@
                        ispId : (function () {  // ISP 아이디
                            var ispId = -1;
                            if ($scope.vm.form.issueIspFields != null && $scope.vm.form.issueIspFields.length > 0) {
                            if ($scope.vm.form.issueCompanyFields != null && $scope.vm.form.issueCompanyFields.length > 0) {
                                if ($scope.vm.form.issueCompanyFields[0].ispId != null){
                                    ispId = $scope.vm.form.issueCompanyFields[0].ispId;
                                }
                            }else if ($scope.vm.form.issueIspFields != null && $scope.vm.form.issueIspFields.length > 0) {
                                ispId = $scope.vm.form.issueIspFields[0].id;
                            }
                            return ispId;
@@ -480,7 +489,11 @@
                        hostingId : (function () {  // Hosting 아이디
                            var hostingId = -1;
                            if ($scope.vm.form.issueHostingFields != null && $scope.vm.form.issueHostingFields.length > 0) {
                            if ($scope.vm.form.issueCompanyFields != null && $scope.vm.form.issueCompanyFields.length > 0) {
                                if ($scope.vm.form.issueCompanyFields[0].hostingId != null){
                                    hostingId = $scope.vm.form.issueCompanyFields[0].hostingId;
                                }
                            }else if ($scope.vm.form.issueHostingFields != null && $scope.vm.form.issueHostingFields.length > 0) {
                                hostingId = $scope.vm.form.issueHostingFields[0].id;
                            }
                            return hostingId;
@@ -524,7 +537,6 @@
                                var companyField = $scope.vm.form.issueCompanyFields[0];
                                issueCompanyFields.push({
                                    id : companyField.id,
                                    companyId : $scope.vm.companyId,
                                    name : $scope.vm.companyName,
                                    manager : $scope.vm.companyManager,
@@ -540,11 +552,11 @@
                        issueIspFields : (function () {
                            var issueIspFields = [];
                            if ($scope.vm.form.issueIspFields != null && $scope.vm.form.issueIspFields.length > 0 ){
                            if ($scope.vm.form.issueCompanyFields[0].ispFieldVo != null
                                || $scope.vm.form.issueIspFields != null && $scope.vm.form.issueIspFields.length > 0 ){
                                var ispField = $scope.vm.form.issueIspFields[0];
                                issueIspFields[0] = {
                                    id : ispField.id,
                                issueIspFields.push({
                                    ispId : $scope.vm.ispId,
                                    name : $scope.vm.ispName,
                                    code : $scope.vm.ispCode,
@@ -553,7 +565,7 @@
                                    email :$scope.vm.ispEmail,
                                    url :$scope.vm.ispUrl,
                                    memo : $scope.vm.ispMemo
                                };
                                });
                            }
@@ -562,12 +574,11 @@
                        issueHostingFields : (function () {
                            var issueHostingFields = [];
                            if ($scope.vm.form.issueHostingFields != null && $scope.vm.form.issueHostingFields.length > 0 ){
                            if ($scope.vm.form.issueCompanyFields[0].hostingFieldVo != null
                                || $scope.vm.form.issueHostingFields != null && $scope.vm.form.issueHostingFields.length > 0 ){
                                var hostingField = $scope.vm.form.issueHostingFields[0];
                                issueHostingFields[0] = {
                                    id : hostingField.id,
                                    hostingId : $scope.vm.hostingId,
                                    name : $scope.vm.hostingName,
                                    code : $scope.vm.hostingCode,
@@ -578,7 +589,6 @@
                                    memo : $scope.vm.hostingMemo
                                };
                            }
                            return issueHostingFields;
                        })(),
src/main/webapp/scripts/app/issue/issueAddDown.controller.js
New file
@@ -0,0 +1,919 @@
/**
 * Created by wisestone on 2017-12-15.
 */
'use strict';
define([
        'app',
        'angular'
    ],
    function (app, angular) {
        app.controller('issueAddDownController', ['$scope', '$rootScope', '$log', '$resourceProvider', '$uibModalInstance', '$uibModal', '$injector',
            '$controller', '$tableProvider', 'parameter' ,'SweetAlert', '$timeout', '$stateParams', '$q', 'Issue', 'User', 'AttachedFile', 'IssueType', 'Priority', 'Severity','IssueTypeCustomField', '$filter', '$state',
            function ($scope, $rootScope, $log, $resourceProvider, $uibModalInstance, $uibModal,  $injector, $controller, $tableProvider, parameter, SweetAlert, $timeout,
                      $stateParams, $q, Issue, User, AttachedFile, IssueType, Priority, Severity, IssueTypeCustomField, $filter, $state) {
                $scope.fn = {
                    cancel : cancel,    //  팝업 창 닫기
                    formSubmit : formSubmit,    //  폼 전송
                    formCheck : formCheck,  //  폼 체크
                    getUserListCallBack : getUserListCallBack,  //  담당자 autocomplete 페이징
                    getProjectListCallBack : getProjectListCallBack,    //  프로젝트 autocomplete 페이징
                    getIssueCompanyFieldListCallBack : getIssueCompanyFieldListCallBack,    // 업체정보 autocomplete 페이징
                    getIssueDepartmentListCallBack : getIssueDepartmentListCallBack,    // 담당자 -> 담당부서 autocomplete 페이징
                    getIssueIspFieldListCallBack : getIssueIspFieldListCallBack,    // ISP정보 autocomplete 페이징
                    getIssueHostingFieldListCallBack : getIssueHostingFieldListCallBack,    // 호스팅정보 autocomplete 페이징
                    getOptionColor : getOptionColor,    //  우선순위, 중요도 색상으로 Select 태그 적용
                    onFileSelect : onFileSelect,    //  파일 첨부
                    infiniteAddForm : infiniteAddForm,  //  계속 생성
                    imageUpload : imageUpload,  //  섬머노트 이미지 업로드
                    getIssueTypes : getIssueTypes,  //  이슈 타입 목록 가져오기
                    getPriorities : getPriorities,  //  우선순위 목록 가져오기
                    getSeverities : getSeverities,  //  중요도 목록 가져오기
                    getIssueTypeCustomFields : getIssueTypeCustomFields,    //  이슈 유형에 연결된 사용자 정의 필드 목록 가져오기
                    removeUploadFile : removeUploadFile,    //  업로드하려는 특정 파일을 삭제
                    removeManager : removeManager,  //  담당자 삭제
                    removeDepartment : removeDepartment,  //  담당부서 삭제
                    setIssueTypeTemplate : setIssueTypeTemplate,    //  이슈 유형 템플릿 적용하기
                    startExecute : startExecute, //  컨트롤 로딩시 처음으로 시작되는 함수
                    containsPartner : containsPartner,
                    getPartners : getPartners,
                    addDownIssue : addDownIssue,
                };
                $scope.vm = {
                    partnerVos : "",
                    form : {
                        title : "",    //  제목
                        description : "",   //  내용
                        issueStatusId : "", // 이슈 상태
                        projects : [],  //  프로젝트
                        issueCompanyFields : [], // 업체정보
                        issueIspFields : [], // ISP 정보
                        issueHostingFields : [], // 호스팅정보
                        issueTypeId : "",   //  이슈 유형 아이디
                        priorityId : "",    //  우선순위 아이디
                        severityId : "",    //  중요도 아이디
                        users : [],     //  담당자
                        departments : [], // 딤당부서
                        files : [], //  업로드 파일
                        attachedFiles : [], //  섬머노트로 파일 업로드를 할 경우 서버에서 pk를 따고 issue id와 연동 작업이 필요하다.
                        startCompleteDateRange : "", //  시작일 ~ 종료일
                        detectingDateRange : "", //  탐지일
                        issueCustomFields : [],  //  이슈에서 사용되는 사용자 정의 필드
                        removeFiles : [], // 삭제 파일
                    },
                    id : parameter.id,
                    infiniteAdd : false,    //  연속 생성
                    projectName : "",   //  프로젝트 명 검색
                    userName : "",  //  사용자 검색
                    departmentName : "",  // 부서명 검색
                    companyId : -1, // 부서 ID
                    companyName : "",   // 업체명 검색
                    companyManager : "",   // 업체 담당자
                    companyTel : "",  // 업체 전화번호
                    companyEmail : "",  // 업체 이메일
                    companyUrl : "", // 업체 url
                    companyMemo : "",  // 업체 비고
                    ispId : -1, // ISP ID
                    ispName : "", // ISP 명
                    ispCode : "", // ISP 코드
                    ispManager : "", // ISP 담당자
                    ispTel : "", // ISP 전화번호
                    ispEmail : "", // ISP 이메일
                    ispUrl : "", // ISP url
                    ispMemo : "", // ISP 비고
                    hostingId : -1, // 호스팅 ID
                    hostingName : "", // 호스팅명 검색
                    hostingManager : "", // 호스팅 담당자
                    hostingTel : "", // 호스팅 전화번호
                    hostingCode : "", // 호스팅 코드
                    hostingEmail : "", // 호스팅 이메일
                    hostingUrl : "", // 호스팅 url
                    hostingMemo :"", // 호스팅 비고
                    autoCompletePage : {
                        user : {
                            page : 0,
                            totalPage : 0
                        },
                        project : {
                            page : 0,
                            totalPage : 0
                        },
                        companyField : {
                            page : 0,
                            totalPage : 0
                        },
                        department : {
                            page : 0,
                            totalPage : 0
                        },
                        ispField : {
                            page : 0,
                            totalPage : 0
                        },
                        hostingField : {
                            page : 0,
                            totalPage : 0
                        }
                    },
                    summerNote : {
                        editable : null,
                        editor : null
                    },
                    issueTypes : [],    //  이슈 유형 전체 목록
                    priorities : [],    //  우선순위 정보
                    severities : [],    //  중요도 정보
                    fileTableConfigs : [],   //  파일 업로드 정보 테이블
                };
                angular.extend(this, $controller('autoCompleteController', {$scope : $scope, $injector : $injector}));
                function getStartProjectListCallback(result){
                    //  프로젝트 autocomplete page 업데이트
                    $scope.vm.autoCompletePage.project.totalPage = result.data.page.totalPage;
                    var projectVo = result.data.data[0];
                    $scope.vm.form.projects.push(projectVo);
                }
                //  프로젝트가 변경되면 담당자 초기화
                $scope.$watch("vm.form.projects", function (newValue, oldValue) {
                    if (angular.isDefined(newValue)) {
                        if (newValue.length < 1) {
                            $scope.vm.form.users = [];
                        } else {
                            //  이슈 유형에 연결된 사용자 정의 필드 가져오기
                            $scope.fn.getIssueTypeCustomFields();
                        }
                    }
                });
                $scope.$watch("vm.form.issueTypeId", function (newValue, oldValue) {
                    $scope.vm.partnerVos = $scope.fn.getPartners();
                });
                //  섬머노트 이미지 업로드
                function imageUpload($files) {
                    var listFiles = [];
                    var uploadFileSize = 0;
                    for (var count in $files) {
                        var $file = $files[count];
                        if (typeof ($file) == "object") {
                            uploadFileSize += $file.size;
                            //  파일당 용량 제한 10MB
                            if ($file.size > $rootScope.fileByte.image) {
                                SweetAlert.error($filter("translate")("issue.capacityExceededImageFile"), $filter("translate")("issue.attachedOnlyImageFiles10mb")); // "이미지 파일 용량 초과", "10MB 이하의 이미지 파일만 첨부가 가능합니다."
                                listFiles = [];
                                break;
                            }
                            //  여러건의 파일을 한번에 업로드할 경우 제한 300MB
                            if (uploadFileSize > $rootScope.fileByte.file) {
                                SweetAlert.error($filter("translate")("issue.capacityExceededImageFile"), $filter("translate")("issue.attachedMultipleImageFiles100mb")); // "이미지 파일 용량 초과", "여러 건의 이미지를 한번에 첨부할 경우 100MB 이하까지만 첨부가 가능합니다."
                                listFiles = [];
                                break;
                            }
                            if (!$rootScope.checkImageType($file)) {
                                SweetAlert.error($filter("translate")("issue.limitImageFile"), $filter("translate")("issue.canBeUploadedOnlyImageFiles")); // "이미지 파일 제한", "이미지 파일만 업로드 가능합니다. - bmp, jpg, jpeg, png, tif"
                                listFiles = [];
                                break;
                            }
                            if (!angular.isDefined($file.name)) {
                                var fileType = $file.type.split("/");
                                var imageType = "";
                                if (fileType[0] === "image") {
                                    imageType = "." + fileType[1];
                                }
                                $file.name = new Date().getTime() + imageType;
                            }
                            else {
                                if ($file.name.indexOf(';') !== -1) {
                                    SweetAlert.error($filter("translate")("issue.nameErrorImageFile"), $filter("translate")("issue.cannotUploadFileNameSpecialCharacters")); // "이미지 파일명 오류", "파일명에 특수문자(;)가 들어가면 업로드 할 수 없습니다."
                                    listFiles = [];
                                    break;
                                }
                            }
                            listFiles.push($file);
                        }
                    }
                    //  파일 업로드 검증을 거친 파일이 1개이상 존재할 경우에만 실행
                    if (listFiles.length > 0) {
                        AttachedFile.add({
                            method : "POST",
                            file : listFiles,
                            //      data 속성으로 별도의 데이터 전송
                            fields : {
                                content : {
                                    workspaceId : $rootScope.user.lastWorkspaceId
                                }
                            },
                            fileFormDataName : "file"
                        })
                            .then(function (result) {
                                if (result.data.message.status === "success") {
                                    angular.forEach(result.data.attachedFiles, function (fileInfo) {
                                        $scope.vm.summerNote.editor.summernote("editor.insertImage", fileInfo.path);
                                        $scope.vm.form.attachedFiles.push(fileInfo);
                                    });
                                }
                                else {
                                    SweetAlert.error($filter("translate")("issue.errorFileUpload"), result.data.message.message); // 파일 업로드 오류
                                }
                            });
                    }
                }
                //  연속으로 이슈를 등록할 때 입력 폼 초기화
                function infiniteAddForm() {
                    $scope.vm.form.title = "";
                    $scope.vm.form.description = "";
                    $scope.vm.form.files = [];
                    $scope.vm.form.attachedFiles = [];
                    //  이슈 유형 템플릿 적용하기
                    $scope.fn.setIssueTypeTemplate();
                    $(".modal-body").animate({
                        scrollTop : 0
                    }, 500);
                    $timeout(function () {
                        $("[name='title']").trigger("focus")
                    }, 100);
                }
                //  파일 업로드에 사용
                function onFileSelect($files) {
                    var uploadFileSize = 0;
                    //  이전에 첨부한 파일이 있을 경우 전체 업로드 용량에 포함
                    angular.forEach($scope.vm.form.files, function ($file) {
                        uploadFileSize += $file.size;
                    });
                    for (var count in $files) {
                        var $file = $files[count];
                        if (typeof ($file) == "object") {
                            uploadFileSize += $file.size;
                            //  파일당 용량 제한 300MB
                            if (($file.size > $rootScope.fileByte.file) || (uploadFileSize > $rootScope.fileByte.file)) {
                                SweetAlert.error($filter("translate")("issue.attachmentCapacityExceeded"), $filter("translate")("issue.canAttachFileUpTo100mb")); // "첨부 파일 용량 초과", "100MB 이하까지만 파일 첨부가 가능합니다."
                                break;
                            }
                            //  파일을 업로드할 때 파일 유형을 확인해주는 기능 - 허용되지 않은 확장자일 때는 첨부 금지
                            if (!$rootScope.checkFileType($file)) {
                                SweetAlert.error($filter("translate")("issue.limitAttachmentExtensions"), $filter("translate")("issue.notAllowedAttachment")); // "첨부 파일 확장자 제한", "첨부가 허용되지 않는 파일입니다."
                                break;
                            }
                            if ($file.name.indexOf(';') !== -1) {
                                SweetAlert.error($filter("translate")("issue.nameErrorAttachment"), $filter("translate")("issue.cannotUploadFileNameSpecialCharacters")); // "첨부 파일명 오류", "파일명에 특수문자(;)가 들어가면 업로드 할 수 없습니다."
                                break;
                            }
                            $file.index = count;
                            $scope.vm.form.files.push($file);
                        }
                    }
                }
                //  셀렉트 박스에서 중요도, 우선순위 색상 표시
                function getOptionColor(list, key) {
                    var color = "#353535";  //  기본색은 검은색.
                    for (var count in list) {
                        if (String(list[count].id) === key) {
                            color = list[count].color;
                            break;
                        }
                    }
                    return color;
                }
                //  담당자 삭제
                function removeManager(index) {
                    $scope.vm.form.departments.splice(index, 1);
                }
                // 담당부서 삭제
                function removeDepartment(index) {
                    $scope.vm.form.departments.splice(index, 1);
                }
                //  업로드 파일 삭제
                function removeUploadFile(index) {
                    $scope.vm.form.files.splice(index, 1);
                    angular.forEach($scope.vm.form.files, function (file, index) {
                        file.index = index;
                    });
                }
                // 업체/ISP/호스팅 이름이 포함 여부 확인
                function containsPartner(name) {
                    var result = false;
                    if ($scope.vm.partnerVos != null) {
                        $scope.vm.partnerVos.forEach(function (partnerVo) {
                            if (name === partnerVo.name) {
                                result = true;
                            }
                        });
                    }
                    return result;
                }
                //  담당자 autocomplete page 업데이트트
                function getUserListCallBack(result) {
                    $scope.vm.autoCompletePage.user.totalPage = result.data.page.totalPage;
                }
                //  프로젝트 autocomplete page 업데이트
                function getProjectListCallBack(result) {
                    $scope.vm.autoCompletePage.project.totalPage = result.data.page.totalPage;
                }
                //  업체정보 autocomplete page 업데이트
                function getIssueCompanyFieldListCallBack(result) {
                    $scope.vm.autoCompletePage.companyField.totalPage = result.data.page.totalPage;
                }
                // 부서정보 autocomplete page 업데이트
                function getIssueDepartmentListCallBack(result) {
                    $scope.vm.autoCompletePage.department.totalPage = result.data.page.totalPage;
                }
                // ISP정보 autocomplete page 업데이트
                function getIssueIspFieldListCallBack(result) {
                    $scope.vm.autoCompletePage.ispField.totalPage = result.data.page.totalPage;
                }
                // 호스팅정보 autocomplete page 업데이트
                function getIssueHostingFieldListCallBack(result) {
                    $scope.vm.autoCompletePage.hostingField.totalPage = result.data.page.totalPage;
                }
                // 폼 체크
                function formCheck(formInvalid) {
                    if (formInvalid) {
                        return true;
                    }
                    return false;
                }
                // 업체정보 결과 값 Event 처리(set)
                $scope.$on("companyFieldEvent", function (event, result) {
                    var ispFieldVo = result[0].ispFieldVo;
                    var hostingFieldVo = result[0].hostingFieldVo;
                    $scope.vm.companyId = result[0].id;
                    $scope.vm.companyName = result[0].name;
                    $scope.vm.companyManager = result[0].manager;
                    $scope.vm.companyTel = result[0].tel;
                    $scope.vm.companyEmail = result[0].email;
                    $scope.vm.companyUrl = result[0].url;
                    $scope.vm.companyMemo = result[0].memo;
                    $scope.vm.ispId = ispFieldVo.id;
                    $scope.vm.ispName = ispFieldVo.name;
                    $scope.vm.ispCode = ispFieldVo.code;
                    $scope.vm.ispManager = ispFieldVo.manager;
                    $scope.vm.ispTel = ispFieldVo.tel;
                    $scope.vm.ispEmail = ispFieldVo.email;
                    $scope.vm.ispUrl = ispFieldVo.url;
                    $scope.vm.ispMemo = ispFieldVo.memo;
                    $scope.vm.hostingId = hostingFieldVo.id;
                    $scope.vm.hostingName = hostingFieldVo.name;
                    $scope.vm.hostingCode = hostingFieldVo.code;
                    $scope.vm.hostingManager = hostingFieldVo.manager;
                    $scope.vm.hostingTel = hostingFieldVo.tel;
                    $scope.vm.hostingEmail = hostingFieldVo.email;
                    $scope.vm.hostingUrl = hostingFieldVo.url;
                    $scope.vm.hostingMemo = hostingFieldVo.memo;
                });
                // ISP정보 결과 값 Event 처리(set)
                $scope.$on("ispFieldEvent", function (event, result) {
                    $scope.vm.ispId = result[0].id;
                    $scope.vm.ispName = result[0].name;
                    $scope.vm.ispCode = result[0].code;
                    $scope.vm.ispManager = result[0].manager;
                    $scope.vm.ispTel = result[0].tel;
                    $scope.vm.ispEmail = result[0].email;
                    $scope.vm.ispUrl = result[0].url;
                    $scope.vm.ispMemo = result[0].memo;
                });
                // 호스팅정보 결과 값 Event 처리(set)
                $scope.$on("hostingFieldEvent", function (event, result) {
                    $scope.vm.hostingId = result[0].id;
                    $scope.vm.hostingName = result[0].name;
                    $scope.vm.hostingCode = result[0].code;
                    $scope.vm.hostingManager = result[0].manager;
                    $scope.vm.hostingTel = result[0].tel;
                    $scope.vm.hostingEmail = result[0].email;
                    $scope.vm.hostingUrl = result[0].url;
                    $scope.vm.hostingMemo = result[0].memo;
                });
                //  폼 전송
                function formSubmit() {
                    $rootScope.spinner = true;
                    var content = {
                        //id : parameter.id,
                        title : $rootScope.preventXss($scope.vm.form.title),    //  제목
                        description : $rootScope.preventXss($scope.vm.form.description),   //  내용
                        companyName : $scope.vm.companyName,
                        companyManager : $scope.vm.companyManager,
                        companyTel : $scope.vm.companyTel,
                        companyEmail :$scope.vm.companyEmail,
                        companyUrl : $scope.vm.companyUrl,
                        companyMemo : $scope.vm.companyMemo,
                        ispName : $scope.vm.ispName,
                        ispCode : $scope.vm.ispCode,
                        ispManager : $scope.vm.ispManager,
                        ispTel : $scope.vm.ispTel,
                        ispEmail : $scope.vm.ispEmail,
                        ispUrl : $scope.vm.ispUrl,
                        ispMemo : $scope.vm.ispMemo,
                        hostingName : $scope.vm.hostingName,
                        hostingCode : $scope.vm.hostingCode,
                        hostingManager : $scope.vm.hostingManager,
                        hostingTel : $scope.vm.hostingTel,
                        hostingEmail : $scope.vm.hostingEmail,
                        hostingUrl : $scope.vm.hostingUrl,
                        hostingMemo : $scope.vm.hostingMemo,
                        projectId : (function () {   //  프로젝트 아이디
                            var projectId = "";
                            if ($scope.vm.form.projects.length > 0) {
                                projectId = $scope.vm.form.projects[0].id;
                            }
                            return projectId;
                        })(),
                        issueTypeId : $scope.vm.form.issueTypeId,   //  이슈 유형 아이디
                        priorityId : $scope.vm.form.priorityId,    //  우선순위 아이디
                        severityId : $scope.vm.form.severityId,    //  중요도 아이디
                        issueStatusId : $scope.vm.form.issueStatusId,   //  이슈 상태 아이디
                        companyId : (function () {
                            var companyId = -1;
                            if ($scope.vm.form.issueCompanyFields.length > 0) {
                                companyId = $scope.vm.form.issueCompanyFields[0].id;
                            }
                            return companyId;
                        }),
                        ispId : (function () {
                            var ispId = -1;
                            if ($scope.vm.form.issueIspFields.length > 0) {
                                ispId = $scope.vm.form.issueIspFields[0].id;
                            }
                            return ispId;
                        }),
                        hostingId : (function () {
                            var hostingId = -1;
                            if ($scope.vm.form.issueHostingFields != null && $scope.vm.form.issueHostingFields.length > 0) {
                                hostingId = $scope.vm.form.issueHostingFields[0].id;
                            }
                            return hostingId;
                        }),
                        userIds : (function () {
                            var userIds = [];
                            angular.forEach($scope.vm.form.users, function (user) {
                                userIds.push(user.id);
                            });
                            return userIds;
                        })(),
                        departmentIds : (function () {
                            var departmentIds = [];
                            angular.forEach($scope.vm.form.departments, function (department) {
                                departmentIds.push(department.id);
                            });
                            return departmentIds;
                        })(),
                        attachedFileIds : (function () {
                            var attachedFileIds = [];
                            angular.forEach($scope.vm.form.attachedFiles, function (attachedFile) {
                                if ($scope.vm.form.description.indexOf(attachedFile.path) !== -1) {
                                    attachedFileIds.push(attachedFile.id);
                                }
                            });
                            return attachedFileIds;
                        })(),
                        issueCompanyFields : (function () {
                            var issueCompanyFields = [];
                            if ($scope.vm.form.issueCompanyFields != null && $scope.vm.form.issueCompanyFields.length > 0 ){
                                var companyField = $scope.vm.form.issueCompanyFields[0];
                                issueCompanyFields.push({
                                    id : companyField.id,
                                    companyId : $scope.vm.companyId,
                                    name : $scope.vm.companyName,
                                    manager : $scope.vm.companyManager,
                                    tel : $scope.vm.companyTel,
                                    email :$scope.vm.companyEmail,
                                    url :$scope.vm.companyUrl,
                                    memo : $scope.vm.companyMemo
                                });
                            }
                            return issueCompanyFields;
                        })(),
                        issueIspFields : (function () {
                            var issueIspFields = [];
                            if ($scope.vm.form.issueIspFields != null && $scope.vm.form.issueIspFields.length > 0 ){
                                var ispField = $scope.vm.form.issueIspFields[0];
                                issueIspFields.push({
                                    id : ispField.id,
                                    ispId : $scope.vm.ispId,
                                    code : $scope.vm.ispCode,
                                    name : $scope.vm.ispName,
                                    manager : $scope.vm.ispManager,
                                    tel : $scope.vm.ispTel,
                                    email :$scope.vm.ispEmail,
                                    url :$scope.vm.ispUrl,
                                    memo : $scope.vm.ispMemo
                                });
                            }
                            return issueIspFields;
                        })(),
                        issueHostingFields : (function () {
                            var issueHostingFields = [];
                            if ($scope.vm.form.issueHostingFields != null && $scope.vm.form.issueHostingFields.length > 0 ){
                                var hostingField = $scope.vm.form.issueHostingFields[0];
                                issueHostingFields.push({
                                    id : hostingField.id,
                                    hostingId : $scope.vm.hostingId,
                                    name : $scope.vm.hostingName,
                                    code : $scope.vm.hostingCode,
                                    manager : $scope.vm.hostingManager,
                                    tel : $scope.vm.hostingTel,
                                    email :$scope.vm.hostingEmail,
                                    url :$scope.vm.hostingUrl,
                                    memo : $scope.vm.hostingMemo
                                });
                            }
                            return issueHostingFields;
                        })(),
                        removeFiles : $scope.vm.form.removeFiles,
                        startCompleteDateRange : $scope.vm.form.startCompleteDateRange,
                        issueCustomFields : (function () {    //  이슈에서 사용되는 사용자 정의 필드
                            var issueCustomFields = [];
                            angular.forEach($scope.vm.form.issueCustomFields, function (issueCustomField) {
                                var useValues = [];
                                if (angular.isArray(issueCustomField.useValues)) {
                                    angular.forEach(issueCustomField.useValues, function (useValue) {
                                        useValues.push(useValue.value);
                                    });
                                }
                                else {
                                    useValues.push(issueCustomField.useValues);
                                }
                                //  useValues 를 배열로 변환한다.
                                var temp = angular.copy(issueCustomField);
                                temp.useValues = useValues;
                                issueCustomFields.push(temp);
                            });
                            return issueCustomFields;
                        })()
                    };
                    Issue.downAdd({
                        method : "POST",
                        file : (function () {
                            var files = [];
                            angular.forEach($scope.vm.form.files, function (file) {
                                if (angular.isUndefined(file.id)) {
                                    files.push(file);
                                }
                            });
                            return files;
                        })(),
                        //      data 속성으로 별도의 데이터 전송
                        fields : {
                            content : content
                        },
                        fileFormDataName : "file"
                    }).then(function (result) {
                        if (result.data.message.status === "success") {
                            $scope.fn.addDownIssue(result.data.data);
                            $scope.fn.cancel();
                            //  이슈 상세 화면 요청
                            $rootScope.$broadcast("getIssueDetail", {
                                id : parameter.id
                            });
                        }
                        else {
                            SweetAlert.error($filter("translate")("issue.failedIssueModify"), result.data.message.message); // 이슈 수정 실패
                        }
                        $rootScope.spinner = false;
                    });
                }
                // 연관 이슈 추가
                function addDownIssue(downId) {
                    /*if ($scope.vm.issueName.length == 0 || $scope.vm.form.issues.length == 0
                        || $scope.vm.issueName != $scope.vm.form.issues[0].title) {
                        SweetAlert.error($filter("translate")("issue.errorSelectRelationIssue"), "");
                        return;
                    }*/
                    var contents = {
                        //relationIssueType : $scope.vm.form.relationIssueTypeId,
                        // issueId : $rootScope.currentDetailIssueId,
                        issueId :  parameter.id,
                        id : downId,
                        parentIssueId : parameter.id
                    };
                    Issue.modifyParentIssue($resourceProvider.getContent(
                        contents,
                        $resourceProvider.getPageContent(0, 10))).then(function (result) {
                        if (result.data.message.status === "success") {
                            //  이슈 상세 화면 요청
                            $rootScope.$broadcast("getIssueDetail", {
                                id : parameter.id
                            });
                        }
                        else {
                            SweetAlert.error($filter("translate")("issue.failedToIssueAddIssueDown"), result.data.message.message); // "연관일감 생성 실패"
                        }
                    });
                }
                //  팝업 창 닫기
                function cancel() {
                    SweetAlert.close(); //  알림 창 닫기
                    $rootScope.$broadcast("closeLayer");    //  팝업이 열리고 나서 js-multi, js-single 등에서 body 이벤트가 날아가는 현상 수정
                    $uibModalInstance.dismiss('cancel');
                    $(document).unbind("keydown");  //  단축키 이벤트 제거
                }
                //  이슈 유형 목록
                function getIssueTypes() {
                    var deferred = $q.defer();
                    IssueType.find($resourceProvider.getContent({},
                        $resourceProvider.getPageContent(0, 1000))).then(function (result) {
                        if (result.data.message.status === "success") {
                            $scope.vm.issueTypes = result.data.data;
                        }
                        else {
                            SweetAlert.swal($filter("translate")("issue.failedToIssueTypeListLookup"), result.data.message.message, "error"); // 이슈 타입 목록 조회 실패
                        }
                        deferred.resolve(result.data.data);
                    });
                    return deferred.promise;
                }
                //  우선순위 목록
                function getPriorities() {
                    var deferred = $q.defer();
                    Priority.find($resourceProvider.getContent({},
                        $resourceProvider.getPageContent(0, 1000))).then(function (result) {
                        if (result.data.message.status === "success") {
                            $scope.vm.priorities = result.data.data;
                        }
                        else {
                            SweetAlert.swal($filter("translate")("issue.failedToPriorityListLookup"), result.data.message.message, "error"); // 우선순위 목록 조회 실패
                        }
                        deferred.resolve(result.data.data);
                    });
                    return deferred.promise;
                }
                //  중요도 목록
                function getSeverities() {
                    var deferred = $q.defer();
                    Severity.find($resourceProvider.getContent({},
                        $resourceProvider.getPageContent(0, 1000))).then(function (result) {
                        if (result.data.message.status === "success") {
                            $scope.vm.severities = result.data.data;
                        }
                        else {
                            SweetAlert.swal($filter("translate")("issue.failedToCriticalListLookup"), result.data.message.message, "error"); // 중요도 목록 조회 실패
                        }
                        deferred.resolve(result.data.data);
                    });
                    return deferred.promise;
                }
                //  이슈 유형에 연결된 사용자 정의 필드
                function getIssueTypeCustomFields() {
                    $scope.vm.form.issueCustomFields = [];
                    //  이슈 타입 아이디나 프로젝트 아이디가 없으면 통신을 하지 않는다.
                    if (!$rootScope.isDefined($scope.vm.form.issueTypeId) || $scope.vm.form.projects.length < 1) {
                        return;
                    }
                    //  이슈 유형 템플릿 적용하기
                    $scope.fn.setIssueTypeTemplate();
                    var deferred = $q.defer();
                    IssueTypeCustomField.find($resourceProvider.getContent({projectId : $scope.vm.form.projects[0].id, issueTypeId : $scope.vm.form.issueTypeId},
                        $resourceProvider.getPageContent(0, 1000))).then(function (result) {
                        if (result.data.message.status === "success") {
                            $scope.vm.form.issueCustomFields = [];
                            angular.forEach(result.data.data, function (issueTypeCustomField) {
                                switch (issueTypeCustomField.customFieldVo.customFieldType) {
                                    case "INPUT" :
                                    case "NUMBER" :
                                    case "DATETIME" :
                                    case "IP_ADDRESS" :
                                    case "EMAIL" :
                                    case "SITE" :
                                    case "TEL" :
                                        issueTypeCustomField.useValues = issueTypeCustomField.customFieldVo.defaultValue;
                                        break;
                                    case "SINGLE_SELECT" :
                                        issueTypeCustomField.useValues = issueTypeCustomField.customFieldVo.defaultValue.replace("#", "");
                                        break;
                                    case "MULTI_SELECT" :
                                        issueTypeCustomField.useValues = [];
                                        angular.forEach(issueTypeCustomField.customFieldVo.defaultValue.split("#"), function (value) {
                                            if ($rootScope.isDefined(value)) {
                                                issueTypeCustomField.useValues.push({
                                                    value : value
                                                });
                                            }
                                        });
                                        break;
                                }
                                $scope.vm.form.issueCustomFields.push(issueTypeCustomField);
                            });
                        }
                        else {
                            SweetAlert.swal($filter("translate")("issue.failedToUserDefinedFieldListAssociatedLookup"), result.data.message.message, "error"); // 이슈 유형에 연결된 사용자 정의 필드 목록 조회 실패
                        }
                        deferred.resolve(result.data.data);
                    });
                    return deferred.promise;
                }
                //  이슈 타입에 있는 템플릿을 적용한다.
                function setIssueTypeTemplate() {
                    for (var count in $scope.vm.issueTypes) {
                        var issueType = $scope.vm.issueTypes[count];
                        if ($scope.vm.form.issueTypeId === String(issueType.id)) {
                            //  템플릿이 작성되어 있는지 확인
                            if ($rootScope.isDefined(issueType.description)) {
                                //  이슈 내용이 작성되어 있지 않으면 바로 템플릿 적용
                                if (!$rootScope.isDefined($scope.vm.form.description)) {
                                    $scope.vm.form.description = issueType.description;
                                }
                                else {
                                    //  이미 내용이 작성되어 있으면 확인 후 적용
                                    SweetAlert.swal({
                                            title : $filter("translate")("issue.applyTemplate"), // 템플릿 적용하기
                                            text : $filter("translate")("issue.issueContentIsWrittenApplyTheTemplate"), // 이슈 내용이 작성되어 있습니다. 템플릿을 적용하겠습니까? 템플릿이 적용되면 이미 작성된 내용이 사라집니다.
                                            type : "warning",
                                            showCancelButton : true,
                                            confirmButtonColor : "#DD6B55",
                                            confirmButtonText : $filter("translate")("issue.applyTemplate"), // 템플릿 적용하기
                                            cancelButtonText : $filter("translate")("common.cancel"), // 취소
                                            closeOnConfirm : false,
                                            closeOnCancel : true
                                        },
                                        function (isConfirm) {
                                            SweetAlert.close();
                                            if (isConfirm) {
                                                $scope.vm.form.description = issueType.description;
                                            }
                                        });
                                }
                            }
                            break;
                        }
                    }
                }
                function getPartners() {
                    if($scope.vm.form.issueTypeId === ""){
                        $scope.vm.form.issueTypeId = $rootScope.issueTypeMenu.id
                    }
                    var content = {
                        issueTypeId : $scope.vm.form.issueTypeId,
                    };
                    Issue.findPartners($resourceProvider.getContent(
                        content,
                        $resourceProvider.getPageContent(0, 1))).then(function (result) {
                        if (result.data.message.status === "success") {
                            $scope.vm.partnerVos = result.data.data;
                        }
                    });
                }
                //  최초 실행
                function startExecute() {
                    var promises = {
                        getIssueTypes : $scope.fn.getIssueTypes(),
                        getPriorities : $scope.fn.getPriorities(),
                        getSeverities : $scope.fn.getSeverities(),
                        getPartners : $scope.fn.getPartners()
                    };
                    $q.all(promises).then(function (results) {
                        // 현재 프로젝트 설정
                        if ($rootScope.workProject != null && $rootScope.workProject.id > -1) {
                            $scope.vm.projectName = $rootScope.workProject.name;
                            $scope.vm.form.projects = [];
                            $scope.vm.form.projects.push($rootScope.workProject);
                        }
                        // 현재 이슈타입 유형 설정
                        var id = $rootScope.getCurrentIssueTypeId();
                        if (id != null) {
                            $scope.vm.form.issueTypeId = id.toString();
                        }
                        $log.debug("promises 결과 ", results);
                    });
                }
                $scope.fn.startExecute();
            }]);
    });
src/main/webapp/scripts/app/issue/issueAddRelation.controller.js
@@ -406,6 +406,7 @@
                    $scope.vm.companyUrl = result[0].url;
                    $scope.vm.companyMemo = result[0].memo;
                    $scope.vm.ispId = ispFieldVo.id;
                    $scope.vm.ispName = ispFieldVo.name;
                    $scope.vm.ispCode = ispFieldVo.code;
                    $scope.vm.ispManager = ispFieldVo.manager;
@@ -414,6 +415,7 @@
                    $scope.vm.ispUrl = ispFieldVo.url;
                    $scope.vm.ispMemo = ispFieldVo.memo;
                    $scope.vm.hostingId = hostingFieldVo.id;
                    $scope.vm.hostingName = hostingFieldVo.name;
                    $scope.vm.hostingCode = hostingFieldVo.code;
                    $scope.vm.hostingManager = hostingFieldVo.manager;
src/main/webapp/scripts/app/issue/issueList.controller.js
@@ -24,6 +24,7 @@
                    add : add,   //  이슈 생성
                    modify : modify,    //  이슈 수정
                    addRelationIssueForm : addRelationIssueForm,    //  연관 이슈 추가
                    addDownIssueForm : addDownIssueForm,    //  하위 이슈 추가
                    modifyMultiIssueStatus : modifyMultiIssueStatus,    //  이슈 다중 상태 변경
                    removes : removes,  //  이슈 삭제
                    addIssueTableConfig : addIssueTableConfig,    //  이슈 목록 테이블 설정
@@ -801,6 +802,22 @@
                    });
                }
                function addDownIssueForm(id) {
                    $uibModal.open({
                        templateUrl : 'views/issue/issueAddDown.html',
                        size : "lg",
                        controller : 'issueAddDownController',
                        backdrop : 'static',
                        resolve : {
                            parameter : function () {
                                return {
                                    id : id,
                                };
                            }
                        }
                    });
                }
                //  이슈 삭제
                function removes() {
                    var removeIds = [];
src/main/webapp/scripts/app/issue/issueModify.controller.js
@@ -382,6 +382,7 @@
                    $scope.vm.companyUrl = result[0].url;
                    $scope.vm.companyMemo = result[0].memo;
                    $scope.vm.ispId = ispFieldVo.id;
                    $scope.vm.ispName = ispFieldVo.name;
                    $scope.vm.ispCode = ispFieldVo.code;
                    $scope.vm.ispManager = ispFieldVo.manager;
@@ -390,6 +391,7 @@
                    $scope.vm.ispUrl = ispFieldVo.url;
                    $scope.vm.ispMemo = ispFieldVo.memo;
                    $scope.vm.hostingId = hostingFieldVo.id;
                    $scope.vm.hostingName = hostingFieldVo.name;
                    $scope.vm.hostingCode = hostingFieldVo.code;
                    $scope.vm.hostingManager = hostingFieldVo.manager;
src/main/webapp/scripts/components/issue/issue.service.js
@@ -50,7 +50,6 @@
                    return response;
                });
            },
            relAdd : function (conditions) {
                conditions.url = "issue/relIssueAdd";
                return $upload.upload(conditions).progress(function (evt) {
@@ -62,6 +61,17 @@
                    return response;
                });
            },
            downAdd : function (conditions) {
                conditions.url = "issue/downIssueAdd";
                return $upload.upload(conditions).progress(function (evt) {
                    //  파일 업로드 진행율을 표시해준다.
                    fileUploadProgress(evt);
                }).then(function (response) {
                    $log.debug("이슈 생성 결과 : ", response);
                    return response;
                });
            },
            modifyParentIssue : function (conditions) {
                return $http.post("issue/modifyParentIssue", conditions).then(function (response) {
                    $log.debug("상위 일감 수정 결과 : ", response);
src/main/webapp/scripts/main.js
@@ -179,7 +179,8 @@
        /*  이슈 */
        'issueRoute' : 'app/issue/issue',  //  이슈에 관련된 route 정보
        'issueAddController' : 'app/issue/issueAdd.controller',  //  이슈 생성 컨트롤러
        'issueAddRelationController' : 'app/issue/issueAddRelation.controller',  //  이슈 생성 컨트롤러
        'issueAddRelationController' : 'app/issue/issueAddRelation.controller',  //  연관이슈 생성 컨트롤러
        'issueAddDownController' : 'app/issue/issueAddDown.controller',  //  하위이슈 생성 컨트롤러
        'issueModifyController' : 'app/issue/issueModify.controller',  //  이슈 수정 컨트롤러
        'issueDetailController' : 'app/issue/issueDetail.controller',  //  이슈 상세 컨트롤러
        'issueListController' : 'app/issue/issueList.controller',   //  이슈 목록 컨트롤러
@@ -508,7 +509,8 @@
    'autoCompleteController',
    'userInviteController',
    'issueAddController',   //  이슈 만들기에서 사용
    'issueAddRelationController',   //  이슈 만들기에서 사용
    'issueAddRelationController',   //  연관 이슈 만들기에서 사용
    'issueAddDownController',   //하위 이슈 만들기에서 사용
    'issueService', //  이슈 만들기에서 사용
    'issueTypeService', //  이슈 만들기에서 사용
    'priorityService',  //  이슈 만들기에서 사용
src/main/webapp/views/issue/issueAddDown.html
New file
@@ -0,0 +1,767 @@
<div class="formModal">
    <div class="modal-header faded smaller">
        <div class="modal-title">
            <strong>하위 이슈 추가</strong>
        </div>
        <button aria-label="Close" class="close" type="button" ng-click="fn.cancel()">
            <span aria-hidden="true"> &times;</span>
        </button>
    </div>
    <div class="modal-body">
        <form role="form" name="issueAddDownForm">
            <div class="form-group mb10">
                <label for="issueAddDownForm1" class="issue-label"><span translate="issue.issueTitle">이슈 제목</span> <code
                        class="highlighter-rouge">*</code></label>
                <input id="issueAddDownForm1"
                       class="form-control input-sm"
                       ng-model="vm.form.title"
                       name="title"
                       required
                       kr-input
                       maxlength="300"
                       autocomplete="off"
                       autofocus
                       owl-auto-focus>
                <small class="help-block form-text text-danger"
                       ng-if="issueAddDownForm.title.$touched && issueAddDownForm.title.$error.required"
                       translate="issue.requireIssueTitle">이슈 제목을 입력하세요.
                </small>
            </div>
            <div class="row">
                <div class="col-lg-4">
                    <div class="form-group mb10">
                        <label class="issue-label"> <span translate="common.project">프로젝트</span> <code
                                class="highlighter-rouge">*</code></label>
                        <js-autocomplete-single data-input-name="project"
                                                selected-model="vm.form.projects"
                                                search="vm.projectName"
                                                source="fn.getProjectList(vm.projectName, vm.form.projects, vm.autoCompletePage.project.page, fn.getProjectListCallBack)"
                                                page="vm.autoCompletePage.project.page"
                                                total-page="vm.autoCompletePage.project.totalPage"
                                                input-disabled="vm.form.projects != null ? vm.form.projects.length > 0 : false"
                                                translation-texts="{ empty : 'common.emptyProject' }"
                                                extra-settings="{ displayProp : 'name' , idProp : 'id', imageable : false, imagePathProp : '',
                                                type : '', maxlength : 200, autoResize : false, stopRemoveBodyEvent : true }"></js-autocomplete-single>
                    </div>
                </div>
                <div class="col-lg-8 bdl1">
                    <div class="row">
                        <div class="col-md-4">
                            <div class="form-group mb10">
                                <label for="issueAddDownForm4" class="issue-label"> <span
                                        translate="issue.issueType">이슈 타입</span>
                                    <code class="highlighter-rouge">*</code></label>
                                <select id="issueAddDownForm4"
                                        name="issueType"
                                        class="form-control input-sm issue-select-label"
                                        ng-model="vm.form.issueTypeId"
                                        ng-change="fn.getIssueTypeCustomFields()"
                                        ng-style="{ 'color' : fn.getOptionColor(vm.issueTypes, vm.form.issueTypeId) }"
                                        required
                                        disabled>
                                    <option value="" translate="common.selectTarget" ng-style="{ 'color' : '#353535' }"><span
                                            translate="common.selectTarget">대상 선택</span>
                                    </option>
                                    <option ng-repeat="issueType in vm.issueTypes"
                                            ng-style="{ 'color' : issueType.color, 'font-weight': 600 }"
                                            value="{{issueType.id}}">●&nbsp;{{issueType.name}}
                                    </option>
                                </select>
                            </div>
                        </div>
                        <div class="col-md-4">
                            <div class="form-group mb10">
                                <label for="issueAddDownForm2" class="issue-label">
                                    <span translate="common.priority">우선 순위</span>
                                    <code class="highlighter-rouge">*</code>
                                </label>
                                <select id="issueAddDownForm2"
                                        name="priority"
                                        class="form-control input-sm issue-select-label"
                                        ng-model="vm.form.priorityId"
                                        ng-style="{ 'color' : fn.getOptionColor(vm.priorities, vm.form.priorityId) }"
                                        required>
                                    <option value="" translate="common.selectTarget" ng-style="{ 'color' : '#353535' }">
                                        <span translate="common.selectTarget">대상 선택</span>
                                    </option>
                                    <option ng-repeat="priority in vm.priorities"
                                            ng-style="{ 'color' : priority.color, 'font-weight': 600 }"
                                            value="{{priority.id}}"
                                            translate="{{priority.name}}">
                                    </option>
                                </select>
                            </div>
                        </div>
                        <div class="col-md-4">
                            <div class="form-group mb10">
                                <label for="issueAddDownForm3" class="issue-label"> <span
                                        translate="common.importance">중요도</span> <code
                                        class="highlighter-rouge">*</code></label>
                                <select id="issueAddDownForm3"
                                        name="severity"
                                        class="form-control input-sm issue-select-label"
                                        ng-model="vm.form.severityId"
                                        ng-style="{ 'color' : fn.getOptionColor(vm.severities, vm.form.severityId) }"
                                        required>
                                    <option value="" translate="common.selectTarget" ng-style="{ color : '#353535' }">
                                        <span translate="common.selectTarget">대상 선택</span>
                                    </option>
                                    <option ng-repeat="severity in vm.severities"
                                            ng-style="{ color : severity.color, 'font-weight': 600 }"
                                            value="{{severity.id}}"
                                            translate="{{severity.name}}">
                                    </option>
                                </select>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <hr>
            <div class="row">
                <div class="col-lg-4">
                    <div class="form-group mb10">
                        <label class="issue-label"> <span translate="common.assigneeTeam">담당부서</span> </label>
                        <js-autocomplete-multi data-input-name="departments"
                                               selected-model="vm.form.departments"
                                               search="vm.departmentName"
                                               source="fn.getIssueDepartmentList(vm.form.issueTypeId, vm.departmentName, vm.form.departments)"
                                               input-disabled="false"
                                               translation-texts="{ count : 'common.userNum', empty : 'common.emptyProjectDepartment'}"
                                               extra-settings="{ displayProp : 'byName' , idProp : 'id', imageable : false, maxlength : 100, autoResize : true}"></js-autocomplete-multi>
                        <div class="select3-selection__choicediv mt-10">
                            <span class="select3-selection__choice" ng-repeat="department in vm.form.departments">
                                <span>{{department.byName}}</span>
                                <span class="select3-selection__choice__remove" ng-click="fn.removeDepartment($index)">×</span>
                            </span>
                        </div>
                    </div>
                </div>
                <div class="col-lg-4">
                    <div class="form-group mb10">
                        <label for="issueAddDownForm5" class="issue-label"> <span translate="common.period">기간</span></label>
                        <input id="issueAddDownForm5"
                               tabindex="-1"
                               type="text"
                               readonly
                               class="form-control cursor"
                               placeholder="{{'issue.clickToSelectDate' | translate}}"
                               ng-model="vm.form.startCompleteDateRange"
                               modal-form-auto-scroll
                               date-format="YYYY-MM-DD"
                               parent-el="'#createdWidget'"
                               date-range-picker>
                        <div class="row">
                            <div class="col-xs-12">
                                <div id="createdWidget" class="bootstrap-datepicker"></div>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="col-lg-4">
                    <div class="form-group mgb5">
                        <label class="issue-label"><span translate="common.attachFiles">파일 첨부</span></label>
                        <div class="filebox input-group">
                            <input class="upload-name form-control"
                                   placeholder="{{'users.pleaseSelectFile' | translate}}"
                                   tabindex="-1"
                                   disabled="disabled">
                            <label for="uploadFileField"><span
                                    translate="common.selectFile">파일선택</span></label>
                            <input id="uploadFileField"
                                   tabindex="-1"
                                   type="file"
                                   class="form-control"
                                   multiple
                                   ng-file-select="fn.onFileSelect($files)">
                        </div>
                        <div class="select2-selection__choicediv">
                            <div class="select2-selection__choice2" ng-repeat="file in vm.form.files">
                                <div class="select2-selection__choice2__remove" ng-click="fn.removeUploadFile($index)">
                                    ×
                                </div>
                                <div class="ssg-items ssg-items-blocks">
                                    <div class="ssg-item">
                                        <div class="item-icon">
                                            <!--    문서  -->
                                            <i class="os-icon os-icon-file-text" ng-if="file.fileType == 'DOC'"></i>
                                            <!--    미디어  -->
                                            <i class="os-icon os-icon-film" ng-if="file.fileType == 'MEDIA'"></i>
                                            <!--    이미지(업로드 전)  -->
                                            <i class="os-icon os-icon-documents-07"
                                               ng-if="file.fileType == 'IMAGE'"></i>
                                            <!--    기타  -->
                                            <i class="os-icon os-icon-ui-51" ng-if="file.fileType == 'ETC'"></i>
                                        </div>
                                        <div class="item-name">
                                            <small>{{file.name}}</small>
                                        </div>
                                        <div class="item-amount">
                                            ({{file.size/1024/1024 | number:2}} MB)
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <hr>
            <div class="row">
                <div class="col-lg-4 fontcolor_green">
                    <label class="issue-label"><span class="fontcolor_green" translate="common.detectingInfo">탐지정보</span></label>
                </div>
            </div>
            <div class="col-lg-12">
                <div class="row">
                    <div class="col-md-12">
                        <div class="form-group mgb5" ng-show="vm.form.issueCustomFields.length < 1">
                            <label class="issue-label" translate="issue.notIssueTypeCustomFields">이슈 타입에 연결된 사용자 정의
                                필드가 없습니다.</label>
                        </div>
                    </div>
                    <div class="col-md-4" ng-repeat="issueCustomField in vm.form.issueCustomFields">
                        <div class="form-group mgb5">
                            <label class="issue-label">{{issueCustomField.customFieldVo.name}}</label>
                            <div ng-switch on="issueCustomField.customFieldVo.customFieldType">
                                <!-- 기본 입력 -->
                                <div ng-switch-when="INPUT">
                                    <input type="text" class="form-control input-sm"
                                           name="input"
                                           ng-model="issueCustomField.useValues"
                                           maxlength="100"
                                           autocomplete="off"
                                           kr-input
                                           ng-required="issueCustomField.fieldOption == '01' || issueCustomField.customFieldVo.requiredData == 'Y'">
                                    <small class="help-block form-text text-danger"
                                           ng-show="issueCustomField.customFieldVo.requiredData == 'Y'"
                                           ng-if="issueAddDownForm.input.$error.required"
                                           translate="issue.pleaseEnterIssueTypeCustomFields">해당 사용자정의필드는 필수 입력 값 입니다.
                                    </small>
                                </div>
                                <div ng-switch-when="NUMBER">
                                    <input type="text" class="form-control input-sm"
                                           name="number"
                                           ng-model="issueCustomField.useValues"
                                           maxlength="100"
                                           autocomplete="off"
                                           kr-input
                                           ng-required="issueCustomField.fieldOption == '01' || issueCustomField.customFieldVo.requiredData == 'Y'">
                                    <small class="help-block form-text text-danger"
                                           ng-show="issueCustomField.customFieldVo.requiredData == 'Y'"
                                           ng-if="issueAddDownForm.number.$error.required"
                                           translate="issue.pleaseEnterIssueTypeCustomFields">해당 사용자 정의 필드는 필수 입력 값 입니다.
                                    </small>
                                </div>
                                <div ng-switch-when="DATETIME">
                                    <input type="text" class="form-control input-sm"
                                           name="dateTime"
                                           ng-model="issueCustomField.useValues"
                                           maxlength="100"
                                           autocomplete="off"
                                           kr-input
                                           ng-required="issueCustomField.fieldOption == '01' || issueCustomField.customFieldVo.requiredData == 'Y'">
                                    <small class="help-block form-text text-danger"
                                           ng-show="issueCustomField.customFieldVo.requiredData == 'Y'"
                                           ng-if="issueAddDownForm.dateTime.$error.required"
                                           translate="issue.pleaseEnterIssueTypeCustomFields">해당 사용자 정의 필드는 필수 입력 값 입니다.
                                    </small>
                                </div>
                                <div ng-switch-when="IP_ADDRESS">
                                    <input type="text" class="form-control input-sm"
                                           name="ipAddress"
                                           ng-model="issueCustomField.useValues"
                                           maxlength="100"
                                           autocomplete="off"
                                           kr-input
                                           ng-required="issueCustomField.fieldOption == '01' || issueCustomField.customFieldVo.requiredData == 'Y'">
                                    <small class="help-block form-text text-danger"
                                           ng-show="issueCustomField.customFieldVo.requiredData == 'Y'"
                                           ng-if="issueAddDownForm.ipAddress.$error.required"
                                           translate="issue.pleaseEnterIssueTypeCustomFields">해당 사용자 정의 필드는 필수 입력 값 입니다.
                                    </small>
                                </div>
                                <div ng-switch-when="SITE">
                                    <input type="text" class="form-control input-sm"
                                           name="site"
                                           ng-model="issueCustomField.useValues"
                                           maxlength="100"
                                           autocomplete="off"
                                           kr-input
                                           ng-required="issueCustomField.fieldOption == '01' || issueCustomField.customFieldVo.requiredData == 'Y'">
                                    <small class="help-block form-text text-danger"
                                           ng-show="issueCustomField.customFieldVo.requiredData == 'Y'"
                                           ng-if="issueAddDownForm.site.$error.required"
                                           translate="issue.pleaseEnterIssueTypeCustomFields">해당 사용자 정의 필드는 필수 입력 값 입니다.
                                    </small>
                                </div>
                                <div ng-switch-when="TEL">
                                    <input type="text" class="form-control input-sm"
                                           name="tel"
                                           ng-model="issueCustomField.useValues"
                                           maxlength="100"
                                           autocomplete="off"
                                           kr-input
                                           ng-required="issueCustomField.fieldOption == '01' || issueCustomField.customFieldVo.requiredData == 'Y'">
                                    <small class="help-block form-text text-danger"
                                           ng-show="issueCustomField.customFieldVo.requiredData == 'Y'"
                                           ng-if="issueAddDownForm.tel.$error.required"
                                           translate="issue.pleaseEnterIssueTypeCustomFields">해당 사용자 정의 필드는 필수 입력 값 입니다.
                                    </small>
                                </div>
                                <!-- 단일 셀렉트 -->
                                <div ng-switch-when="SINGLE_SELECT">
                                    <select class="form-control input-sm issue-select-label"
                                            name="singleSelect"
                                            ng-required="issueCustomField.fieldOption == '01' || issueCustomField.customFieldVo.requiredData == 'Y'"
                                            ng-model="issueCustomField.useValues">
                                        <option value="" value="" translate="common.choose">선택하세요.</option>
                                        <option ng-repeat="customFieldValueVo in issueCustomField.customFieldVo.customFieldValueVos"
                                                value="{{customFieldValueVo.value}}"
                                                ng-selected="$root.selectOption(issueCustomField.useValues, customFieldValueVo.value)">
                                            {{customFieldValueVo.value}}
                                        </option>
                                    </select>
                                    <small class="help-block form-text text-danger"
                                           ng-show="issueCustomField.customFieldVo.requiredData == 'Y'"
                                           ng-if="issueAddDownForm.singleSelect.$error.required"
                                           translate="issue.pleaseEnterIssueTypeCustomFields">해당 사용자 정의 필드는 필수 입력 값 입니다.
                                    </small>
                                </div>
                                <!-- 멀티 셀렉트 -->
                                <div ng-switch-when="MULTI_SELECT">
                                    <ng-dropdown-multiselect class="multiSelect cursor"
                                                             name="multiSelect"
                                                             ng-required="issueCustomField.customFieldVo.requiredData == 'Y'"
                                                             data-input-name=""
                                                             modal-form-auto-scroll
                                                             selected-model="issueCustomField.useValues"
                                                             extra-settings="{ 'idProp' : 'value', 'externalIdProp' : 'value', 'displayProp' : 'value', 'stringTypeOption' : 'true', stopRemoveBodyEvent : true }"
                                                             options="issueCustomField.customFieldVo.customFieldValueVos"></ng-dropdown-multiselect>
                                    <small class="help-block form-text text-danger"
                                           ng-show="issueCustomField.customFieldVo.requiredData == 'Y'"
                                           ng-if="issueAddDownForm.multiSelect.$error.required"
                                           translate="issue.pleaseEnterIssueTypeCustomFields">해당 사용자 정의 필드는 필수 입력 값 입니다.
                                    </small>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <hr>
            <div ng-show="fn.containsPartner('업체')" class="row">
                <div class="col-lg-8 fontcolor_green">
                    <label class="issue-label"><span class="fontcolor_green" translate="companyField.info">업체정보</span>
                        &nbsp;&nbsp;<span class="select3-selection__choice" style="position: relative; bottom: 2px;"><code class="highlighter-rouge">*</code>&nbsp;
                            &nbsp;업체 이름 클릭시 선택된 이름의 업체 정보가 조회되며, 업체정보에서 추가한 ISP, 호스팅 정보를 불러옵니다.</span>
                    </label>
                </div>
            </div>
            <div ng-show="fn.containsPartner('업체')" class="row">
                <div class="col-lg-4">
                    <div class="form-group mb10">
                        <label class="issue-label"> <span translate="companyField.name">업체이름</span> </label>
                        <js-autocomplete-single data-input-name="issueCompanyField"
                                                selected-model="vm.form.issueCompanyFields"
                                                search="vm.companyName"
                                                source="fn.getIssueCompanyFieldList(vm.companyName, vm.form.issueCompanyFields, vm.autoCompletePage.companyField.page, fn.getIssueCompanyFieldListCallBack)"
                                                page="vm.autoCompletePage.companyField.page"
                                                total-page="vm.autoCompletePage.companyField.totalPage"
                                                input-disabled="false"
                                                translation-texts="{ empty : 'common.emptyCompany' }"
                                                broad-cast="companyFieldEvent"
                                                extra-settings="{ displayProp : 'name' , idProp : 'id', imageable : false, imagePathProp : '',
                                                type : '', maxlength : 200, autoResize : false, stopRemoveBodyEvent : true }"></js-autocomplete-single>
                    </div>
                </div>
                <div class="col-lg-4">
                    <div class="form-group mb10">
                        <label for="companyFieldManagerAddForm" class="issue-label"><span translate="companyField.manager">담당자</span></label>
                        <input id="companyFieldManagerAddForm"
                               name="companyManager"
                               type="text"
                               class="form-control"
                               autofocus
                               kr-input
                               input-regex="[^a-zA-Z0-9 가-힣ㄱ-ㅎㅏ-ㅣ\u318D\u119E\u11A2\u2022\u2025a\u00B7\uFE55]"
                               autocomplete="off"
                               ng-model="vm.companyManager"
                               ng-maxlength="100"
                               maxlength="100"
                        >
                    </div>
                </div>
                <div class="col-lg-4">
                    <div class="form-group mgb5">
                        <label for="companyFieldTelAddForm" class="issue-label"><span translate="companyField.tel">전화번호</span></label>
                        <input id="companyFieldTelAddForm"
                               name="companyTel"
                               type="text"
                               class="form-control"
                               kr-input
                               ng-pattern="/^\d{2,3}-\d{3,4}-\d{4}$/"
                               autocomplete="off"
                               ng-model="vm.companyTel"
                               maxlength="20">
                        <div ng-show="issueAddDownForm.companyTel.$error.pattern" class="help-block form-text text-danger"
                             translate="companyField.invalidTelFormat">전화번호 형식이 맞지 않습니다. xxx-xxx-xxxx 형식으로 입력하세요.
                        </div>
                    </div>
                </div>
                <div class="col-lg-4">
                    <div class="form-group mgb5">
                        <label for="companyFieldEmailAddForm" class="issue-label"><span translate="companyField.email">이메일</span></label>
                        <input id="companyFieldEmailAddForm"
                               name="companyEmail"
                               type="email"
                               class="form-control"
                               autocomplete="off"
                               maxLength="50"
                               ng-model="vm.companyEmail"
                               kr-input
                               ng-pattern="/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/">
                        <div ng-show="issueAddDownForm.companyEmail.$error.pattern" class="help-block form-text text-danger"
                             translate="users.invalidEmailFormat">이메일 형식이 맞지 않습니다.
                        </div>
                    </div>
                </div>
                <div class="col-lg-4">
                    <div class="form-group mgb5">
                        <label for="companyFieldUrlAddForm" class="issue-label"><span translate="companyField.url">url</span></label>
                        <input id="companyFieldUrlAddForm"
                               name="companyUrl"
                               type="text"
                               class="form-control"
                               kr-input
                               autocomplete="off"
                               ng-maxlength="200"
                               ng-model="vm.companyUrl"
                               maxlength="200">
                    </div>
                </div>
                <div class="col-lg-4">
                    <div class="form-group mgb5">
                        <label for="companyFieldDescAddForm" class="issue-label"><span translate="companyField.memo">비고</span></label>
                        <input id="companyFieldDescAddForm"
                               name="companyMemo"
                               type="text"
                               class="form-control"
                               kr-input
                               autocomplete="off"
                               ng-maxlength="200"
                               ng-model="vm.companyMemo"
                               maxlength="200">
                    </div>
                </div>
            </div>
            <p></p>
            <div ng-show="fn.containsPartner('ISP')" class="row">
                <div class="col-lg-8 fontcolor_green">
                    <label class="issue-label"><span class="fontcolor_green" translate="ispField.info">ISP 정보</span>
                        &nbsp;&nbsp;<!--<span class="select3-selection__choice" style="position: relative; bottom: 2px;"><code class="highlighter-rouge">*</code>&nbsp;&nbsp;ISP 이름 클릭시 선택된 이름의 ISP 정보가 조회됩니다.</span>-->
                    </label>
                </div>
            </div>
            <div ng-show="fn.containsPartner('ISP')"  class="row">
                <div class="col-lg-4">
                    <div class="form-group mb10">
                        <label class="issue-label"> <span translate="ispField.name">ISP 이름</span> </label>
                        <input name="ispName"
                               type="text"
                               class="form-control"
                               kr-input
                               autocomplete="off"
                               ng-model="vm.ispName"
                               ng-maxlength="100"
                               maxlength="100">
                        <!--<js-autocomplete-single data-input-name="ispField"
                                                selected-model="vm.form.issueIspFields"
                                                search="vm.ispName"
                                                source="fn.getIssueIspFieldList(vm.ispName, vm.form.issueIspFields, vm.autoCompletePage.ispField.page, fn.getIssueIspFieldListCallBack)"
                                                page="vm.autoCompletePage.ispField.page"
                                                total-page="vm.autoCompletePage.ispField.totalPage"
                                                input-disabled="false"
                                                translation-texts="{ empty : 'common.emptyIsp' }"
                                                broad-cast="ispFieldEvent"
                                                extra-settings="{ displayProp : 'name' , idProp : 'id', imageable : false, imagePathProp : '',
                                                type : '', maxlength : 200, autoResize : false, stopRemoveBodyEvent : true }"></js-autocomplete-single>-->
                    </div>
                </div>
                <div class="col-lg-4">
                    <div class="form-group mb10">
                        <label for="ispFieldCodeAddForm" class="issue-label"><span translate="ispField.code">코드</span></label>
                        <input id="ispFieldCodeAddForm"
                               name="ispCode"
                               type="text"
                               class="form-control"
                               kr-input
                               input-regex="[^a-zA-Z0-9 가-힣ㄱ-ㅎㅏ-ㅣ\u318D\u119E\u11A2\u2022\u2025a\u00B7\uFE55]"
                               autocomplete="off"
                               ng-model="vm.ispCode"
                               ng-maxlength="100"
                               maxlength="100">
                    </div>
                </div>
                <div class="col-lg-4">
                    <div class="form-group mb10">
                        <label for="ispFieldManagerAddForm" class="issue-label"><span translate="ispField.manager">담당자</span></label>
                        <input id="ispFieldManagerAddForm"
                               name="ispManager"
                               type="text"
                               class="form-control"
                               kr-input
                               input-regex="[^a-zA-Z0-9 가-힣ㄱ-ㅎㅏ-ㅣ\u318D\u119E\u11A2\u2022\u2025a\u00B7\uFE55]"
                               autocomplete="off"
                               ng-model="vm.ispManager"
                               ng-maxlength="100"
                               maxlength="100">
                    </div>
                </div>
                <div class="col-lg-4">
                    <div class="form-group mgb5">
                        <label for="ispFieldTelAddForm" class="issue-label"><span translate="ispField.tel">전화번호</span></label>
                        <input id="ispFieldTelAddForm"
                               name="ispTel"
                               type="text"
                               class="form-control"
                               kr-input
                               ng-pattern="/^\d{2,3}-\d{3,4}-\d{4}$/"
                               autocomplete="off"
                               ng-model="vm.ispTel"
                               maxlength="20">
                        <div ng-show="issueAddDownForm.ispTel.$error.pattern" class="help-block form-text text-danger"
                             translate="companyField.invalidTelFormat">전화번호 형식이 맞지 않습니다. xxx-xxx-xxxx 형식으로 입력하세요.
                        </div>
                    </div>
                </div>
                <div class="col-lg-4">
                    <div class="form-group mgb5">
                        <label for="ispFieldEmailAddForm" class="issue-label"><span translate="ispField.email">이메일</span></label>
                        <input id="ispFieldEmailAddForm"
                               name="ispEmail"
                               type="email"
                               class="form-control"
                               autocomplete="off"
                               maxLength="50"
                               ng-model="vm.ispEmail"
                               kr-input
                               ng-pattern="/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/">
                        <div ng-show="issueAddDownForm.ispEmail.$error.pattern" class="help-block form-text text-danger"
                             translate="users.invalidEmailFormat">이메일 형식이 맞지 않습니다.
                        </div>
                    </div>
                </div>
                <div class="col-lg-4">
                    <div class="form-group mgb5">
                        <label for="ispFieldUrlAddForm" class="issue-label"><span translate="companyField.url">url</span></label>
                        <input id="ispFieldUrlAddForm"
                               name="ispUrl"
                               type="text"
                               class="form-control"
                               kr-input
                               autocomplete="off"
                               ng-maxlength="200"
                               ng-model="vm.ispUrl"
                               maxlength="200">
                    </div>
                </div>
                <div class="col-lg-4">
                    <div class="form-group mgb5">
                        <label for="ispFieldDescAddForm" class="issue-label"><span translate="ispField.memo">비고</span></label>
                        <input id="ispFieldDescAddForm"
                               name="ispMemo"
                               type="text"
                               class="form-control"
                               kr-input
                               autocomplete="off"
                               ng-model="vm.ispMemo"
                               ng-maxlength="200"
                               maxlength="200">
                    </div>
                </div>
            </div>
            <p></p>
            <div ng-show="fn.containsPartner('호스팅')" class="row">
                <div class="col-lg-8 fontcolor_green">
                    <label class="issue-label"><span class="fontcolor_green" translate="hostingField.info">호스팅 정보</span>
                        &nbsp;&nbsp;<!--<span class="select3-selection__choice" style="position: relative; bottom: 2px;"><code class="highlighter-rouge">*</code>&nbsp;&nbsp;호스팅 이름 클릭시 선택된 이름의 호스팅 정보가 조회됩니다.</span>-->
                    </label>
                </div>
            </div>
            <div ng-show="fn.containsPartner('호스팅')" class="row">
                <div class="col-lg-4">
                    <div class="form-group mb10">
                        <label class="issue-label"> <span translate="hostingField.name">호스팅 이름</span> </label>
                        <input name="hostingName"
                               type="text"
                               class="form-control"
                               kr-input
                               autocomplete="off"
                               ng-model="vm.hostingName"
                               ng-maxlength="100"
                               maxlength="100">
                        <!--<js-autocomplete-single data-input-name="hostingField"
                                                selected-model="vm.form.issueHostingFields"
                                                search="vm.hostingName"
                                                source="fn.getIssueHostingFieldList(vm.hostingName, vm.form.issueHostingFields, vm.autoCompletePage.hostingField.page, fn.getIssueHostingFieldListCallBack)"
                                                page="vm.autoCompletePage.hostingField.page"
                                                total-page="vm.autoCompletePage.hostingField.totalPage"
                                                input-disabled="false"
                                                translation-texts="{ empty : 'common.emptyHosting' }"
                                                broad-cast="hostingFieldEvent"
                                                extra-settings="{ displayProp : 'name' , idProp : 'id', imageable : false, imagePathProp : '',
                                                type : '', maxlength : 200, autoResize : false, stopRemoveBodyEvent : true }"></js-autocomplete-single>-->
                    </div>
                </div>
                <div class="col-lg-4">
                    <div class="form-group mb10">
                        <label for="hostingCodeManagerAdd" class="issue-label"><span translate="hostingField.code">담당자</span></label>
                        <input id="hostingCodeManagerAdd"
                               name="hostingCode"
                               type="text"
                               class="form-control"
                               kr-input
                               input-regex="[^a-zA-Z0-9 가-힣ㄱ-ㅎㅏ-ㅣ\u318D\u119E\u11A2\u2022\u2025a\u00B7\uFE55]"
                               autocomplete="off"
                               ng-model="vm.hostingCode"
                               ng-maxlength="100"
                               maxlength="100">
                    </div>
                </div>
                <div class="col-lg-4">
                    <div class="form-group mb10">
                        <label for="hostingFieldManagerAdd" class="issue-label"><span translate="hostingField.manager">담당자</span></label>
                        <input id="hostingFieldManagerAdd"
                               name="hostingManager"
                               type="text"
                               class="form-control"
                               kr-input
                               input-regex="[^a-zA-Z0-9 가-힣ㄱ-ㅎㅏ-ㅣ\u318D\u119E\u11A2\u2022\u2025a\u00B7\uFE55]"
                               autocomplete="off"
                               ng-model="vm.hostingManager"
                               ng-maxlength="100"
                               maxlength="100">
                    </div>
                </div>
                <div class="col-lg-4">
                    <div class="form-group mgb5">
                        <label for="hostingFieldTelAdd" class="issue-label"><span translate="hostingField.tel">전화번호</span></label>
                        <input id="hostingFieldTelAdd"
                               name="hostingTel"
                               type="text"
                               class="form-control"
                               kr-input
                               ng-pattern="/^\d{2,3}-\d{3,4}-\d{4}$/"
                               autocomplete="off"
                               ng-model="vm.hostingTel"
                               maxlength="20">
                        <div ng-show="issueAddDownForm.hostingTel.$error.pattern" class="help-block form-text text-danger"
                             translate="companyField.invalidTelFormat">전화번호 형식이 맞지 않습니다. xxx-xxx-xxxx 형식으로 입력하세요.
                        </div>
                    </div>
                </div>
                <div class="col-lg-4">
                    <div class="form-group mgb5">
                        <label for="hostingFieldEmailAdd" class="issue-label"><span translate="hostingField.email">이메일</span></label>
                        <input id="hostingFieldEmailAdd"
                               name="hostingEmail"
                               type="email"
                               class="form-control"
                               autocomplete="off"
                               maxLength="50"
                               ng-model="vm.hostingEmail"
                               kr-input
                               ng-pattern="/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/">
                        <div ng-show="issueAddDownForm.hostingEmail.$error.pattern" class="help-block form-text text-danger"
                             translate="users.invalidEmailFormat">이메일 형식이 맞지 않습니다.
                        </div>
                    </div>
                </div>
                <div class="col-lg-4">
                    <div class="form-group mgb5">
                        <label for="hostingFieldUrlAddForm" class="issue-label"><span translate="companyField.url">url</span></label>
                        <input id="hostingFieldUrlAddForm"
                               name="hostingUrl"
                               type="text"
                               class="form-control"
                               kr-input
                               autocomplete="off"
                               ng-maxlength="200"
                               ng-model="vm.hostingUrl"
                               maxlength="200">
                    </div>
                </div>
                <div class="col-lg-4">
                    <div class="form-group mgb5">
                        <label for="hostingFieldDescAdd" class="issue-label"><span translate="hostingField.memo">비고</span></label>
                        <input id="hostingFieldDescAdd"
                               name="hostingMemo"
                               type="text"
                               class="form-control"
                               kr-input
                               autocomplete="off"
                               ng-model="vm.hostingMemo"
                               ng-maxlength="200"
                               maxlength="200">
                    </div>
                </div>
            </div>
        </form>
        <hr>
        <div class="form-group mb10">
            <label class="issue-label"><span translate="common.content">내용</span></label>
            <summernote
                    class="summernote"
                    lang="ko-KR"
                    summer-note-auto-focus
                    ng-model="vm.form.description"
                    data-editor="vm.summerNote.editor"
                    data-editable="vm.summerNote.editable"
                    on-image-upload="fn.imageUpload(files)"
                    target=".note-editable"></summernote>
        </div>
    </div>
</div>
<div class="modal-footer buttons-on-right">
    <div class="pull-left">
        <label>
            <input class="form-control issue-continue-checkbox pull-left" type="checkbox" ng-model="vm.infiniteAdd"
                   tabindex="-1">
            <span translate="issue.continueCreateIssue">이 화면에서 이슈를 계속 생성합니다.</span>
        </label>
    </div>
    <button type="button" class="btn btn-md btn-grey" ng-click="fn.cancel()" tabindex="-1"><span
            translate="common.cancel">취소</span></button>
    <button type="button" class="btn btn-md btn-primary bold"
            js-short-cut
            js-short-cut-action="(fn.formCheck(issueAddDownForm.$invalid) || $root.spinner) ? null : fn.formSubmit()"
            ng-disabled="fn.formCheck(issueAddDownForm.$invalid)"
            ng-click="fn.formSubmit()"><span translate="common.save">저장</span>
    </button>
</div>
src/main/webapp/views/issue/issueDetail.html
@@ -574,6 +574,11 @@
                                    ng-if="vm.viewer.modifyPermissionCheck"
                                    ng-click="fn.addDownIssue()"
                                    translate="issue.addDownIssue">추가</button>
                            <button type="button" class="btn btn-sm btn-primary btn-roundRel  offset-1"
                                    ng-if="vm.viewer.modifyPermissionCheck"
                                    ng-click="fn.addDownIssueForm(vm.viewer.id)">
                                <i class="os-icon os-icon-plus"><span></span></i>
                            </button>
                        </div>
                    </div>