OWL ITS + 탐지시스템(인터넷 진흥원)
jhjang
2021-10-14 d680ff9fa4298ad3c0cd12f5f9d87f6c51110480
owl-local
476개 파일 추가됨
46855 ■■■■■ 파일 변경됨
src/main/java/kr/wisestone/owl/annotation/Viewer.java 13 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/common/ExcelConditionCheck.java 69 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/common/MessageAccessor.java 45 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/AppConfiguration.java 15 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/ApplicationContextProvider.java 21 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/AsyncConfiguration.java 82 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/CommonConfiguration.java 174 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/DataBaseConfiguration.java 175 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/ElasticSearchConfiguration.java 24 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/KafkaConfiguration.java 71 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/SecurityConfiguration.java 160 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/SessionConfiguration.java 73 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/ThymeleafConfiguration.java 33 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/WebMvcConfiguration.java 88 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/WebSocketStompConfiguration.java 30 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/kafka/KafkaReceiver.java 28 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/kafka/KafkaSender.java 22 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/persistence/aop/TransactionDefinitionInterceptor.java 68 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/persistence/routing/TransactionDefinitionRoutingDataSource.java 39 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/security/exception/LoginProcessingException.java 14 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/security/filter/AjaxSessionExpiredFilter.java 94 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/security/handler/AjaxAuthenticationEntryPoint.java 48 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/security/handler/AjaxAuthenticationFailureHandler.java 57 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/security/handler/AjaxAuthenticationSuccessHandler.java 60 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/security/handler/AjaxLogoutSuccessHandler.java 20 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/security/service/UserSecurityService.java 36 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/security/strategy/SecuritySessionExpiredStrategy.java 22 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/websocket/RedisPublisher.java 19 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/websocket/RedisSubscriber.java 34 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/websocket/WebSocketHandshakeHandler.java 16 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/config/websocket/WebSocketService.java 64 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/constant/Constants.java 16 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/constant/ElasticSearchConstants.java 14 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/constant/MailConstants.java 61 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/constant/MngPermission.java 63 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/constant/MsgConstants.java 209 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/AttachedFile.java 136 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/BaseEntity.java 76 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/CustomField.java 113 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/CustomFieldValue.java 52 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/Event.java 77 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/Faq.java 59 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/Guide.java 59 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/Issue.java 277 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/IssueComment.java 63 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/IssueCustomFieldValue.java 78 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/IssueHistory.java 75 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/IssueNumberGenerator.java 52 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/IssueRelation.java 56 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/IssueReservation.java 72 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/IssueRisk.java 77 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/IssueSearch.java 60 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/IssueStatus.java 136 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/IssueTableConfig.java 59 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/IssueType.java 98 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/IssueTypeCustomField.java 103 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/IssueUser.java 65 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/IssueVersion.java 88 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/ManageUser.java 80 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/Notice.java 47 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/Payment.java 83 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/PaymentHistory.java 139 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/Permission.java 80 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/Priority.java 86 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/Project.java 200 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/ProjectClosure.java 49 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/ProjectRole.java 111 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/ProjectRolePermission.java 55 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/ProjectRoleUser.java 56 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/Qna.java 47 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/ReservationDisableUser.java 47 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/Severity.java 85 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/SystemEmail.java 67 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/SystemRole.java 222 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/SystemRolePermission.java 92 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/SystemRoleUser.java 54 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/User.java 376 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/UserHistory.java 30 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/UserInvite.java 89 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/UserInviteProject.java 55 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/UserLikeIssue.java 62 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/UserWithDraw.java 42 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/UserWorkspace.java 91 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/Workflow.java 92 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/WorkflowStatus.java 119 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/WorkflowTransition.java 154 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/Workspace.java 343 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/enumType/AttachedType.java 10 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/enumType/CustomFieldType.java 10 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/enumType/EmailType.java 42 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/enumType/FileType.java 11 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/enumType/IssueHistoryType.java 11 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/enumType/IssueModifyType.java 16 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/enumType/IssueReservationType.java 11 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/enumType/IssueStatusType.java 10 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/enumType/ProjectType.java 10 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/enumType/ServiceType.java 9 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/enumType/SocialType.java 11 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/interceptor/AuditLogInterceptor.java 80 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/strategy/PrefixNamingStrategy.java 47 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/support/ZonedDateTimeTypeHandler.java 51 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/exception/OwlRuntimeException.java 52 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/initializer/AppInitializer.java 52 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/mapper/AttachedFileMapper.java 25 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/mapper/CustomFieldMapper.java 17 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/mapper/EventMapper.java 14 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/mapper/FaqMapper.java 14 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/mapper/GuideMapper.java 14 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/mapper/IssueCustomFieldValueMapper.java 24 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/mapper/IssueHistoryMapper.java 17 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/mapper/IssueMapper.java 41 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/mapper/IssueStatusMapper.java 17 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/mapper/IssueTypeMapper.java 17 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/mapper/IssueUserMapper.java 21 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/mapper/NoticeMapper.java 14 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/mapper/ProjectMapper.java 34 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/mapper/ProjectRoleUserMapper.java 19 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/mapper/QnaMapper.java 14 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/mapper/UserMapper.java 32 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/mapper/UserWorkspaceMapper.java 17 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/mapper/WidgetMapper.java 82 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/mapper/WorkflowMapper.java 17 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/mapper/WorkspaceMapper.java 13 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/monitor/Email.java 76 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/monitor/EmailAttachment.java 36 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/monitor/MailMonitor.java 226 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/AttachedFileRepository.java 10 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/CustomFieldRepository.java 16 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/CustomFieldValueRepository.java 7 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/EventRepository.java 15 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/FaqRepository.java 7 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/GuideRepository.java 15 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/IssueCommentRepository.java 14 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/IssueCustomFieldValueRepository.java 13 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/IssueHistoryRepository.java 13 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/IssueNumberGeneratorRepository.java 9 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/IssueRelationRepository.java 10 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/IssueRepository.java 7 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/IssueReservationRepository.java 10 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/IssueRiskRepository.java 7 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/IssueSearchRepository.java 8 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/IssueStatusRepository.java 18 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/IssueTableConfigRepository.java 9 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/IssueTypeCustomFieldRepository.java 15 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/IssueTypeRepository.java 18 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/IssueUserRepository.java 9 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/IssueVersionRepository.java 10 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/NoticeRepository.java 7 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/PaymentHistoryRepository.java 11 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/PaymentRepository.java 7 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/PermissionRepository.java 15 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/PriorityRepository.java 12 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/ProjectClosureRepository.java 14 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/ProjectRepository.java 19 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/ProjectRolePermissionRepository.java 7 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/ProjectRoleRepository.java 15 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/ProjectRoleUserRepository.java 14 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/QnaRepository.java 7 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/ReservationDisableUserRepository.java 7 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/SeverityRepository.java 12 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/SystemEmailRepository.java 11 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/SystemRolePermissionRepository.java 9 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/SystemRoleRepository.java 10 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/SystemRoleUserRepository.java 8 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/UserHistoryRepository.java 8 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/UserInviteProjectRepository.java 10 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/UserInviteRepository.java 15 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/UserRepository.java 32 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/UserWithDrawRepository.java 11 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/UserWorkspaceRepository.java 27 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/WorkflowRepository.java 21 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/WorkflowStatusRepository.java 22 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/WorkflowTransitionRepository.java 15 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/repository/WorkspaceRepository.java 19 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/scheduler/Scheduler.java 98 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/search/ElasticSearch.java 50 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/AbstractService.java 19 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/AttachedFileService.java 45 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/CustomFieldService.java 35 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/CustomFieldValueService.java 13 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/EventService.java 25 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/FaqService.java 27 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/GanttService.java 36 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/GuideService.java 25 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/IssueCommentService.java 20 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/IssueCustomFieldValueService.java 33 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/IssueHistoryService.java 56 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/IssueNumberGeneratorService.java 13 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/IssueRelationService.java 22 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/IssueReservationService.java 20 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/IssueRiskService.java 13 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/IssueSearchService.java 14 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/IssueService.java 69 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/IssueStatusService.java 51 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/IssueTableConfigService.java 14 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/IssueTypeCustomFieldService.java 21 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/IssueTypeService.java 39 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/IssueUserService.java 20 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/IssueVersionService.java 16 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/ManageUserService.java 23 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/NoticeService.java 26 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/PaymentHistoryService.java 15 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/PaymentService.java 29 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/PermissionService.java 13 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/PriorityService.java 21 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/ProjectClosureService.java 8 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/ProjectRolePermissionService.java 10 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/ProjectRoleService.java 15 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/ProjectRoleUserService.java 22 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/ProjectService.java 53 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/QnaService.java 24 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/ReservationDisableUserService.java 14 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/SeverityService.java 22 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/SystemEmailService.java 26 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/SystemRoleService.java 8 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/UserHistoryService.java 11 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/UserInviteProjectService.java 11 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/UserInviteService.java 19 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/UserService.java 84 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/UserWithDrawService.java 11 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/UserWorkspaceService.java 43 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/WidgetService.java 45 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/WorkflowService.java 40 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/WorkflowStatusService.java 29 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/WorkflowTransitionService.java 24 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/WorkspaceService.java 54 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/AbstractServiceImpl.java 93 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/AttachedFileServiceImpl.java 589 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/CustomFieldServiceImpl.java 408 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/CustomFieldValueServiceImpl.java 125 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/EventServiceImpl.java 158 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/FaqServiceImpl.java 167 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/GanttServiceImpl.java 115 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/GuideServiceImpl.java 161 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/IssueCommentServiceImpl.java 139 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/IssueCustomFieldValueServiceImpl.java 355 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/IssueHistoryServiceImpl.java 774 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/IssueNumberGeneratorServiceImpl.java 77 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/IssueRelationServiceImpl.java 121 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/IssueReservationServiceImpl.java 243 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/IssueRiskServiceImpl.java 84 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/IssueSearchServiceImpl.java 88 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/IssueServiceImpl.java 2336 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/IssueStatusServiceImpl.java 570 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/IssueTableConfigServiceImpl.java 89 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/IssueTypeCustomFieldServiceImpl.java 191 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/IssueTypeServiceImpl.java 353 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/IssueUserServiceImpl.java 128 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/IssueVersionServiceImpl.java 146 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/ManageUserServiceImpl.java 113 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/NoticeServiceImpl.java 156 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/PaymentHistoryServiceImpl.java 78 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/PaymentServiceImpl.java 789 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/PermissionServiceImpl.java 75 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/PriorityServiceImpl.java 99 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/ProjectClosureServiceImpl.java 30 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/ProjectRolePermissionServiceImpl.java 48 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/ProjectRoleServiceImpl.java 87 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/ProjectRoleUserServiceImpl.java 140 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/ProjectServiceImpl.java 1040 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/QnaServiceImpl.java 133 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/ReservationDisableUserServiceImpl.java 81 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/SeverityServiceImpl.java 99 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/SystemEmailServiceImpl.java 436 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/SystemRoleServiceImpl.java 29 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/UserHistoryServiceImpl.java 41 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/UserInviteProjectServiceImpl.java 55 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/UserInviteServiceImpl.java 322 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/UserServiceImpl.java 1427 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/UserWithDrawServiceImpl.java 49 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/UserWorkspaceServiceImpl.java 253 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/WidgetServiceImpl.java 1231 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/WorkflowServiceImpl.java 343 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/WorkflowStatusServiceImpl.java 209 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/WorkflowTransitionServiceImpl.java 316 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/WorkspaceServiceImpl.java 630 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/type/LikeType.java 7 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/util/ApplicationContextUtil.java 48 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/util/CommonUtil.java 922 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/util/ConvertUtil.java 552 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/util/DateUtil.java 166 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/util/ElasticSearchUtil.java 112 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/util/MapUtil.java 191 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/util/PageUtil.java 59 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/util/SecurityUtils.java 131 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/util/StringTemplateUtil.java 18 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/util/ThreadCounter.java 37 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/util/WebAppUtil.java 275 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/AttachedFileVo.java 72 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/BaseVo.java 55 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/CustomFieldValueVo.java 27 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/CustomFieldVo.java 78 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/EventVo.java 82 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/ExportExcelAttrVo.java 156 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/ExportExcelVo.java 64 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/FaqVo.java 64 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/GuideVo.java 64 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/IssueCommentVo.java 68 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/IssueCustomFieldValueVo.java 36 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/IssueHistoryVo.java 114 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/IssueRelationVo.java 40 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/IssueReservationVo.java 45 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/IssueStatusVo.java 135 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/IssueTypeCustomFieldVo.java 36 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/IssueTypeVo.java 63 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/IssueVersionVo.java 53 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/IssueVo.java 373 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/ManageUserVo.java 87 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/MessageVo.java 34 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/NoticeVo.java 45 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/PageVo.java 39 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/PaymentHistoryVo.java 82 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/PaymentVo.java 43 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/PermissionVo.java 54 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/PriorityVo.java 50 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/ProjectVo.java 150 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/QnaVo.java 45 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/ResMessageVo.java 53 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/ResPage.java 98 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/SeverityVo.java 50 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/UserInviteVo.java 45 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/UserVo.java 192 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/UserWorkspaceVo.java 54 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/WorkflowStatusVo.java 132 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/WorkflowTransitionVo.java 94 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/WorkflowVo.java 67 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/vo/WorkspaceVo.java 145 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/AttachedFileCondition.java 47 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/CustomFieldCondition.java 101 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/EventCondition.java 71 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/FaqCondition.java 71 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/GuideCondition.java 71 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/IssueCondition.java 396 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/IssueCustomFieldValueCondition.java 100 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/IssueHistoryCondition.java 54 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/IssueRelationCondition.java 54 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/IssueReservationCondition.java 35 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/IssueStatusCondition.java 132 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/IssueTypeCondition.java 72 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/IssueTypeCustomFieldCondition.java 36 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/IssueVersionCondition.java 60 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/NoticeCondition.java 62 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/ProjectCondition.java 168 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/QnaCondition.java 62 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/UserCondition.java 164 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/UserHistoryCondition.java 25 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/UserWorkspaceCondition.java 80 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/WidgetCondition.java 158 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/WorkflowCondition.java 73 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/condition/WorkflowStatusCondition.java 26 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/AttatchedFileController.java 54 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/BaseController.java 66 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/CustomFieldController.java 98 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/EventController.java 88 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/FaqController.java 89 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/GanttController.java 115 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/GuideController.java 89 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/IssueCommentController.java 58 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/IssueController.java 146 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/IssueHistoryController.java 37 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/IssueRelationController.java 61 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/IssueReservationController.java 46 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/IssueSearchController.java 47 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/IssueStatusController.java 133 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/IssueTableConfigController.java 46 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/IssueTypeController.java 97 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/IssueTypeCustomFieldController.java 51 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/IssueUserController.java 40 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/IssueVersionController.java 37 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/LanguageController.java 35 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/ManageUserController.java 56 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/NoticeController.java 89 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/PaymentController.java 79 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/PermissionController.java 33 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/PriorityController.java 34 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/ProjectController.java 161 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/QnaController.java 76 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/ReservationDisableUserController.java 35 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/SeverityController.java 34 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/SystemEmailController.java 35 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/UserController.java 224 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/UserHistoryController.java 46 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/UserInviteController.java 36 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/UserWorkspaceController.java 53 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/WebSocketSessionController.java 68 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/WidgetController.java 157 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/WorkflowController.java 100 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/WorkflowStatusController.java 66 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/controller/WorkspaceController.java 87 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/converter/FileHttpMessageConverter.java 75 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/CustomFieldForm.java 106 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/EventForm.java 111 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/FaqForm.java 93 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/GuideForm.java 93 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/IssueCommentForm.java 62 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/IssueForm.java 260 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/IssueReservationForm.java 54 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/IssueStatusChangeForm.java 87 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/IssueStatusForm.java 122 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/IssueTypeCustomFieldForm.java 62 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/IssueTypeForm.java 84 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/ManageUserForm.java 130 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/NoticeForm.java 75 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/PaymentForm.java 171 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/ProjectForm.java 157 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/QnaForm.java 75 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/UserForm.java 162 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/UserInviteForm.java 44 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/UserWorkspaceForm.java 37 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/WorkflowForm.java 144 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/WorkflowStatusForm.java 87 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/form/WorkspaceForm.java 50 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/resolver/OwlResponseEntityExceptionHandler.java 87 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/view/AbstractExcelView.java 143 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/view/ExcelView.java 333 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/web/view/FileDownloadView.java 66 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/META-INF/orm.xml 189 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/log4j2.xml 70 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/issueAddEmail.html 161 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/issueRemoveEmail.html 161 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/issueSendEmail.html 161 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/projectDefaultExcludeAndManagerIncludeEmail.html 81 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/projectDefaultExcludeEmail.html 83 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/projectDefaultIncludeEmail.html 83 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/projectManagerExcludeAndDefaultIncludeEmail.html 82 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/projectManagerExcludeEmail.html 82 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/projectManagerIncludeEmail.html 82 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/regularPaymentCancelByAccountingManagerEmail.html 98 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/regularPaymentCancelEmail.html 87 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/regularPaymentEmail.html 103 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/regularPaymentModifyEmail.html 106 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/totalStatisticsEmail.html 77 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/userJoinStatisticsEmail.html 84 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/userSearchPasswordEmail.html 67 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/userWithDrawEmail.html 45 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/workspaceExpireAlarmEmail.html 265 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/workspaceExpireEmail.html 180 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/workspaceInviteNewUserEmail.html 65 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/workspaceInviteSystemUserEmail.html 65 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/workspaceJoinEmail.html 281 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/workspaceMaxStorageExcessEmail.html 85 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mails/workspaceMaxUserExcessEmail.html 73 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/migration/V1_1__Initial_Setup.sql 840 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/migration/V1_2__Alter_Table.sql 1 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/migration/V1_3__Alter_Table.sql 2 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/migration/V1_4__Alter_Table.sql 14 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/migration/V1_5__Alter_Table.sql 10 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/migration/V1_6__Alter_Table.sql 59 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/migration/V1_7__Alter_Table.sql 1 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/migration/V1_8__Alter_Table.sql 63 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/migration/V1_9__Alter_Table.sql 70 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mybatis/config/mybatis-config.xml 9 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mybatis/query-template/attachedFile-template.xml 61 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mybatis/query-template/customField-template.xml 54 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mybatis/query-template/event-template.xml 39 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mybatis/query-template/faq-template.xml 37 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mybatis/query-template/guide-template.xml 37 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mybatis/query-template/issue-template.xml 426 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mybatis/query-template/issueCustomFieldValue-template.xml 57 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mybatis/query-template/issueHistory-template.xml 26 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mybatis/query-template/issueStatus-template.xml 55 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mybatis/query-template/issueType-template.xml 38 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mybatis/query-template/issueUser-template.xml 45 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mybatis/query-template/notice-template.xml 34 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mybatis/query-template/project-template.xml 426 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mybatis/query-template/projectRoleUser-template.xml 46 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mybatis/query-template/qna-template.xml 36 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mybatis/query-template/user-template.xml 174 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mybatis/query-template/userWorkspace-template.xml 71 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mybatis/query-template/widget-template.xml 746 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mybatis/query-template/workflow-template.xml 37 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/mybatis/query-template/workspace-template.xml 160 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/system_design.properties 76 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/system_dev.properties 102 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/system_prod.properties 92 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/resources/system_test.properties 90 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/annotation/Viewer.java
New file
@@ -0,0 +1,13 @@
package kr.wisestone.owl.annotation;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Viewer {
    String value() default "";
}
src/main/java/kr/wisestone/owl/common/ExcelConditionCheck.java
New file
@@ -0,0 +1,69 @@
package kr.wisestone.owl.common;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.util.CommonUtil;
import kr.wisestone.owl.util.WebAppUtil;
import kr.wisestone.owl.vo.ExportExcelAttrVo;
import kr.wisestone.owl.vo.ExportExcelVo;
import kr.wisestone.owl.web.view.ExcelView;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
/**
 * Created by wisestone on 2018-12-18.
 */
@Component
public class ExcelConditionCheck {
    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;
    @Autowired
    private WebAppUtil webAppUtil;
    @Autowired
    private MessageAccessor messageAccessor;
    @Autowired
    private ExcelView excelView;
    public ModelAndView checkCondition(Map<String, Object> conditions, HttpServletRequest request, Model model) {
        Map<String, Object> results;
        try {
            results = CommonUtil.getSearchConditions(request);
            Iterator iterator = results.keySet().iterator();
            while(iterator.hasNext()) {
                String key = (String)iterator.next();
                Object value = results.get(key);
                conditions.put(key, value);
            }
        }
        catch(IOException e) {
            ExportExcelVo excelInfo = this.makeEmptyDownloadExcel();
            excelInfo.setFileName("검색 조건 오류 - 와이즈스톤 담당자에게 문의하세요.");
            this.simpMessagingTemplate.convertAndSendToUser(this.webAppUtil.getLoginUser().getAccount(), "/notification/system-alert", this.messageAccessor.getMessage(MsgConstants.EXCEL_CONDITIONS_NOT_EXIST));
            model.addAttribute(Constants.EXCEL, excelInfo);
            return new ModelAndView(this.excelView);
        }
        return null;
    }
    public ExportExcelVo makeEmptyDownloadExcel() {
        ExportExcelVo excelInfo = new ExportExcelVo();
        excelInfo.addAttrInfos(new ExportExcelAttrVo("name", "", 120, ExportExcelAttrVo.ALIGN_LEFT));
        return excelInfo;
    }
}
src/main/java/kr/wisestone/owl/common/MessageAccessor.java
New file
@@ -0,0 +1,45 @@
package kr.wisestone.owl.common;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.vo.MessageVo;
import kr.wisestone.owl.vo.ResMessageVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.LocaleResolver;
import java.util.Locale;
@Component
public class MessageAccessor {
    @Autowired
    private MessageSourceAccessor messageSourceAccessor;
    @Autowired
    private LocaleResolver localeResolver;
    public MessageSourceAccessor getMessageSourceAccessor() {
        return messageSourceAccessor;
    }
    public String message(String code, Locale locale) {
        return this.messageSourceAccessor.getMessage(code, locale);
    }
    public String message(String code, Object... args) {
        return this.messageSourceAccessor.getMessage(code, args, LocaleContextHolder.getLocale());
    }
    public MessageVo getMessage(String code, Object... args) {
        return new MessageVo(code, this.messageSourceAccessor.getMessage(code, args, LocaleContextHolder.getLocale()));
    }
    public ResMessageVo getResMessage(String code, String status, Object... args) {
        return new ResMessageVo(this.messageSourceAccessor.getMessage(code, args, LocaleContextHolder.getLocale()), code, status);
    }
    public Object getResMessage(OwlRuntimeException ex, String status) {
        return new ResMessageVo(ex.getMessage(), ex.getCode(), status);
    }
}
src/main/java/kr/wisestone/owl/config/AppConfiguration.java
New file
@@ -0,0 +1,15 @@
package kr.wisestone.owl.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
 * Created by jeong on 2017-08-01.
 */
@Configuration
@ComponentScan(basePackages = "kr.wisestone.owl")
@MapperScan(basePackages = "kr.wisestone.owl.mapper")
public class AppConfiguration {
}
src/main/java/kr/wisestone/owl/config/ApplicationContextProvider.java
New file
@@ -0,0 +1,21 @@
package kr.wisestone.owl.config;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component("")
public class ApplicationContextProvider implements ApplicationContextAware {
    private static ApplicationContext context;
    public static ApplicationContext getContext() {
        return context;
    }
    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        context = ctx;
    }
}
src/main/java/kr/wisestone/owl/config/AsyncConfiguration.java
New file
@@ -0,0 +1,82 @@
package kr.wisestone.owl.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.util.ErrorHandler;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
/**
 * Created by jeong on 2017-07-26.
 */
@Configuration
@EnableScheduling
@EnableAsync(order= Ordered.HIGHEST_PRECEDENCE)
public class AsyncConfiguration implements AsyncConfigurer, SchedulingConfigurer {
    private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class);
    private static final String CORE_POOL_SIZE = "10";
    private static final String MAX_POOL_SIZE = "100";
    private static final String QUEUE_CAPACITY = "100000";
    @Override
    @Bean(name = "taskExecutor")
    public ThreadPoolTaskExecutor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(Integer.parseInt(CORE_POOL_SIZE));
        executor.setMaxPoolSize(Integer.parseInt(MAX_POOL_SIZE));
        executor.setQueueCapacity(Integer.parseInt(QUEUE_CAPACITY));
        executor.setThreadNamePrefix("owl-Executor-");
        return executor;
    }
    @Bean
    public ThreadPoolTaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(20);
        scheduler.setThreadNamePrefix("task-");
        scheduler.setAwaitTerminationSeconds(60);
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        scheduler.setErrorHandler(new ErrorHandler() {
            @Override
            public void handleError(Throwable t) {
                log.error("task 실행시 알 수 없는 에러가 발생했습니다.", t);
            }
        });
        scheduler.setRejectedExecutionHandler(new RejectedExecutionHandler() {
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                log.error("알 수 없는 이유로 task 실행이 거절되었습니다.", r);
            }
        });
        return scheduler;
    }
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new SimpleAsyncUncaughtExceptionHandler();
    }
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        TaskScheduler taskScheduler = this.taskScheduler();
        taskRegistrar.setTaskScheduler(taskScheduler);
    }
}
src/main/java/kr/wisestone/owl/config/CommonConfiguration.java
New file
@@ -0,0 +1,174 @@
package kr.wisestone.owl.config;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.Protocol;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import kr.wisestone.owl.util.PageUtil;
import kr.wisestone.owl.util.WebAppUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.security.SpringSessionBackedSessionRegistry;
import java.util.Properties;
/**
 * Created by jeong on 2017-08-14.
 */
@Configuration
public class CommonConfiguration {
    private static final String FILE_ENCODING = "UTF-8";
    private static final String MAIL_DEBUG = "mail.debug";
    private static final String MAIL_SMTP_STARTTLS_ENABLE = "mail.smtp.starttls.enable";
    private static final String MAIL_SMTP_AUTH = "mail.smtp.auth";
    private static final String MAIL_SMTP_SSL_TRUST = "mail.smtp.ssl.trust";
    @Value("${aws.access.key}")
    private String accessKey;
    @Value("${aws.access.password}")
    private String accessPassword;
    @Autowired
    private FindByIndexNameSessionRepository findByIndexNameSessionRepository;
    @Value("${email.host}")
    private String emailHost;
    @Value("${email.port}")
    private String emailPort;
    @Value("${email.userName}")
    private String emailUserName;
    @Value("${email.password}")
    private String emailPassword;
    @Value("${email.transport.protocol}")
    private String emailTransportProtocol;
    @Value("${email.smtp.auth}")
    private String emailSmtpAuth;
    @Value("${email.smtp.starttle.enable}")
    private String emailSmtpStarttleEnable;
    @Value("${email.debug}")
    private String emailDebug;
    @Value("${email.sendUrl}")
    private String emailSendUrl;
    @Bean
    public WebAppUtil webAppUtil() {
        return new WebAppUtil();
    }
    @Bean
    public PageUtil pageUtil() {
        return new PageUtil();
    }
    @Profile("prod")
    @Bean
    public static PropertySourcesPlaceholderConfigurer prodProperties() {
        PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
        configurer.setLocations(new ClassPathResource("system_prod.properties"));
        configurer.setFileEncoding(FILE_ENCODING);
        return configurer;
    }
    @Profile("dev")
    @Bean
    public static PropertySourcesPlaceholderConfigurer devProperties() {
        PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
        configurer.setLocations(new ClassPathResource("system_dev.properties"));
        configurer.setFileEncoding(FILE_ENCODING);
        return configurer;
    }
    @Profile("test")
    @Bean
    public static PropertySourcesPlaceholderConfigurer testProperties() {
        PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
        configurer.setLocations(new ClassPathResource("system_test.properties"));
        configurer.setFileEncoding(FILE_ENCODING);
        return configurer;
    }
    @Profile("design")
    @Bean
    public static PropertySourcesPlaceholderConfigurer designProperties() {
        PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
        configurer.setLocations(new ClassPathResource("system_design.properties"));
        configurer.setFileEncoding(FILE_ENCODING);
        return configurer;
    }
    @Bean
    public String getEmailSendUrl() { return this.emailSendUrl; }
    @Bean
    public MessageSourceAccessor messageSourceAccessor(ReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource) {
        return new MessageSourceAccessor(reloadableResourceBundleMessageSource);
    }
    @Bean
    public ReloadableResourceBundleMessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setCacheSeconds(10);
        messageSource.setDefaultEncoding(FILE_ENCODING);
        messageSource.setBasenames("/WEB-INF/i18n/messages", "/WEB-INF/i18n/mail", "/WEB-INF/i18n/code");
        messageSource.setUseCodeAsDefaultMessage(true);
        return messageSource;
    }
    @Bean
    public JavaMailSender javaMailSender() {
        JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
        javaMailSender.setHost(this.emailHost);
        javaMailSender.setPort(Integer.valueOf(this.emailPort));
        javaMailSender.setUsername(this.emailUserName);
        javaMailSender.setPassword(this.emailPassword);
        Properties javaMailProperties = new Properties();
        javaMailProperties.setProperty(MAIL_SMTP_SSL_TRUST, this.emailHost);
        javaMailProperties.setProperty(MAIL_SMTP_AUTH, this.emailSmtpAuth);
        javaMailProperties.setProperty(MAIL_SMTP_STARTTLS_ENABLE, this.emailSmtpStarttleEnable);
        javaMailProperties.setProperty(MAIL_DEBUG, this.emailDebug);
        javaMailSender.setJavaMailProperties(javaMailProperties);
        return javaMailSender;
    }
    @Bean
    public AmazonS3 amazonS3() {
        BasicAWSCredentials credentials = new BasicAWSCredentials(this.accessKey, this.accessPassword);
        ClientConfiguration clientConfiguration = new ClientConfiguration();
        clientConfiguration.setProtocol(Protocol.HTTP);
        return AmazonS3ClientBuilder
                .standard()
                .withRegion(Regions.AP_NORTHEAST_2)
                .withCredentials(new AWSStaticCredentialsProvider(credentials))
                .withClientConfiguration(clientConfiguration).build();
    }
    @Bean
    @SuppressWarnings("unchecked")
    public SpringSessionBackedSessionRegistry sessionRegistry() {
        return new SpringSessionBackedSessionRegistry(this.findByIndexNameSessionRepository);
    }
}
src/main/java/kr/wisestone/owl/config/DataBaseConfiguration.java
New file
@@ -0,0 +1,175 @@
package kr.wisestone.owl.config;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import kr.wisestone.owl.config.persistence.aop.TransactionDefinitionInterceptor;
import kr.wisestone.owl.config.persistence.routing.TransactionDefinitionRoutingDataSource;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.SqlSessionFactory;
import org.flywaydb.core.Flyway;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;
import org.springframework.transaction.interceptor.TransactionAttributeSource;
import javax.persistence.SharedCacheMode;
import javax.persistence.ValidationMode;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
 * Created by jeong on 2017-08-02.
 */
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        basePackages = {"kr.wisestone.owl.repository"},
        entityManagerFactoryRef = "entityManagerFactory",
        transactionManagerRef = "jpaTransactionManager"
)
@EnableAspectJAutoProxy
public class DataBaseConfiguration implements TransactionManagementConfigurer {
    @Value("${db.primary.driverName}")
    private String dbPrimaryDriverName;
    @Value("${db.primary.url}")
    private String dbPrimaryUrl;
    @Value("${db.primary.userName}")
    private String dbPrimaryUserName;
    @Value("${db.primary.password}")
    private String dbPrimaryPassword;
    @Value("${db.replica1.url}")
    private String dbReplica1PrimaryUrl;
    @Value("${db.replica2.url}")
    private String dbReplica2PrimaryUrl;
    @Value("${db.replica3.url}")
    private String dbReplica3PrimaryUrl;
    @Value("${db.replica4.url}")
    private String dbReplica4PrimaryUrl;
    @Value("${db.replica5.url}")
    private String dbReplica5PrimaryUrl;
    @Bean
    public TransactionDefinitionInterceptor transactionDefinitionInterceptor(TransactionAttributeSource transactionAttributeSource) {
        return new TransactionDefinitionInterceptor(transactionAttributeSource);
    }
    @Bean
    public DataSource dataSource() {
        final TransactionDefinitionRoutingDataSource transactionDefinitionRoutingDataSource = new TransactionDefinitionRoutingDataSource();
        final Map<Object, Object> targetDataSources = new HashMap<>();
        this.setReplica(targetDataSources, this.dbReplica1PrimaryUrl, 1);
        this.setReplica(targetDataSources, this.dbReplica2PrimaryUrl, 2);
        this.setReplica(targetDataSources, this.dbReplica3PrimaryUrl, 3);
        this.setReplica(targetDataSources, this.dbReplica4PrimaryUrl, 4);
        this.setReplica(targetDataSources, this.dbReplica5PrimaryUrl, 5);
        transactionDefinitionRoutingDataSource.setDefaultTargetDataSource(this.buildDataSource("primaryHikariPool", this.dbPrimaryUrl, this.dbPrimaryUserName, this.dbPrimaryPassword, this.dbPrimaryDriverName));    //  master db
        transactionDefinitionRoutingDataSource.setTargetDataSources(targetDataSources);
        return transactionDefinitionRoutingDataSource;
    }
    //  리플리카 설정
    private void setReplica(Map<Object, Object> targetDataSources, String replicaUrl, int count) {
        if (!StringUtils.isEmpty(replicaUrl)) {
            targetDataSources.put("replica" + count, this.buildDataSource("replicaHikariPool" + count, replicaUrl, this.dbPrimaryUserName, this.dbPrimaryPassword, this.dbPrimaryDriverName));
        }
    }
    private DataSource buildDataSource(String poolName, String jdbcUrl, String userName, String password, String driverClassName) {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setMinimumIdle(40);
        hikariConfig.setConnectionTimeout(10000);
        hikariConfig.setMaxLifetime(58000);
        hikariConfig.setValidationTimeout(10000);
        hikariConfig.setMaximumPoolSize(40);
        hikariConfig.setConnectionTestQuery("SELECT 1");
        hikariConfig.setPoolName(poolName);
        hikariConfig.setJdbcUrl(jdbcUrl);
        hikariConfig.setUsername(userName);
        hikariConfig.setPassword(password);
        hikariConfig.setDriverClassName(driverClassName);
        return new HikariDataSource(hikariConfig);
    }
    @Override
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return this.jpaTransactionManager();
    }
    @Bean
    public PlatformTransactionManager jpaTransactionManager() {
        JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
        jpaTransactionManager.setEntityManagerFactory(this.entityManagerFactory().getObject());
        return jpaTransactionManager;
    }
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
        adapter.setDatabasePlatform("org.hibernate.dialect.MySQL5InnoDBDialect");
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(adapter);
        factory.setDataSource(this.dataSource());
        factory.setSharedCacheMode(SharedCacheMode.ENABLE_SELECTIVE);
        factory.setValidationMode(ValidationMode.NONE);
        factory.setPackagesToScan("kr.wisestone.owl.domain");
        factory.setMappingResources("META-INF/orm.xml");
        factory.setJpaPropertyMap(this.getJpaProperties());
        return factory;
    }
    private Map<String, Object> getJpaProperties() {
        Map<String, Object> jpaProperties = new HashMap<>();
        jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5InnoDBDialect");
        jpaProperties.put("hibernate.connection.driver_class", "com.mysql.jdbc.Driver");
        jpaProperties.put("hibernate.physical_naming_strategy", "kr.wisestone.owl.domain.strategy.PrefixNamingStrategy");
        jpaProperties.put("hibernate.show_sql", "false");
        jpaProperties.put("hibernate.format_sql", "true");
        jpaProperties.put("hibernate.use_sql_comments", "true");
        jpaProperties.put("hibernate.session_factory.interceptor", "kr.wisestone.owl.domain.interceptor.AuditLogInterceptor");
        return jpaProperties;
    }
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setConfigLocation(
                new PathMatchingResourcePatternResolver().getResource("classpath:/mybatis/config/mybatis-config.xml")
        );
        sqlSessionFactoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources("classpath:/mybatis/query-template*//*.xml")
        );
        return sqlSessionFactoryBean.getObject();
    }
    @Bean(initMethod = "migrate")
    public Flyway flyway(DataSource dataSource) {
        Flyway flyway = new Flyway();
        flyway.setBaselineOnMigrate(true);
        flyway.setLocations("classpath:/migration");
        flyway.setDataSource(dataSource);
        return flyway;
    }
}
src/main/java/kr/wisestone/owl/config/ElasticSearchConfiguration.java
New file
@@ -0,0 +1,24 @@
package kr.wisestone.owl.config;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
@Configuration
public class ElasticSearchConfiguration {
    @Value("${elastic.search.hosts}")
    private String hosts;
    @Bean
    public RestHighLevelClient restHighLevelClient() {
        String[] elasticSearchHosts = this.hosts.replaceAll("\\p{Z}", "").split(",");
        return new RestHighLevelClient(
                RestClient.builder(Arrays.stream(elasticSearchHosts).map(HttpHost::create).toArray(HttpHost[]::new))
        );
    }
}
src/main/java/kr/wisestone/owl/config/KafkaConfiguration.java
New file
@@ -0,0 +1,71 @@
package kr.wisestone.owl.config;
import kr.wisestone.owl.service.impl.AttachedFileServiceImpl;
import kr.wisestone.owl.config.kafka.KafkaReceiver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.support.serializer.JsonDeserializer;
import org.springframework.kafka.support.serializer.JsonSerializer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.*;
import java.util.HashMap;
import java.util.Map;
// edit by zenith : for disable kafka
//@EnableKafka
@Configuration
public class KafkaConfiguration {
    private static final Logger LOGGER = LoggerFactory.getLogger(AttachedFileServiceImpl.class);
    @Value("${use.kafka}")
    private boolean useKafka;
    @Value("${kafka.bootstrap.servers}")
    private String bootstrapServers;
    @Value("${kafka.consumer.group.id}")
    private String groupId;
    @Bean
    public ProducerFactory<String, Object> producerFactory() {
        Map<String, Object> configProps = new HashMap<>();
        configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, this.bootstrapServers);
        configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
        return new DefaultKafkaProducerFactory<>(configProps);
    }
    @Bean
    public KafkaTemplate<String, Object> kafkaTemplate() {
        return new KafkaTemplate<>(this.producerFactory());
    }
    @Bean
    public ConsumerFactory<String, Object> consumerFactory() {
        Map<String, Object> configProps = new HashMap<>();
        configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, this.bootstrapServers);
        configProps.put(ConsumerConfig.GROUP_ID_CONFIG, this.groupId);
        configProps.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
        configProps.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, 1000);
        configProps.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 30000);
        configProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        configProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
        return new DefaultKafkaConsumerFactory<>(configProps);
    }
    @Bean
    public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(this.consumerFactory());
        return factory;
    }
}
src/main/java/kr/wisestone/owl/config/SecurityConfiguration.java
New file
@@ -0,0 +1,160 @@
package kr.wisestone.owl.config;
import kr.wisestone.owl.config.security.filter.AjaxSessionExpiredFilter;
import kr.wisestone.owl.config.security.handler.AjaxAuthenticationEntryPoint;
import kr.wisestone.owl.config.security.handler.AjaxAuthenticationFailureHandler;
import kr.wisestone.owl.config.security.handler.AjaxAuthenticationSuccessHandler;
import kr.wisestone.owl.config.security.handler.AjaxLogoutSuccessHandler;
import kr.wisestone.owl.config.security.service.UserSecurityService;
import kr.wisestone.owl.config.security.strategy.SecuritySessionExpiredStrategy;
import kr.wisestone.owl.constant.Constants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    private static final String REMEMBER_ME_KEY = "rememberMe";
    @Autowired
    private AjaxAuthenticationSuccessHandler ajaxAuthenticationSuccessHandler;
    @Autowired
    private AjaxAuthenticationFailureHandler ajaxAuthenticationFailureHandler;
    @Autowired
    private AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler;
    @Autowired
    private AjaxAuthenticationEntryPoint authenticationEntryPoint;
    @Autowired
    private UserSecurityService userSecurityService;
    @Autowired
    private SessionRegistry sessionRegistry;
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(this.userSecurityService)
        .passwordEncoder(this.passwordEncoder());
    }
    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(this.userSecurityService);
        authenticationProvider.setPasswordEncoder(this.passwordEncoder());
        return authenticationProvider;
    }
    @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
    @Bean
    public AjaxSessionExpiredFilter ajaxSessionExpiredFilter() {
        return new AjaxSessionExpiredFilter(this.sessionRegistry);
    }
    @Bean
    public SecuritySessionExpiredStrategy securitySessionExpiredStrategy() {
        return new SecuritySessionExpiredStrategy(Constants.SESSION_EXPIRE_REDIRECT_URL);
    }
    @Bean
    public SpringSessionRememberMeServices springSessionRememberMeServices() {
        SpringSessionRememberMeServices springSessionRememberMeServices =
                new SpringSessionRememberMeServices();
        springSessionRememberMeServices.setAlwaysRemember(false);
        springSessionRememberMeServices.setRememberMeParameterName(REMEMBER_ME_KEY);
        return springSessionRememberMeServices;
    }
    @Override
    public void configure(WebSecurity web) {
        web.ignoring()
                .antMatchers("/**/*.{js,css}");
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginProcessingUrl("/security/login")
                .successHandler(this.ajaxAuthenticationSuccessHandler)
                .failureHandler(this.ajaxAuthenticationFailureHandler)
                .usernameParameter("j_username")
                .passwordParameter("j_password")
                .permitAll();
        http.logout()
                .clearAuthentication(true)
                .logoutUrl("/security/logout")
                .logoutSuccessHandler(this.ajaxLogoutSuccessHandler)
                .deleteCookies("JSESSIONID", REMEMBER_ME_KEY)
                .invalidateHttpSession(true)
                .permitAll();
        http.headers()
                .frameOptions()
                .disable()
                .and()
                .csrf()
                .disable()
                .httpBasic()
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(this.authenticationEntryPoint);
        http.authorizeRequests()
                .antMatchers("/kakaoOAuth2CallBack").permitAll()
                .antMatchers("/naverOAuth2CallBack").permitAll()
                .antMatchers("/googleOAuth2CallBack").permitAll()
                .antMatchers("/facebookOAuth2CallBack").permitAll()
                .antMatchers("/user/addSocialLogin").permitAll()
                .antMatchers("/user/add").permitAll()
                .antMatchers("/user/getUserSession").permitAll()
                .antMatchers("/user/returnEmailPassword").permitAll()
                .antMatchers("/workspace/find").permitAll()
                .antMatchers("/workspace/findPrimaryWorkspace").permitAll()
                .antMatchers("/guide/detail").permitAll()
                .antMatchers("/language/change").permitAll()
                .antMatchers("/security/*").permitAll()
                .antMatchers("/**/*").authenticated();
        http.rememberMe()
                .rememberMeServices(this.springSessionRememberMeServices());
        http.sessionManagement()
                .maximumSessions(1)
                .maxSessionsPreventsLogin(false)
                .expiredSessionStrategy(this.securitySessionExpiredStrategy())
                .sessionRegistry(this.sessionRegistry).and().and()
                .addFilterBefore(this.ajaxSessionExpiredFilter(), ConcurrentSessionFilter.class);
    }
}
src/main/java/kr/wisestone/owl/config/SessionConfiguration.java
New file
@@ -0,0 +1,73 @@
package kr.wisestone.owl.config;
import kr.wisestone.owl.config.websocket.RedisSubscriber;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.session.data.redis.config.ConfigureRedisAction;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
/**
 * Created by jeong on 2019-02-28.
 */
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600)
public class SessionConfiguration extends AbstractHttpSessionApplicationInitializer {
    @Value("${redis.host}")
    private String redisHost;
    @Value("${redis.port}")
    private int redisPort;
    @Value("${redis.common.topic}")
    private String redisCommonTopic;
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(this.redisHost, this.redisPort);
    }
    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }
    @Bean
    public ConfigureRedisAction configureRedisAction() {
        return ConfigureRedisAction.NO_OP;
    }
    @Bean
    public RedisSubscriber redisSubscriber() {
        return new RedisSubscriber();
    }
    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory) {
        RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
        redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory);
        redisMessageListenerContainer.addMessageListener(this.redisSubscriber(), new ChannelTopic(this.redisCommonTopic));
        return redisMessageListenerContainer;
    }
    @Bean
    public StringRedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        stringRedisTemplate.setConnectionFactory(redisConnectionFactory);
        stringRedisTemplate.setKeySerializer(new StringRedisSerializer());
        stringRedisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(String.class));
        return stringRedisTemplate;
    }
}
src/main/java/kr/wisestone/owl/config/ThymeleafConfiguration.java
New file
@@ -0,0 +1,33 @@
package kr.wisestone.owl.config;
import org.apache.commons.codec.CharEncoding;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
/**
 * Created by wisestone on 2018-03-19.
 */
@Configuration
public class ThymeleafConfiguration {
    @Bean
    public ClassLoaderTemplateResolver emailTemplateResolver() {
        ClassLoaderTemplateResolver emailTemplateResolver = new ClassLoaderTemplateResolver();
        emailTemplateResolver.setPrefix("mails/");
        emailTemplateResolver.setSuffix(".html");
        emailTemplateResolver.setTemplateMode(TemplateMode.HTML);
        emailTemplateResolver.setCharacterEncoding(CharEncoding.UTF_8);
        emailTemplateResolver.setOrder(1);
        return emailTemplateResolver;
    }
    @Bean
    public SpringTemplateEngine templateEngine() {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.addTemplateResolver(emailTemplateResolver());
        return templateEngine;
    }
}
src/main/java/kr/wisestone/owl/config/WebMvcConfiguration.java
New file
@@ -0,0 +1,88 @@
package kr.wisestone.owl.config;
import kr.wisestone.owl.web.converter.FileHttpMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
/**
 * Created by jeong on 2017-08-01.
 */
@EnableWebMvc
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new StringHttpMessageConverter());
        MappingJackson2HttpMessageConverter jsonConverter = this.mappingJackson2HttpMessageConverter();
        jsonConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML));
        converters.add(jsonConverter);
        FileHttpMessageConverter fileConverter = this.fileHttpMessageConverter();
        converters.add(fileConverter);
    }
    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        return new MappingJackson2HttpMessageConverter();
    }
    @Bean
    public FileHttpMessageConverter fileHttpMessageConverter() {
        return new FileHttpMessageConverter();
    }
    @Bean
    public MultipartResolver multipartResolver() {
        CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
        commonsMultipartResolver.setMaxUploadSize(314572800);
        return commonsMultipartResolver;
    }
    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
        localeChangeInterceptor.setParamName("language");
        return localeChangeInterceptor;
    }
    @Bean
    public LocaleResolver localeResolver() {
        CookieLocaleResolver localeResolver = new CookieLocaleResolver();
        localeResolver.setCookieMaxAge(3600);
        localeResolver.setDefaultLocale(new Locale("ko_KR"));
        Locale locale = new Locale("ko", "KR");
        locale.setDefault(locale);
        return localeResolver;
    }
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index.html");
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(this.localeChangeInterceptor());
    }
}
src/main/java/kr/wisestone/owl/config/WebSocketStompConfiguration.java
New file
@@ -0,0 +1,30 @@
package kr.wisestone.owl.config;
import kr.wisestone.owl.config.websocket.WebSocketHandshakeHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketStompConfiguration implements WebSocketMessageBrokerConfigurer {
    @Override
    public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
        stompEndpointRegistry.addEndpoint("/owl-socket")
                            .setAllowedOrigins("*")
                            .addInterceptors(new HttpSessionHandshakeInterceptor())
                            .setHandshakeHandler(new WebSocketHandshakeHandler())
                            .withSockJS();
    }
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/session", "/notification", "/permission");
        registry.setApplicationDestinationPrefixes("/app");
    }
}
src/main/java/kr/wisestone/owl/config/kafka/KafkaReceiver.java
New file
@@ -0,0 +1,28 @@
package kr.wisestone.owl.config.kafka;
import kr.wisestone.owl.util.CommonUtil;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
public class KafkaReceiver {
    private static final Logger log = LoggerFactory.getLogger(KafkaReceiver.class);
    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;
    @KafkaListener(topics = "#{'${kafka.common.topic}'}")
    public void receive(ConsumerRecord<?, ?> consumerRecord) {
        Map<String, Object> messageMap = (Map<String, Object>) consumerRecord.value();
        CommonUtil.sendWebSocketMessage(this.simpMessagingTemplate, messageMap);
    }
}
src/main/java/kr/wisestone/owl/config/kafka/KafkaSender.java
New file
@@ -0,0 +1,22 @@
package kr.wisestone.owl.config.kafka;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
public class KafkaSender {
    private static final Logger log = LoggerFactory.getLogger(KafkaSender.class);
    @Autowired
    private KafkaTemplate<String, Object> kafkaTemplate;
    public void send(String topic, Map<String, Object> message) {
        if(kafkaTemplate != null)
            this.kafkaTemplate.send(topic, message);
    }
}
src/main/java/kr/wisestone/owl/config/persistence/aop/TransactionDefinitionInterceptor.java
New file
@@ -0,0 +1,68 @@
package kr.wisestone.owl.config.persistence.aop;
import java.lang.reflect.Method;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.NamedThreadLocal;
import org.springframework.core.Ordered;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAttributeSource;
@Aspect
public class TransactionDefinitionInterceptor implements Ordered {
    private static final ThreadLocal<TransactionDefinition> transactionDefinitionHolder = new NamedThreadLocal<>("Current aspect-driven transaction definition");
    public static TransactionDefinition currentTransactionDefinition() {
        return transactionDefinitionHolder.get();
    }
    public static boolean isCurrentTransactionReadOnly() {
        return transactionDefinitionHolder.get() != null ? transactionDefinitionHolder.get().isReadOnly() : false;
    }
    private final TransactionAttributeSource transactionAttributeSource;
    private int order = Ordered.LOWEST_PRECEDENCE - 10;
    public TransactionDefinitionInterceptor(
            TransactionAttributeSource transactionAttributeSource) {
        this.transactionAttributeSource = transactionAttributeSource;
    }
    @Around("@annotation(transactional)")
    public Object rememberTransactionDefinition(
            ProceedingJoinPoint joinPoint,
            Transactional transactional) throws Throwable {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Class<?> targetClass = joinPoint.getTarget().getClass();
        TransactionDefinition transactionDefinition = transactionAttributeSource.getTransactionAttribute(method, targetClass);
        boolean restore = false;
        if (transactionDefinitionHolder.get() == null) {
            transactionDefinitionHolder.set(transactionDefinition);
            restore = true;
        }
        try {
            return joinPoint.proceed();
        } finally {
            if (restore) {
                transactionDefinitionHolder.set(null);
            }
        }
    }
    public void setOrder(int order) {
        this.order = order;
    }
    @Override
    public int getOrder() {
        return order;
    }
}
src/main/java/kr/wisestone/owl/config/persistence/routing/TransactionDefinitionRoutingDataSource.java
New file
@@ -0,0 +1,39 @@
package kr.wisestone.owl.config.persistence.routing;
import com.google.common.collect.Lists;
import kr.wisestone.owl.config.persistence.aop.TransactionDefinitionInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
public class TransactionDefinitionRoutingDataSource extends AbstractRoutingDataSource {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    private List<Object> dataSourceKeys;
    private final AtomicInteger currentIndex = new AtomicInteger();
    @Override
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        super.setTargetDataSources(targetDataSources);
        this.dataSourceKeys = Lists.newArrayList(targetDataSources.keySet());
    }
    @Override
    protected Object determineCurrentLookupKey() {
        if (TransactionDefinitionInterceptor
                .isCurrentTransactionReadOnly()
                && !dataSourceKeys.isEmpty()) {
            int size = dataSourceKeys.size();
            Object key = dataSourceKeys.get(currentIndex.getAndIncrement() % size);
            return key;
        }
        return null;
    }
}
src/main/java/kr/wisestone/owl/config/security/exception/LoginProcessingException.java
New file
@@ -0,0 +1,14 @@
package kr.wisestone.owl.config.security.exception;
import org.springframework.security.core.AuthenticationException;
public class LoginProcessingException extends AuthenticationException {
    public LoginProcessingException(String message) {
        super(message);
    }
    public LoginProcessingException(String message, Throwable t) {
        super(message, t);
    }
}
src/main/java/kr/wisestone/owl/config/security/filter/AjaxSessionExpiredFilter.java
New file
@@ -0,0 +1,94 @@
package kr.wisestone.owl.config.security.filter;
import kr.wisestone.owl.constant.Constants;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
public class AjaxSessionExpiredFilter extends GenericFilterBean {
    private final Logger log = LoggerFactory.getLogger(AjaxSessionExpiredFilter.class);
    private String ajaxHeader;
    private SessionRegistry sessionRegistry;
    private String expiredUrl;
    private LogoutHandler[] handlers = new LogoutHandler[]{new SecurityContextLogoutHandler()};
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
    public void destroy() {
    }
    public AjaxSessionExpiredFilter(SessionRegistry sessionRegistry) {
        Assert.notNull(sessionRegistry, "SessionRegistry required");
        this.sessionRegistry = sessionRegistry;
    }
    public AjaxSessionExpiredFilter(SessionRegistry sessionRegistry, String expiredUrl) {
        Assert.notNull(sessionRegistry, "SessionRegistry required");
        Assert.isTrue(expiredUrl == null || UrlUtils.isValidRedirectUrl(expiredUrl), expiredUrl + " isn\'t a valid redirect URL");
        this.sessionRegistry = sessionRegistry;
        this.expiredUrl = expiredUrl;
    }
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        HttpSession session = request.getSession(false);
        String uri = request.getRequestURI();
        if (FilenameUtils.isExtension(uri, "html") || uri.contains("/user/getUserSession") || uri.contains("/owl-socket")) {
            chain.doFilter(request, response);
            return;
        }
        if(session != null) {
            SessionInformation info = this.sessionRegistry.getSessionInformation(session.getId());
            if(info != null) {
                if(info.isExpired()) {
                    this.doLogoutAndFireErrorMessage(request, response);
                    return;
                }
                this.sessionRegistry.refreshLastRequest(info.getSessionId());
            }
        }
        chain.doFilter(request, response);
    }
    private void doLogoutAndFireErrorMessage(HttpServletRequest request, HttpServletResponse response) throws IOException {
        this.doLogout(request, response);
        response.sendRedirect(Constants.SESSION_EXPIRE_REDIRECT_URL);
    }
    private void doLogout(HttpServletRequest request, HttpServletResponse response) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        LogoutHandler[] var4 = this.handlers;
        int var5 = var4.length;
        for(int var6 = 0; var6 < var5; ++var6) {
            LogoutHandler handler = var4[var6];
            handler.logout(request, response, auth);
        }
    }
}
src/main/java/kr/wisestone/owl/config/security/handler/AjaxAuthenticationEntryPoint.java
New file
@@ -0,0 +1,48 @@
package kr.wisestone.owl.config.security.handler;
import kr.wisestone.owl.common.MessageAccessor;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.util.ConvertUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
@Component
public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint {
    private final Logger log = LoggerFactory.getLogger(AjaxAuthenticationEntryPoint.class);
    @Autowired
    protected MessageAccessor messageAccessor;
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException arg2) {
        Map<String, Object> resJsonData = new HashMap<>();
        resJsonData.put(Constants.RES_KEY_MESSAGE,
                this.messageAccessor.getResMessage(MsgConstants.SESSION_EXPIRED, Constants.RES_KEY_MSG_FAIL));
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        String data = ConvertUtil.convertObjectToJson(resJsonData);
        try (PrintWriter out = response.getWriter()) {
            out.print(data);
            out.flush();
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }
}
src/main/java/kr/wisestone/owl/config/security/handler/AjaxAuthenticationFailureHandler.java
New file
@@ -0,0 +1,57 @@
package kr.wisestone.owl.config.security.handler;
import kr.wisestone.owl.common.MessageAccessor;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.util.ConvertUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
@Component
public class AjaxAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
    private final Logger log = LoggerFactory.getLogger(AjaxAuthenticationFailureHandler.class);
    @Autowired
    protected MessageAccessor messageAccessor;
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) {
        Map<String, Object> resJsonData = new HashMap<>();
        if (exception instanceof BadCredentialsException) {
            resJsonData.put(Constants.RES_KEY_MESSAGE, this.messageAccessor.getResMessage(MsgConstants.USER_NOT_EQUAL_PASSWORD, Constants.RES_KEY_MSG_FAIL));
        }
        else {
            resJsonData.put(Constants.RES_KEY_MESSAGE, this.messageAccessor.getResMessage(exception.getMessage(), Constants.RES_KEY_MSG_FAIL));
        }
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        String data = ConvertUtil.convertObjectToJson(resJsonData);
        try {
            PrintWriter out = response.getWriter();
            out.print(data);
            out.flush();
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }
}
src/main/java/kr/wisestone/owl/config/security/handler/AjaxAuthenticationSuccessHandler.java
New file
@@ -0,0 +1,60 @@
package kr.wisestone.owl.config.security.handler;
import kr.wisestone.owl.common.MessageAccessor;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.util.ElasticSearchUtil;
import kr.wisestone.owl.vo.UserVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
@Component
public class AjaxAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
    private final Logger log = LoggerFactory.getLogger(AjaxAuthenticationSuccessHandler.class);
    @Autowired
    protected MessageAccessor messageAccessor;
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse response, Authentication authentication) {
        Map<String, Object> resJsonData = new HashMap<>();
        User user = (User) authentication.getPrincipal();
        try {
            log.info(ElasticSearchUtil.makeUserSessionHistoryMessage(httpServletRequest, ConvertUtil.copyProperties(user, UserVo.class)));
        }
        catch (Exception ex) {
            log.error(ex.getMessage());
        }
        resJsonData.put(Constants.RES_KEY_MESSAGE,
                this.messageAccessor.getResMessage(MsgConstants.SUCCESS_REQUEST, Constants.RES_KEY_MSG_SUCCESS));
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        response.setStatus(HttpServletResponse.SC_OK);
        String data = ConvertUtil.convertObjectToJson(resJsonData);
        try {
            PrintWriter out = response.getWriter();
            out.print(data);
            out.flush();
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }
}
src/main/java/kr/wisestone/owl/config/security/handler/AjaxLogoutSuccessHandler.java
New file
@@ -0,0 +1,20 @@
package kr.wisestone.owl.config.security.handler;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class AjaxLogoutSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        response.setStatus(HttpServletResponse.SC_OK);
    }
}
src/main/java/kr/wisestone/owl/config/security/service/UserSecurityService.java
New file
@@ -0,0 +1,36 @@
package kr.wisestone.owl.config.security.service;
import kr.wisestone.owl.common.MessageAccessor;
import kr.wisestone.owl.config.security.exception.LoginProcessingException;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
@Component("userSecurityService")
public class UserSecurityService implements UserDetailsService {
    @Autowired
    protected MessageAccessor messageAccessor;
    @Autowired
    private UserService userService;
    @Override
    public UserDetails loadUserByUsername(final String login) {
        User user = this.userService.findByAccount(login);
        if (user == null) {
            throw new LoginProcessingException(MsgConstants.USER_NOT_EXIST);
        }
        if (!User.USER_STATUS_ACTIVE.equals(user.getStatus())) {
            throw new LoginProcessingException(MsgConstants.USER_NOT_USE_ACTIVE_STATUS);
        }
        return user;
    }
}
src/main/java/kr/wisestone/owl/config/security/strategy/SecuritySessionExpiredStrategy.java
New file
@@ -0,0 +1,22 @@
package kr.wisestone.owl.config.security.strategy;
import org.springframework.security.web.session.SessionInformationExpiredEvent;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class SecuritySessionExpiredStrategy implements SessionInformationExpiredStrategy {
    private String defaultUrl;
    public SecuritySessionExpiredStrategy(String defaultUrl) {
        this.defaultUrl = defaultUrl;
    }
    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException {
        HttpServletResponse response = event.getResponse();
        response.sendRedirect(this.defaultUrl);
    }
}
src/main/java/kr/wisestone/owl/config/websocket/RedisPublisher.java
New file
@@ -0,0 +1,19 @@
package kr.wisestone.owl.config.websocket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.data.redis.listener.ChannelTopic;
import java.util.Map;
@Service
public class RedisPublisher {
    @Autowired
    private RedisTemplate redisTemplate;
    public void publish(ChannelTopic channelTopic, Map<String, Object> message) {
        this.redisTemplate.convertAndSend(channelTopic.getTopic(), message);
    }
}
src/main/java/kr/wisestone/owl/config/websocket/RedisSubscriber.java
New file
@@ -0,0 +1,34 @@
package kr.wisestone.owl.config.websocket;
import com.google.gson.Gson;
import kr.wisestone.owl.util.CommonUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import java.util.Map;
public class RedisSubscriber implements MessageListener {
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;
    @Override
    public void onMessage(Message message, byte[] pattern) {
        try {
            Gson gson = new Gson();
            String publishMessage = (String) this.redisTemplate.getStringSerializer().deserialize(message.getBody());
            Map<String, Object> messageMap = (Map<String, Object>) gson.fromJson(publishMessage, Object.class);
            CommonUtil.sendWebSocketMessage(this.simpMessagingTemplate, messageMap);
        } catch (Exception ex) {
            System.out.println("ex : " + ex.getMessage());
        }
    }
}
src/main/java/kr/wisestone/owl/config/websocket/WebSocketHandshakeHandler.java
New file
@@ -0,0 +1,16 @@
package kr.wisestone.owl.config.websocket;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
import java.security.Principal;
import java.util.Map;
public class WebSocketHandshakeHandler extends DefaultHandshakeHandler {
    @Override
    protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
        return request.getPrincipal();
    }
}
src/main/java/kr/wisestone/owl/config/websocket/WebSocketService.java
New file
@@ -0,0 +1,64 @@
package kr.wisestone.owl.config.websocket;
import com.google.gson.Gson;
import kr.wisestone.owl.util.MapUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@Service
public class WebSocketService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisPublisher redisPublisher;
    @Value("${redis.common.topic}")
    private String redisCommonTopic;
    public void checkActiveUser() {
        Map<String, Object> totalActiveUserMap = this.checkExpireWebSocketSession();
        Gson gson = new Gson();
        Map<String, Object> responseMap = new HashMap<>();
        responseMap.put("url", "/session/user-list");
        responseMap.put("message", gson.toJson(totalActiveUserMap));
        ChannelTopic channelTopic = new ChannelTopic(this.redisCommonTopic);
        this.redisPublisher.publish(channelTopic, responseMap);
    }
    private Map<String, Object> checkExpireWebSocketSession() {
        Map<String, Object> totalActiveUserMap = new HashMap<>();
        Gson gson = new Gson();
        String loginUsers = this.stringRedisTemplate.opsForValue().get("activeUsers");
        if (!StringUtils.isEmpty(loginUsers)) {
            totalActiveUserMap = (Map<String, Object>)gson.fromJson(loginUsers, Object.class);
            Iterator<String> iterator = totalActiveUserMap.keySet().iterator();
            while (iterator.hasNext()) {
                String key = iterator.next();
                Map<String, Object> userMap = (Map<String, Object>)totalActiveUserMap.get(key);
                Long sessionActiveTime = Long.valueOf(MapUtil.getString(userMap, "sessionActiveTime"));
                if (System.currentTimeMillis() - sessionActiveTime >= 60000) {
                    iterator.remove();
                }
            }
        }
        return totalActiveUserMap;
    }
}
src/main/java/kr/wisestone/owl/constant/Constants.java
New file
@@ -0,0 +1,16 @@
package kr.wisestone.owl.constant;
/**
 * Created by jeong on 2017-08-14.
 */
public class Constants {
    public static final String REQ_KEY_PAGE_VO = "page";
    public static final String REQ_KEY_CONTENT = "content";
    public static final String RES_KEY_MESSAGE = "message";
    public static final String RES_KEY_MSG_FAIL = "fail";
    public static final String RES_KEY_MSG_SUCCESS = "success";
    public static final String RES_KEY_CONTENTS = "data";
    public static final String SESSION_ACCOUNT = "account";
    public static final String EXCEL = "excel";
    public static final String SESSION_EXPIRE_REDIRECT_URL = "/#/login";
}
src/main/java/kr/wisestone/owl/constant/ElasticSearchConstants.java
New file
@@ -0,0 +1,14 @@
package kr.wisestone.owl.constant;
public class ElasticSearchConstants {
    public static final String ISSUE_ADD = "ISSUE_ADD"; //  이슈 생성
    public static final String ISSUE_MODIFY = "ISSUE_MODIFY";   //  이슈 수정
    public static final String ISSUE_REMOVE = "ISSUE_REMOVE";   //  이슈 삭제
    public static final String ISSUE_FIND = "ISSUE_FIND";   //  이슈 조회
    public static final String ISSUE_DETAIL = "ISSUE_DETAIL";   //  이슈 상세 조회
    public static final String ISSUE_STATUS_CHANGE = "ISSUE_STATUS_CHANGE";   //  이슈 상태 변경
    public static final String ISSUE_USER_CHANGE = "ISSUE_STATUS_CHANGE";   //  이슈 담당자 변경
    public static final String ISSUE_ANOTHER_USER_SEND_EMAIL = "ISSUE_ANOTHER_USER_SEND_EMAIL";   //  다른 사용자에게 메일 보내기
    public static final String ISSUE_HISTORY_FIND = "ISSUE_HISTORY_FIND";   //  이슈 이력 조회
    public static final String ISSUE_RESERVATION = "ISSUE_RESERVATION"; //  이슈 예약 발생
}
src/main/java/kr/wisestone/owl/constant/MailConstants.java
New file
@@ -0,0 +1,61 @@
package kr.wisestone.owl.constant;
/**
 * Created by wisestone on 2018-03-14.
 */
public enum MailConstants {
    WORKSPACE_JOIN("workspace.join.title", "workspaceJoinEmail"),    //  회원 가입
    USER_SEARCH_PASSWORD("user.search.password.title", "userSearchPasswordEmail"),   //  사용자 비밀번호 찾기
    USER_WITH_DRAW("user.withDraw.title", "userWithDrawEmail"), //  회원 탈퇴
    REGULAR_PAYMENT("regular.payment.title", "regularPaymentEmail"),   //  정기 결제 완료
    REGULAR_PAYMENT_CANCEL("regular.payment.cancel.title", "regularPaymentCancelEmail"), //  정기 결제 취소
    REGULAR_PAYMENT_CANCEL_BY_ACCOUNTING_MANAGER("regular.payment.cancel.accounting.manager.title", "regularPaymentCancelByAccountingManagerEmail"), //  정기 결제 취소
    REGULAR_PAYMENT_MODIFY("regular.payment.modify.title", "regularPaymentModifyEmail"),    //  정기 결제 변경
    WORKSPACE_INVITE_NEW_USER("workspace.inviteNewUser.title", "workspaceInviteNewUserEmail"),   //  신규 사용자 업무 공간 초대
    WORKSPACE_MAX_USER_EXCESS("workspace.maxUserExcess.title", "workspaceMaxUserExcessEmail"),   //  업무 공간 활성 사용자 초과
    USER_JOIN_STATISTICS("user.join.statistics.title", "userJoinStatisticsEmail"),   //  사용자 현황 정보
    TOTAL_STATISTICS("total.statistics.title", "totalStatisticsEmail"), //  전체 시스템 현황 정보
    WORKSPACE_EXPIRE("workspace.expire.title", "workspaceExpireEmail"),  //  업무 공간 사용 기간 만료
    WORKSPACE_EXPIRE_ALARM("workspace.expire.alarm.title", "workspaceExpireAlarmEmail"),    //  업무 공간 사용 기간 만료 예정 안내
    WORKSPACE_MAX_STORAGE_EXCESS("workspace.maxStorageExcess.title", "workspaceMaxStorageExcessEmail"),  //  업무 공간 저장 용량 초과
    WORKSPACE_INVITE_SYSTEM_USER("workspace.inviteSystemUser.title", "workspaceInviteSystemUserEmail"),  //  기존 사용자 업무 공간 초대
    PROJECT_DEFAULT_EXCLUDE("project.default.exclude.title", "projectDefaultExcludeEmail"),  //  프로젝트 일반 참여자 제외
    PROJECT_DEFAULT_INCLUDE("project.default.include.title", "projectDefaultIncludeEmail"),  //  프로젝트 일반 참여자 참여
    PROJECT_MANAGER_EXCLUDE("project.manager.exclude.title", "projectManagerExcludeEmail"),  //  프로젝트 관리자 제외
    PROJECT_MANAGER_INCLUDE("project.manager.include.title", "projectManagerIncludeEmail"),  //  프로젝트 관리자 참여
    PROJECT_MANAGER_EXCLUDE_AND_PROJECT_DEFAULT_INCLUDE("project.manager.exclude.default.include.title", "projectManagerExcludeAndDefaultIncludeEmail"), //  프로젝트 관리자 제외 후 일반 사용자로 참여
    PROJECT_DEFAULT_EXCLUDE_AND_PROJECT_MANAGER_INCLUDE("project.default.exclude.manager.include.title", "projectDefaultExcludeAndManagerIncludeEmail"), //  일반 사용자에서 제외 후 프로젝트 관리자로 참여
    ISSUE_ADD("issue.add.title", "issueAddEmail"),  //  이슈 생성
    ISSUE_REMOVE("issue.remove.title", "issueRemoveEmail"), //  이슈 삭제
    ISSUE_SEND("issue.send.title", "issueSendEmail"); //  이슈 이메일 전달
    //  ISSUE_MODIFY("issue.modify.title", "issueModifyEmail"); //  이슈 수정
    private String title;
    private String mailTemplate;
    MailConstants(String title, String mailTemplate) {
        this.title = title;
        this.mailTemplate = mailTemplate;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getMailTemplate() {
        return mailTemplate;
    }
    public void setMailTemplate(String mailTemplate) {
        this.mailTemplate = mailTemplate;
    }
}
src/main/java/kr/wisestone/owl/constant/MngPermission.java
New file
@@ -0,0 +1,63 @@
package kr.wisestone.owl.constant;
/**
 * Created by zenith at 20200803
 */
public class MngPermission {
    public static final int USER_PERMISSION_MNG_WORKSPACE = 4096;   //  WORK SPACE 관리    1000000000000
    public static final int USER_PERMISSION_MNG_PROJECT = 2048;   //  프로젝트 관리          0100000000000
    public static final int USER_PERMISSION_MNG_USER = 1024;        //  USER 관리           0010000000000
    public static final int USER_PERMISSION_MNG_ISSUE_STATUS = 512;  //  ISSUE SETTING 관리 0001000000000
    public static final int USER_PERMISSION_MNG_WORKFLOW = 256;     // WORK FLOW 관리       0000100000000
    public static final int USER_PERMISSION_MNG_CUSTOME_FIELD = 128; //  사용자정의 필드 관리  0000010000000
    public static final int USER_PERMISSION_MNG_ISSUE_TYPE = 64;    //  ISSUE TYPE 관리     0000000100000
    public static final int USER_PERMISSION_MNG_NOTICE = 32;        //  ISSUE TYPE 관리     0000000010000
    public static final int USER_PERMISSION_MNG_FAQ = 16;            //  FAQ 관리           0000000001000
    public static final int USER_PERMISSION_MNG_QNA = 8;           //  공지사항 관리          0000000000100
    public static final int USER_PERMISSION_MNG_EVENT = 4;          //  공지사항 관리         0000000000010
    public static final int USER_PERMISSION_MNG_GUIDE = 2;          //  사용자 알림 관리      0000000000001
    public static final int USER_PERMISSION_MNG_NONE = 0;          //
    public static final int USER_PERMISSION_MNG_ISSUE_SETTING = (USER_PERMISSION_MNG_ISSUE_STATUS | USER_PERMISSION_MNG_WORKFLOW |
                                                                USER_PERMISSION_MNG_CUSTOME_FIELD | USER_PERMISSION_MNG_ISSUE_TYPE);
    public static boolean checkMngPermission(int userPermission, int typePermission)
    {
        return ((userPermission & typePermission) != 0);
    }
    public static int makePermission(boolean userPermission, int typePermission)
    {
        if(userPermission)
            return typePermission;
        else
            return USER_PERMISSION_MNG_NONE;
    }
    public static int makeAllPermission()
    {
        int nPermission = (USER_PERMISSION_MNG_WORKSPACE |
                            USER_PERMISSION_MNG_PROJECT |
                          USER_PERMISSION_MNG_USER |
                USER_PERMISSION_MNG_NOTICE |
                USER_PERMISSION_MNG_FAQ |
                USER_PERMISSION_MNG_QNA |
                USER_PERMISSION_MNG_EVENT |
                USER_PERMISSION_MNG_GUIDE |
                USER_PERMISSION_MNG_ISSUE_SETTING);
        return nPermission;
    }
    public static int makeSubAllPermission()
    {
        int nPermission = (/*USER_PERMISSION_MNG_WORKSPACE |*/
                USER_PERMISSION_MNG_USER | USER_PERMISSION_MNG_NOTICE |
                USER_PERMISSION_MNG_FAQ | USER_PERMISSION_MNG_QNA |
                USER_PERMISSION_MNG_EVENT | USER_PERMISSION_MNG_GUIDE |
                        USER_PERMISSION_MNG_ISSUE_SETTING);
        return nPermission;
    }
}
src/main/java/kr/wisestone/owl/constant/MsgConstants.java
New file
@@ -0,0 +1,209 @@
package kr.wisestone.owl.constant;
/**
 * Created by jeong on 2017-08-02.
 */
public class MsgConstants {
    public static final String PROJECT_NAME_MAX_LENGTH_OUT = "PROJECT_NAME_MAX_LENGTH_OUT";   //  프로젝트 명은 최대 50글자까지 입력할 수 있습니다.
    public static final String DATE_PICKER_NOT_AVAILABLE = "DATE_PICKER_NOT_AVAILABLE"; //  날짜 선택이 잘못되었습니다.
    public static final String PROJECT_NOT_EXIST = "PROJECT_NOT_EXIST";    //    프로젝트가 존재하지 않습니다.
    public static final String PROJECT_NOT_MANAGER = "PROJECT_NOT_MANAGER"; //  프로젝트 관리자가 존재하지 않습니다.
    public static final String PROJECT_NOT_MODIFY_PERMISSION = "PROJECT_NOT_MODIFY_PERMISSION"; //  프로젝트 관리 권한이 없습니다.
    public static final String PROJECT_NOT_NAME = "PROJECT_NOT_NAME";   //  프로젝트 이름이 입력되지 않았습니다.
    public static final String PROJECT_NOT_STATUS = "PROJECT_NOT_STATUS";   //  프로젝트의 상태가 선택되지 않았습니다.
    public static final String DATE_NOT_EXIST = "DATE_NOT_EXIST";   //  날짜가 선택되지 않았습니다.
    public static final String PROJECT_OVER_LENGTH_PROJECT_KEY = "PROJECT_OVER_LENGTH_PROJECT_KEY"; //  프로젝트 키는 최대 10글자만 입력 가능합니다.
    public static final String PROJECT_KEY_NOT_EXIST = "PROJECT_KEY_NOT_EXIST"; //  프로젝트 키가 입력 되지 않았습니다.
    public static final String PROJECT_USED_NAME = "PROJECT_USED_NAME";    //    프로젝트 이름이 이미 사용되고 있습니다.
    public static final String DEFAULT_PROJECT_NOT_REMOVE = "DEFAULT_PROJECT_NOT_REMOVE";   //  기본으로 제공되는 프로젝트는 삭제할 수 없습니다.
    public static final String PROJECT_REMOVE_NOT_SELECT = "PROJECT_REMOVE_NOT_SELECT"; //  삭제할 프로젝트가 선택되지 않았습니다.
    public static final String PROJECT_NOT_INCLUDE_USER = "PROJECT_NOT_INCLUDE_USER";   //  프로젝트에 참여하고 있지 않은 사용자 입니다.
    public static final String PROJECT_USED_PROJECT_KEY = "PROJECT_USED_PROJECT_KEY";   //  프로젝트 키가 이미 사용되고 있습니다.
    public static final String PAYMENT_NOT_EXIST = "PAYMENT_NOT_EXIST"; //  결제 정보가 없습니다.
    public static final String PAYMENT_EXECUTE_ONLY_WORKSPACE_MANAGER = "PAYMENT_EXECUTE_ONLY_WORKSPACE_MANAGER"; //  결제는 업무 공간 관리자만 할 수 있습니다.
    public static final String PAYMENT_BUY_USER_MUST_BE_GREATER_THAN_ZERO = "PAYMENT_BUY_USER_MUST_BE_GREATER_THAN_ZERO"; //  결제하려는 사용자 수는 0보다 커야 합니다.
    public static final String PAYMENT_NO_TYPE = "PAYMENT_NO_TYPE";   //  결제 유형이 입력되지 않았습니다.
    public static final String PAYMENT_HISTORY_NOT_EXIST = "PAYMENT_HISTORY_NOT_EXIST"; //  결제 이력 정보가 없습니다.
    public static final String USER_WORKSPACE_MANAGER_NOT_EXIST = "USER_WORKSPACE_MANAGER_NOT_EXIST"; //  업무 공간 관리자 정보가 존재하지 않습니다.
    public static final String OAUTH_STATE_VALUE_NOT_EQUAL = "OAUTH_STATE_VALUE_NOT_EQUAL";  //  OAuth 인증에 실패했습니다. - 상태가 틀렸는데 굳이 알려줄 필요는 없다.
    public static final String WORKSPACE_NOT_EXIST = "WORKSPACE_NOT_EXIST"; //  업무 공간이 존재하지 않습니다.
    public static final String WORKSPACE_STORAGE_SIZE_EXCESS = "WORKSPACE_STORAGE_SIZE_EXCESS"; //  업무 공간 저장 공간이 초과되었습니다. 불필요한 파일을 정리하세요.
    public static final String WORKSPACE_MANAGER_NOT_CHANGE_USE_YN = "WORKSPACE_MANAGER_NOT_CHANGE_USE_YN"; //  업무 공간 관리자는 비활성화로 변경 될 수 없습니다.
    public static final String WORKSPACE_PERIOD_REMAIN = "WORKSPACE_PERIOD_REMAIN"; //  업무 공간 사용 기간이 남아 있습니다.
    public static final String WORKSPACE_USE_PERIOD_EXCESS = "WORKSPACE_USE_PERIOD_EXCESS"; //  업무 공간 사용 기간이 종료되어 생성, 수정, 삭제 기능을 사용할 수 없습니다.
    public static final String WORKSPACE_INCLUDE_DISABLED = "WORKSPACE_INCLUDE_DISABLED"; //  해당 업무 공간에서 비활성 상태이므로 생성, 수정, 삭제 기능을 사용할 수 없습니다.
    public static final String ATTACHED_FILE_NOT_EXIST = "ATTACHED_FILE_NOT_EXIST"; //  첨부 파일 정보를 찾을 수 없습니다.
    public static final String START_ISSUE_STATUS_NOT_EXIST = "START_ISSUE_STATUS_NOT_EXIST";  //  시작하는 이슈 상태가 존재하지 않습니다.
    public static final String END_ISSUE_STATUS_NOT_EXIST = "END_ISSUE_STATUS_NOT_EXIST";  //  종료하는 이슈 상태가 존재하지 않습니다.
    public static final String EMAIL_NOT_SEND = "EMAIL_NOT_SEND";   //  메일을 전송할 수 없습니다.
    public static final String INVITE_USER_USED_WORKSPACE = "INVITE_USER_USED_WORKSPACE";   //  해당 사용자는 이미 업무 공간에 참여하고 있습니다.
    public static final String PROJECT_ROLE_NOT_EXIST = "PROJECT_ROLE_NOT_EXIST";   //  프로젝트 역할이 존재하지 않습니다.
    public static final String WORKSPACE_NOT_NAME = "WORKSPACE_NOT_NAME";   //  업무 공간 명이 입력되지 않았습니다.
    public static final String WORKSPACE_MAX_USER_EXCESS = "WORKSPACE_MAX_USER_EXCESS";   //  업무 공간의 최대 사용자 수가 초과되었습니다. 최대 사용자 수가 초과되면 초대받은 사용자가 해당 업무 공간를 접근할 수 없습니다.
    public static final String WORKSPACE_MAX_USER_EXCESS_NOT_INCLUDE = "WORKSPACE_MAX_USER_EXCESS_NOT_INCLUDE"; // 업무 공간의 최대 사용자 수가 초과되어 참여 상태로 변경할 수 없습니다.
    public static final String WORKSPACE_OUT = "WORKSPACE_OUT"; //  참여하는 업무 공간에서 제외되었습니다. (1)
    public static final String WORKSPACE_NAME_MAX_LENGTH_OUT = "WORKSPACE_NAME_MAX_LENGTH_OUT";   //  업무 공간 명은 최대 50글자까지 입력할 수 있습니다.
    public static final String DEFAULT_PROJECT_MANAGER_NOT_CHANGE = "DEFAULT_PROJECT_MANAGER_NOT_CHANGE";   //  기본 제공되는 프로젝트의 관리자는 업무 공간 관리자가 있어야 합니다.
    public static final String ISSUE_STATUS_NAME_MAX_LENGTH_OUT = "ISSUE_STATUS_NAME_MAX_LENGTH_OUT";   //  이슈 상태명은 최대 50글자까지 입력할 수 있습니다.
    public static final String ISSUE_STATUS_NOT_NAME = "ISSUE_STATUS_NOT_NAME"; //  이슈 상태 명이 입력되지 않았습니다.
    public static final String ISSUE_STATUS_NOT_COLOR = "ISSUE_STATUS_NOT_COLOR";   //  이슈 상태의 색상이 입력되지 않았습니다.
    public static final String ISSUE_STATUS_USED_NAME = "ISSUE_STATUS_USED_NAME";    //    이슈 상태 이름이 이미 사용되고 있습니다.
    public static final String ISSUE_STATUS_REMOVE_NOT_SELECT = "ISSUE_STATUS_REMOVE_NOT_SELECT";   //  삭제할 이슈 상태가 선택되지 않았습니다.
    public static final String DEFAULT_ISSUE_STATUS_NOT_REMOVE = "DEFAULT_ISSUE_STATUS_NOT_REMOVE"; //  기본으로 제공되는 이슈 상태는 삭제할 수 없습니다.
    public static final String ISSUE_STATUS_NOT_EXIST = "ISSUE_STATUS_NOT_EXIST";  //  이슈 상태가 존재하지 않습니다.
    public static final String READY_ISSUE_STATUS_NOT_EXIST = "READY_ISSUE_STATUS_NOT_EXIST";   //  이슈 상태 유형이 대기인 이슈 상태를 찾을 수 없습니다.
    public static final String ISSUE_STATUS_CHANGE_NOT_TARGET = "ISSUE_STATUS_CHANGE_NOT_TARGET";   //  선택한 이슈 상태로 변경할 수 없습니다. 워크플로우를 확인하세요.
    public static final String ISSUE_STATUS_USE_ISSUES = "ISSUE_STATUS_USE_ISSUES"; //  이슈 상태를 사용하고 있는 이슈가 존재하고 있습니다.
    public static final String ISSUE_STATUS_USE_WORKFLOW = "ISSUE_STATUS_USE_WORKFLOW"; //  이슈 상태를 사용하고 있는 워크플로우가 존재하고 있습니다.
    public static final String WORKFLOW_NOT_EXIST = "WORKFLOW_NOT_EXIST";   //  워크플로우가 존재하지 않습니다.
    public static final String WORKFLOW_NOT_NAME = "WORKFLOW_NOT_NAME"; //  워크플로우 명이 입력되지 않았습니다.
    public static final String WORKFLOW_NAME_MAX_LENGTH_OUT = "WORKFLOW_NAME_MAX_LENGTH_OUT";   //  워크플로우 명은 최대 20글자까지 입력할 수 있습니다.
    public static final String WORKFLOW_USED_NAME = "WORKFLOW_USED_NAME";   //  워크플로우 명이 이미 사용되고 있습니다.
    public static final String WORKFLOW_TRANSITION_NOT_EXIST = "WORKFLOW_TRANSITION_NOT_EXIST"; //  워크플로우 전이가 존재하지 않습니다.
    public static final String WORKFLOW_USED_ISSUE_TYPE = "WORKFLOW_USED_ISSUE_TYPE";   //  워크플로우가 이슈 타입에서 사용되고 있어서 삭제할 수 없습니다.
    public static final String WORKFLOW_ISOLATION = "WORKFLOW_ISOLATION"; //  워크플로우에 고립된 이슈 상태가 존재합니다.
    public static final String WORKFLOW_REMOVE_NOT_SELECT = "WORKFLOW_REMOVE_NOT_SELECT"; //  삭제할 워크플로우가 선택되지 않았습니다.
    public static final String WORKFLOW_REQUIRE_ISSUE_STATUS_TYPE_TO_READY = "WORKFLOW_REQUIRE_ISSUE_STATUS_TYPE_TO_READY"; //  워크플로우에는 상태 속성 '대기' 인 이슈 상태가 1개 이상 존재해야 합니다.
    public static final String WORKFLOW_REQUIRE_ISSUE_STATUS_TYPE_TO_OPEN = "WORKFLOW_REQUIRE_ISSUE_STATUS_TYPE_TO_OPEN"; //  워크플로우에는 상태 속성 '진행' 인 이슈 상태가 1개 이상 존재해야 합니다.
    public static final String WORKFLOW_REQUIRE_ISSUE_STATUS_TYPE_TO_CLOSE = "WORKFLOW_REQUIRE_ISSUE_STATUS_TYPE_TO_CLOSE"; //  워크플로우에는 상태 속성 '종료' 인 이슈 상태가 1개 이상 존재해야 합니다.
    public static final String CUSTOM_FIELD_NOT_EXIST = "CUSTOM_FIELD_NOT_EXIST";   //  사용자 정의 필드가 존재하지 않습니다.
    public static final String CUSTOM_FIELD_NOT_NAME = "CUSTOM_FIELD_NOT_NAME"; //  사용자 정의 필드명이 입력되지 않았습니다.
    public static final String CUSTOM_FIELD_NAME_MAX_LENGTH_OUT = "CUSTOM_FIELD_NAME_MAX_LENGTH_OUT";   //  사용자 정의 필드명은 최대 50글자까지 입력할 수 있습니다.
    public static final String CUSTOM_FIELD_USED_NAME = "CUSTOM_FIELD_USED_NAME";   //  사용자 정의 필드 명이 이미 사용되고 있습니다.
    public static final String CUSTOM_FIELD_REMOVE_NOT_SELECT = "CUSTOM_FIELD_REMOVE_NOT_SELECT";   //  삭제할 사용자 정의 필드가 선택되지 않았습니다.
    public static final String CUSTOM_FIELD_OPTIONS_NOT_USE_INPUT_FIELD = "CUSTOM_FIELD_OPTIONS_NOT_USE_INPUT_FIELD";   //  문자열 필드는 옵션 값을 사용할 수 없습니다.
    public static final String CUSTOM_FIELD_OPTIONS_NOT_EXIST_DEFAULT_VALUE = "CUSTOM_FIELD_OPTIONS_NOT_EXIST_DEFAULT_VALUE";   //  사용자 정의 필드 기본 값이 옵션에 존재하지 않습니다.
    public static final String CUSTOM_FIELD_OPTIONS_USED_EXIST_DEFAULT_VALUE = "CUSTOM_FIELD_OPTIONS_USED_EXIST_DEFAULT_VALUE";   //  사용자 정의 필드 기본 값에 중복된 값이 존재합니다.
    public static final String CUSTOM_FIELD_OPTIONS_NOT_USE_MULTI_DEFAULT_VALUE = "CUSTOM_FIELD_OPTIONS_NOT_USE_MULTI_DEFAULT_VALUE";   //  사용자 정의 필드 옵션 값에 대한 기본 값으로 2개 이상 지정할 수 없습니다.
    public static final String CUSTOM_FIELD_OPTIONS_NOT_EMPTY_VALUE = "CUSTOM_FIELD_OPTIONS_NOT_EMPTY_VALUE";   //  사용자 정의 필드 옵션 값에 공백을 등록할 수 없습니다.
    public static final String CUSTOM_FIELD_OPTIONS_NOT_VALUE = "CUSTOM_FIELD_OPTIONS_NOT_VALUE";   //  사용자 정의 필드 옵션 값이 입력되지 않았습니다.
    public static final String CUSTOM_FIELD_TEXT_TYPE_MAX_LENGTH_OUT = "CUSTOM_FIELD_TEXT_TYPE_MAX_LENGTH_OUT"; //  사용자 정의 문자열 필드는 최대 100글자까지 입력할 수 있습니다.
    public static final String CUSTOM_FIELD_DEFAULT_VALUE_MAX_LENGTH_OUT = "CUSTOM_FIELD_DEFAULT_VALUE_MAX_LENGTH_OUT"; //  사용자 정의 필드의 기본 값 필드는 최대 100글자까지 입력할 수 있습니다.
    public static final String CUSTOM_FIELD_OPTION_VALUE_MAX_LENGTH_OUT = "CUSTOM_FIELD_OPTION_VALUE_MAX_LENGTH_OUT";   //  사용자 정의 필드 옵션 값은 최대 15글자까지 입력할 수 있습니다.
    public static final String ISSUE_TYPE_NOT_EXIST = "ISSUE_TYPE_NOT_EXIST";   //  이슈 유형이 존재하지 않습니다.
    public static final String ISSUE_TYPE_NOT_NAME = "ISSUE_TYPE_NOT_NAME"; //  이슈 유형 명이 입력되지 않았습니다.
    public static final String ISSUE_TYPE_NAME_MAX_LENGTH_OUT = "ISSUE_TYPE_NAME_MAX_LENGTH_OUT";   //  이슈 유형은 최대 50글자까지 입력할 수 있습니다.
    public static final String ISSUE_TYPE_USED_NAME = "ISSUE_TYPE_USED_NAME";   //  이슈 유형 명이 이미 사용되고 있습니다.
    public static final String ISSUE_TYPE_REMOVE_NOT_SELECT = "ISSUE_TYPE_REMOVE_NOT_SELECT";   //  삭제할 이슈 유형이 선택되지 않았습니다.
    public static final String ISSUE_TYPE_NOT_COLOR = "ISSUE_TYPE_NOT_COLOR";   //  이슈 유형에 색상이 입력되지 않았습니다.
    public static final String ISSUE_TYPE_USE_ISSUES = "ISSUE_TYPE_USE_ISSUES"; //  이슈 유형을 사용하고 있는 이슈가 존재하고 있습니다.
    public static final String PRIORITY_NOT_EXIST = "PRIORITY_NOT_EXIST";   //  우선순위가 존재하지 않습니다.
    public static final String SEVERITY_NOT_EXIST = "SEVERITY_NOT_EXIST";   //  중요도가 존재하지 않습니다.
    public static final String ISSUE_NOT_EXIST = "ISSUE_NOT_EXIST"; //  이슈가 존재하지 않습니다.
    public static final String ISSUE_NUMBER_GENERATOR_NOT_EXIST = "ISSUE_NUMBER_GENERATOR_NOT_EXIST";   //  이슈 번호 제너레이터가 존재하지 않습니다.
    public static final String ISSUE_NUMBER_NOT_EXIST = "ISSUE_NUMBER_NOT_EXIST";   //  이슈 번호가 존재하지 않습니다.
    public static final String ISSUE_TITLE_MAX_LENGTH_OUT = "ISSUE_TITLE_MAX_LENGTH_OUT";   //  이슈 제목은 최대 300글자까지 입력할 수 있습니다.
    public static final String ISSUE_NOT_MODIFY_PERMISSION = "ISSUE_NOT_MODIFY_PERMISSION"; //  이슈 수정 권한이 없습니다.
    public static final String ISSUE_REMOVE_NOT_SELECT = "ISSUE_REMOVE_NOT_SELECT"; //  삭제할 이슈가 선택되지 않았습니다.
    public static final String ISSUE_NO_TITLE = "ISSUE_NO_TITLE";   //  이슈 제목이 입력되지 않았습니다.
    public static final String ISSUE_NOT_SEND_USER = "ISSUE_NOT_SEND_USER";   //  이슈 발송 대상자를 선택하지 않았습니다.
    public static final String ISSUE_COMMENT_REMOVE_NOT_SELECT = "ISSUE_COMMENT_REMOVE_NOT_SELECT"; //  삭제할 댓글을 선택하지 않았습니다.
    public static final String ISSUE_COMMENT_NOT_EXIST = "ISSUE_COMMENT_NOT_EXIST"; //  댓글이 존재하지 않습니다.
    public static final String ISSUE_COMMENT_NOT_REMOVE_PERMISSION = "ISSUE_COMMENT_NOT_REMOVE_PERMISSION"; //  댓글을 삭제할 수 있는 권한이 없습니다.
    public static final String ISSUE_COMMENT_NOT_COMMENT = "ISSUE_COMMENT_NOT_COMMENT"; //  댓글이 입력되지 않았습니다.
    public static final String ISSUE_COMMENT_MAX_LENGTH_OUT = "ISSUE_COMMENT_MAX_LENGTH_OUT"; //  댓글은 최대 300글자까지 입력할 수 있습니다.
    public static final String ISSUE_RESERVATION_NOT_EXIST = "ISSUE_RESERVATION_NOT_EXIST"; //  이슈 발생 예약 정보가 존재하지 않습니다.
    public static final String ISSUE_RESERVATION_VALUE_INVALID = "ISSUE_RESERVATION_VALUE_INVALID"; //  이슈 발생 예약일이 잘못되었습니다.
    public static final String USER_WORKSPACE_NOT_EXIST = "USER_WORKSPACE_NOT_EXIST";   //  업무 공간 사용자 연결 정보가 존재하지 않습니다.
    public static final String WIDGET_SEARCH_DATE_NOT_FOUND = "WIDGET_SEARCH_DATE_NOT_FOUND";   //  위젯 검색 일자를 찾을 수 없습니다.
    public static final String USER_NO_NAME = "USER_NO_NAME";   //  이름이 입력 되지 않았습니다.
    public static final String USER_NAME_LENGTH_EXCESS = "USER_NAME_LENGTH_EXCESS"; //  사용자 이름은 최대 50글자만 가능합니다.
    public static final String USER_NO_EMAIL = "USER_NO_EMAIL"; //  이메일 주소가 입력 되지 않았습니다.
    public static final String USER_INVALID_EMAIL = "INVALID_EMAIL_ADDRESS";    //  잘못된 이메일 주소 입니다.
    public static final String USER_USED_EMAIL = "USER_USED_EMAIL"; //  이미 가입된 이메일 주소 입니다.
    public static final String USER_NO_PASSWORD = "USER_NO_PASSWORD";   //  비밀번호가 입력 되지 않았습니다.
    public static final String USER_NOT_MODIFY_SELF = "USER_NOT_MODIFY_SELF";   //  다른 사람의 정보를 수정할 수 없습니다.
    public static final String USER_PHONE_MAX_LENGTH_OUT = "USER_PHONE_MAX_LENGTH_OUT"; //  연락처는 최대 20글자까지 입력할 수 있습니다.
    public static final String USER_PHONE_ONLY_NUMBER = "USER_PHONE_ONLY_NUMBER";   //  연락처는 숫자만 입력할 수 있습니다.
    public static final String USER_PASSWORD_MAX_LENGTH_OUT = "USER_PASSWORD_MAX_LENGTH_OUT"; //  비밀번호는 최대 20글자까지 입력할 수 있습니다.
    public static final String USER_INVALID_CURRENT_PASSWORD = "USER_INVALID_CURRENT_PASSWORD"; //  입력한 현재 비밀번호가 잘못되었습니다.
    public static final String USER_INVALID_LICENSEKEY = "USER_INVALID_LICENSEKEY"; //  입력한 현재 라이센스가 잘못되었습니다.
    public static final String USER_PASSWORD_SAME_NEW_PASSWORD = "USER_PASSWORD_SAME_NEW_PASSWORD"; //  현재 사용하는 비밀번호와 변경하려는 비밀번호는 달라야 합니다.
    public static final String USER_PASSWORD_NOT_SAME_CONFIRM_PASSWORD = "USER_PASSWORD_NOT_SAME_CONFIRM_PASSWORD"; //  신규 비밀번호와 신규 비밀번호 확인 필드에 입력한 비밀번호가 다릅니다.
    public static final String USER_WITH_DRAW_EXIST = "USER_WITH_DRAW_EXIST";   //  해당 계정은 회원 탈퇴 기록이 있습니다.
    public static final String USER_PROFILE_SIZE_NOT_ALLOW = "USER_PROFILE_SIZE_NOT_ALLOW"; //  사용자 프로필 사이즈가 너무 큽니다. 10MB 이하 파일로 업로드해주세요.
    public static final String USER_PROFILE_UPLOAD_FILE_TYPE_NOT_ALLOW = "USER_PROFILE_UPLOAD_FILE_TYPE_NOT_ALLOW"; //  프로필 파일은 jpg, png 만 가능합니다.
    public static final String USER_NOT_EQUAL_PASSWORD = "USER_NOT_EQUAL_PASSWORD"; //  비밀번호가 맞지 않습니다.
    public static final String USER_NOT_EXIST = "USER_NOT_EXIST";   //  사용자가 존재하지 않습니다.
    public static final String USER_NOT_AUTHORIZED = "USER_NOT_AUTHORIZED"; //  사용자 인증 권한이 없습니다.
    public static final String USER_EXPIRED_PASSWORD = "USER_EXPIRED_PASSWORD"; //  비밀번호가 만료되었습니다.
    public static final String USER_RETURN_PASSWORD_NOT_PROVIDER_SOCIAL_JOIN_USER = "USER_RETURN_PASSWORD_NOT_PROVIDER_SOCIAL_JOIN_USER";   //  비밀번호 찾기 기능을 소셜 계정 가입 사용자는 사용할 수 없습니다.
    public static final String USER_NOT_USE_ACTIVE_STATUS = "USER_NOT_USE_ACTIVE_STATUS";   //  사용자는 활성 상태가 아니면 로그인할 수 없습니다.
    public static final String EXCEL_NOT_EXTENSION = "EXCEL_NOT_EXTENSION"; //  엑셀 파일 확장자 (xlsx)만 업로드가 가능합니다.
    public static final String EXCEL_DOWNLOAD_MAX_ROWS_OVER = "EXCEL_DOWNLOAD_MAX_ROWS_OVER";   //  검색된 엑셀 행이 1,000건을 초과하여 다운로드 할 수 없습니다. 검색 조건을 사용하여 1,000 건 이하로 다운로드를 진행해야 합니다.
    public static final String EXCEL_IMPORT_MAX_ROWS_OVER = "EXCEL_IMPORT_MAX_ROWS_OVER";   //  엑셀 업로드로 이슈 등록은 최대 1,000 건까지만 가능합니다.
    public static final String EXCEL_CONDITIONS_NOT_EXIST = "EXCEL_CONDITIONS_NOT_EXIST";   //  엑셀 다운로드에 필요한 검색 조건을 찾을 수 없습니다.
    public static final String EXCEL_EMPTY_CELL = "EXCEL_EMPTY_CELL"; //  엑셀 헤더 부분 셀을 찾을 수 없습니다. 엑셀 작성 양식에 문제가 있습니다.
    public static final String EXCEL_HEADER_EMPTY_CELL = "EXCEL_HEADER_EMPTY_CELL"; //  엑셀 헤더에 빈 셀이 있습니다.
    public static final String EXCEL_IMPORT_ISSUE_TITLE_IS_NULL = "EXCEL_IMPORT_ISSUE_TITLE_IS_NULL";   //  다음 엑셀 라인에서 이슈 제목이 입력지 않았습니다.
    public static final String EXCEL_IMPORT_PROJECT_KEY_IS_NULL = "EXCEL_IMPORT_PROJECT_KEY_IS_NULL";   //  다음 엑셀 라인에서 프로젝트 키가 입력되지 않았습니다.
    public static final String EXCEL_IMPORT_PROJECT_NOT_EXIST = "EXCEL_IMPORT_PROJECT_NOT_EXIST";   //  다음 엑셀 라인에서 입력된 프로젝트 키로 검색되는 프로젝트가 없습니다.
    public static final String EXCEL_IMPORT_ISSUE_TYPE_IS_NULL = "EXCEL_IMPORT_ISSUE_TYPE_IS_NULL";   //  다음 엑셀 라인에서 이슈 타입 명이 입력되지 않았습니다.
    public static final String EXCEL_IMPORT_ISSUE_TYPE_NOT_EXIST = "EXCEL_IMPORT_ISSUE_TYPE_NOT_EXIST";   //  다음 엑셀 라인에서 입력된 이슈 타입 명으로 검색되는 이슈 타입이 없습니다.
    public static final String EXCEL_IMPORT_ISSUE_STATUS_READY_NOT_EXIST = "EXCEL_IMPORT_ISSUE_STATUS_READY_NOT_EXIST";   //  다음 엑셀 라인에서 입력된 이슈 타입의 워크플로우에서 상태 속성 '대기'인 상태가 없습니다.
    public static final String EXCEL_IMPORT_PRIORITY_IS_NULL = "EXCEL_IMPORT_PRIORITY_IS_NULL";   //  다음 엑셀 라인에서 우선순위 명이 입력되지 않았습니다.
    public static final String EXCEL_IMPORT_PRIORITY_NOT_EXIST = "EXCEL_IMPORT_PRIORITY_NOT_EXIST";   //  다음 엑셀 라인에서 입력된 우선순위 명으로 검색되는 우선순위가 없습니다.
    public static final String EXCEL_IMPORT_SEVERITY_IS_NULL = "EXCEL_IMPORT_SEVERITY_IS_NULL";   //  다음 엑셀 라인에서 중요도 명이 입력되지 않았습니다.
    public static final String EXCEL_IMPORT_SEVERITY_NOT_EXIST = "EXCEL_IMPORT_SEVERITY_NOT_EXIST";   //  다음 엑셀 라인에서 입력된 중요도 명으로 검색되는 우선순위가 없습니다.
    public static final String EXCEL_IMPORT_PERIOD_NOT_VALIDITY = "EXCEL_IMPORT_PERIOD_NOT_VALIDITY"; //  다음 엑셀 라인에서 입력한 시작일, 종료일에 문제가 있습니다. 시작일은 종료일보다 빨라야 합니다.
    public static final String EXCEL_IMPORT_PERIOD_NOT_VALIDITY_EMPTY = "EXCEL_IMPORT_PERIOD_NOT_VALIDITY_EMPTY"; //  다음 엑셀 라인에서 입력한 시작일, 종료일에 문제가 있습니다. 공백이 포함되어 있는지 확인 후 공백을 제거하세요.
    public static final String EXCEL_CUSTOM_FIELD_VALUE_NOT_VALIDITY = "EXCEL_CUSTOM_FIELD_VALUE_NOT_VALIDITY"; //  다음 엑셀 라인에서 입력한 사용자 정의 필드 값이 유효하지 않습니다.
    public static final String EXCEL_IMPORT_HEADER_CUSTOM_FIELD_NOT_EXIST = "EXCEL_IMPORT_HEADER_CUSTOM_FIELD_NOT_EXIST";   //  등록하려는 사용자 정의 필드를 찾을 수 없습니다. 해당 사용자 정의필드의 이름이 변경되었거나 삭제되었습니다.
    public static final String NOTICE_NOT_EXIST = "NOTICE_NOT_EXIST";   //  공지사항이 존재하지 않습니다.
    public static final String NOTICE_EMPTY_CONTENT = "NOTICE_EMPTY_CONTENT";   //  공지사항 제목 및 내용 중 입력 값이 없는 필드가 있습니다.
    public static final String FAQ_NOT_EXIST = "FAQ_NOT_EXIST";   //  FAQ가 존재하지 않습니다.
    public static final String FAQ_EMPTY_CONTENT = "FAQ_EMPTY_CONTENT";   //  FAQ 제목 및 내용 중 입력 값이 없는 필드가 있습니다.
    public static final String GUIDE_NOT_EXIST="GUIDE_NOT_EXIST";
    public static final String GUIDE_EMPTY_CONTENT="GUIDE_EMPTY_CONTENT";
    public static final String EVENT_NOT_EXIST="EVENT_NOT_EXIST";
    public static final String EVENT_EMPTY_CONTENT="EVENT_EMPTY_CONTENT";
    public static final String QNA_NOT_EXIST = "QNA_NOT_EXIST";   //  QNA 존재하지 않습니다.
    public static final String QNA_EMPTY_CONTENT = "QNA_EMPTY_CONTENT";   //  QNA 제목 및 내용 중 입력 값이 없는 필드가 있습니다.
    public static final String RESERVATION_EMAIL_TITLE = "RESERVATION_EMAIL_TITLE"; //  [OWL ITS] 어제 이슈 및 활동 현황입니다.
    public static final String FILE_TYPE_NOT_ALLOW = "FILE_TYPE_NOT_ALLOW"; //  허용되지 않은 파일 유형입니다.
    public static final String ERR_FAILED_CONVERT_OBJECT = "ERR_FAILED_CONVERT_OBJECT"; //  데이터 변환을 실패했습니다.
    public static final String TARGET_OBJECT_IS_NULL = "TARGET_OBJECT_IS_NULL"; //  타겟 객체가 Null 입니다.
    public static final String SOURCE_OBJECT_IS_NULL = "SOURCE_OBJECT_IS_NULL"; //  소스 객체가 Null 입니다.
    public static final String ERR_FAILED_CONVERT_JSON = "ERR_FAILED_CONVERT_JSON"; //  요청 데이터의 JSON 변환을 실패했습니다.
    public static final String NOT_READABLE_JSON_DATA = "NOT_READABLE_JSON_DATA";   //  JSON 데이터를 읽을 수 없습니다.
    public static final String SUCCESS_REQUEST = "SUCCESS_REQUEST"; //  요청이 성공하였습니다.
    public static final String SESSION_EXPIRED = "SESSION_EXPIRED"; //  사용자 세션이 만료 되었습니다.
    public static final String PAGE_NOT_EXIST_INFO = "PAGE_NOT_EXIST_INFO"; //  페이지 정보를 찾을 수 없습니다.
    public static final String PAGE_NEGATIVE_OR_NULL = "PAGE_NEGATIVE_OR_NULL"; //  요청한 페이지 정보가 잘못되었습니다.
    public static final String PAGE_SIZE_NEGATIVE_OR_NULL = "PAGE_SIZE_NEGATIVE_OR_NULL";   //  요청한 페이지 크기가 잘못되었습니다.
}
src/main/java/kr/wisestone/owl/domain/AttachedFile.java
New file
@@ -0,0 +1,136 @@
package kr.wisestone.owl.domain;
import kr.wisestone.owl.domain.enumType.AttachedType;
import kr.wisestone.owl.domain.enumType.FileType;
import javax.persistence.*;
import java.io.Serializable;
@Entity
public class AttachedFile extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 847376294732544822L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String path;
    private Long size;
    private String contentType;
    private String awsKey;
    @Enumerated(EnumType.STRING)
    private FileType fileType;
    @Enumerated(EnumType.STRING)
    private AttachedType attachedType;
    @ManyToOne(fetch= FetchType.EAGER)
    @JoinColumn(name="issue_id")
    private Issue issue;
    //  파일 다운로드 오류 해결
    @ManyToOne(fetch= FetchType.EAGER)
    @JoinColumn(name="workspace_id")
    private Workspace workspace;
    public AttachedFile() {
    }
    public AttachedFile(String name, Long size, String contentType, String path) {
        this.name = name;
        this.path = path;
        this.size = size;
        this.contentType = contentType;
    }
    public AttachedFile(String name, Long size, String contentType, String path, String awsKey, Issue issue, Workspace workspace, FileType fileType, AttachedType attachedType) {
        this.name = name;
        this.size = size;
        this.contentType = contentType;
        this.path = path;
        this.awsKey = awsKey;
        this.issue = issue;
        this.workspace = workspace;
        this.fileType = fileType;
        this.attachedType = attachedType;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPath() {
        return path;
    }
    public void setPath(String path) {
        this.path = path;
    }
    public Long getSize() {
        return size;
    }
    public void setSize(Long size) {
        this.size = size;
    }
    public String getContentType() {
        return contentType;
    }
    public void setContentType(String contentType) {
        this.contentType = contentType;
    }
    public Issue getIssue() {
        return issue;
    }
    public void setIssue(Issue issue) {
        this.issue = issue;
    }
    public Workspace getWorkspace() {
        return workspace;
    }
    public void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }
    public FileType getFileType() {
        return fileType;
    }
    public void setFileType(FileType fileType) {
        this.fileType = fileType;
    }
    public String getAwsKey() {
        return awsKey;
    }
    public void setAwsKey(String awsKey) {
        this.awsKey = awsKey;
    }
    public AttachedType getAttachedType() {
        return attachedType;
    }
    public void setAttachedType(AttachedType attachedType) {
        this.attachedType = attachedType;
    }
}
src/main/java/kr/wisestone/owl/domain/BaseEntity.java
New file
@@ -0,0 +1,76 @@
/**
 * 상기 프로그램에 대한 저작권을 포함한 지적재산권은 WiseStone에 있으며,
 * WiseStone이 명시적으로 허용하지 않은 사용, 복사, 변경, 제3자에의 공개,
 * 배포는 엄격히 금지되며, WiseStone의 지적재산권 침해에 해당됩니다.
 * (Copyright ⓒ 2014 WiseStone Co., Ltd. All Rights Reserved|Confidential)
 * -----------------------------------------------------------------------------
 * You are strictly prohibited to copy, disclose, distribute, modify,
 * or use this program in part or as a whole without the prior written
 * consent of WiseStone Co., Ltd. WiseStone Co., Ltd., owns the
 * intellectual property rights in and to this program.
 * (Copyright ⓒ 2014 WiseStone Co., Ltd. All Rights Reserved|Confidential)
 * -----------------------------------------------------------------------------
 */
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.util.Date;
@MappedSuperclass
public class BaseEntity {
    @Basic
    @Column(insertable = true, updatable = false)
    Long registerId;
    @Column(insertable = true, updatable = false)
    @Temporal(TemporalType.TIMESTAMP)
    Date registerDate;
    @Basic
    @Column(insertable = true, updatable = true)
    Long modifyId;
    @Column(insertable = true, updatable = true)
    @Temporal(TemporalType.TIMESTAMP)
    Date modifyDate;
    public Long getRegisterId() {
        return registerId;
    }
    public void setRegisterId(Long registerId) {
        this.registerId = registerId;
    }
    public Date getRegisterDate() {
        return registerDate;
    }
    public void setRegisterDate(Date registerDate) {
        this.registerDate = registerDate;
    }
    public Long getModifyId() {
        return modifyId;
    }
    public void setModifyId(Long modifyId) {
        this.modifyId = modifyId;
    }
    public Date getModifyDate() {
        return modifyDate;
    }
    public void setModifyDate(Date modifyDate) {
        this.modifyDate = modifyDate;
    }
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("BaseEntity [registerId=").append(this.registerId)
                .append(", registerDate=").append(this.registerDate)
                .append(", modifyId=").append(this.modifyId).append(", modifyDate=")
                .append(this.modifyDate).append("]");
        return builder.toString();
    }
}
src/main/java/kr/wisestone/owl/domain/CustomField.java
New file
@@ -0,0 +1,113 @@
package kr.wisestone.owl.domain;
import kr.wisestone.owl.domain.enumType.CustomFieldType;
import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
/**
 * Created by wisestone on 2018-03-07.
 */
@Entity
public class CustomField extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @Enumerated(EnumType.STRING)
    private CustomFieldType customFieldType;
    private String defaultValue;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "workspace_id")
    private Workspace workspace;
    @OneToMany(mappedBy = "customField", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<IssueTypeCustomField> issueTypeCustomFields = new HashSet<>();
    @OrderBy("id asc")
    @OneToMany(mappedBy = "customField", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<CustomFieldValue> customFieldValues = new HashSet<>();
    @OneToMany(mappedBy = "customField", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<IssueCustomFieldValue> issueCustomFieldValues = new HashSet<>();
    private String useFlag;
    public CustomField() {
    }
    public String getUse() { return useFlag; }
    public void setUse(String useFlag) { this.useFlag = useFlag; }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public CustomFieldType getCustomFieldType() {
        return customFieldType;
    }
    public void setCustomFieldType(CustomFieldType customFieldType) {
        this.customFieldType = customFieldType;
    }
    public String getDefaultValue() {
        return defaultValue;
    }
    public void setDefaultValue(String defaultValue) {
        this.defaultValue = defaultValue;
    }
    public Workspace getWorkspace() {
        return workspace;
    }
    public void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }
    public Set<IssueTypeCustomField> getIssueTypeCustomFields() {
        return issueTypeCustomFields;
    }
    public void setIssueTypeCustomFields(Set<IssueTypeCustomField> issueTypeCustomFields) {
        this.issueTypeCustomFields = issueTypeCustomFields;
    }
    public Set<CustomFieldValue> getCustomFieldValues() {
        return customFieldValues;
    }
    public void setCustomFieldValues(Set<CustomFieldValue> customFieldValues) {
        this.customFieldValues = customFieldValues;
    }
    public Set<IssueCustomFieldValue> getIssueCustomFieldValues() {
        return issueCustomFieldValues;
    }
    public void setIssueCustomFieldValues(Set<IssueCustomFieldValue> issueCustomFieldValues) {
        this.issueCustomFieldValues = issueCustomFieldValues;
    }
}
src/main/java/kr/wisestone/owl/domain/CustomFieldValue.java
New file
@@ -0,0 +1,52 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
/**
 * Created by wisestone on 2018-03-07.
 */
@Entity
public class CustomFieldValue extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String value;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "custom_field_id")
    private CustomField customField;
    public CustomFieldValue(){}
    public CustomFieldValue(CustomField customField, String value){
        this.customField = customField;
        this.value = value;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getValue() {
        return value;
    }
    public void setValue(String value) {
        this.value = value;
    }
    public CustomField getCustomField() {
        return customField;
    }
    public void setCustomField(CustomField customField) {
        this.customField = customField;
    }
}
src/main/java/kr/wisestone/owl/domain/Event.java
New file
@@ -0,0 +1,77 @@
package kr.wisestone.owl.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.io.Serializable;
/**
 * Create By J E O N G - S U N / 2019-05-22
 */
@Entity
public class Event extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    public static final Integer ACTIVATION = 1;  //  활성
    public static final Integer INACTIVATION = 0;  //  비활성
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String description;
    private Integer status;
    private String startDate;
    private String endDate;
    public Event(){}
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public Integer getStatus() {
        return status;
    }
    public void setStatus(Integer status) {
        this.status = status;
    }
    public String getStartDate() {
        return startDate;
    }
    public void setStartDate(String startDate) {
        this.startDate = startDate;
    }
    public String getEndDate() {
        return endDate;
    }
    public void setEndDate(String endDate) {
        this.endDate = endDate;
    }
}
src/main/java/kr/wisestone/owl/domain/Faq.java
New file
@@ -0,0 +1,59 @@
package kr.wisestone.owl.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.io.Serializable;
/**
 * Create By J E O N G - S U N / 2019-05-22
 */
@Entity
public class Faq extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    public static final Integer ACTIVATION = 1;  //  활성
    public static final Integer INACTIVATION = 0;  //  비활성
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String description;
    private Integer status;
    public Faq(){}
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public Integer getStatus() {
        return status;
    }
    public void setStatus(Integer status) {
        this.status = status;
    }
}
src/main/java/kr/wisestone/owl/domain/Guide.java
New file
@@ -0,0 +1,59 @@
package kr.wisestone.owl.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.io.Serializable;
/**
 * Create By J E O N G - S U N / 2019-05-22
 */
@Entity
public class Guide extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    public static final Integer ACTIVATION = 1;  //  활성
    public static final Integer INACTIVATION = 0;  //  비활성
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String description;
    private Integer status;
    public Guide(){}
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public Integer getStatus() {
        return status;
    }
    public void setStatus(Integer status) {
        this.status = status;
    }
}
src/main/java/kr/wisestone/owl/domain/Issue.java
New file
@@ -0,0 +1,277 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
/**
 * Created by wisestone on 2018-01-03.
 */
@Entity
public class Issue extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    public static final String WORKSPACE_MANAGER = "WORKSPACE_MANAGER"; //  업무 공간 관리자
    public static final String PROJECT_MANAGER = "PROJECT_MANAGER"; //  프로젝트 관리자
    public static final String REGISTER = "REGISTER";   //  이슈 등록자
    public static final String ASSIGNEE = "ASSIGNEE";   //  이슈 담당자
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String description;
    private Long reverseIndex;
    private Long issueNumber;
    private String startDate;
    private String completeDate;
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name = "project_id")
    private Project project;
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name = "issue_status_id")
    private IssueStatus issueStatus;
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name = "issue_type_id")
    private IssueType issueType;
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name = "priority_id")
    private Priority priority;
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name = "severity_id")
    private Severity severity;
    @OneToOne(mappedBy = "issue", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private IssueRisk issueRisk;
    @OrderBy(value="id DESC")
    @OneToMany(mappedBy = "issue", cascade = { CascadeType.ALL }, orphanRemoval = true)
    private Set<IssueUser> issueUsers = new HashSet<>();
    @OrderBy(value="id DESC")
    @OneToMany(mappedBy="issue", cascade={CascadeType.ALL}, orphanRemoval = true)
    private Set<AttachedFile> attachedFiles = new HashSet<>();
    @OneToMany(mappedBy = "issue", cascade = { CascadeType.ALL }, orphanRemoval = true)
    private Set<UserLikeIssue> userLikeIssues = new HashSet<>();
    //  이력은 삭제 금지
    @OrderBy(value="id DESC")
    @OneToMany(mappedBy="issue", cascade={CascadeType.ALL}, orphanRemoval = true)
    private Set<IssueHistory> issueHistory = new HashSet<>();
    @OneToMany(mappedBy="issue", cascade={CascadeType.ALL}, orphanRemoval = true)
    @OrderBy(value="id DESC")
    private Set<IssueComment> issueComments = new HashSet<>();
    @OneToMany(mappedBy = "issue", cascade = { CascadeType.ALL }, orphanRemoval = true)
    private Set<IssueCustomFieldValue> issueCustomFieldValues = new HashSet<>();
    @OneToMany(mappedBy = "issue", cascade = { CascadeType.ALL }, orphanRemoval = true)
    private Set<IssueVersion> issueVersions = new HashSet<>();
    @OneToMany(mappedBy = "issue", cascade = { CascadeType.ALL }, orphanRemoval = true)
    private Set<IssueRelation> issueRelations = new HashSet<>();
    @OneToOne(mappedBy = "issue", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private IssueReservation issueReservation;
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name = "workflow_status_id")
    private WorkflowStatus workflowStatus;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public Long getReverseIndex() {
        return reverseIndex;
    }
    public void setReverseIndex(Long reverseIndex) {
        this.reverseIndex = reverseIndex;
    }
    public Project getProject() {
        return project;
    }
    public void setProject(Project project) {
        this.project = project;
    }
    public IssueStatus getIssueStatus() {
        return issueStatus;
    }
    public void setIssueStatus(IssueStatus issueStatus) {
        this.issueStatus = issueStatus;
    }
    public IssueType getIssueType() {
        return issueType;
    }
    public void setIssueType(IssueType issueType) {
        this.issueType = issueType;
    }
    public Priority getPriority() {
        return priority;
    }
    public void setPriority(Priority priority) {
        this.priority = priority;
    }
    public Severity getSeverity() {
        return severity;
    }
    public void setSeverity(Severity severity) {
        this.severity = severity;
    }
    public IssueRisk getIssueRisk() {
        return issueRisk;
    }
    public void setIssueRisk(IssueRisk issueRisk) {
        this.issueRisk = issueRisk;
    }
    public Long getIssueNumber() {
        return issueNumber;
    }
    public void setIssueNumber(Long issueNumber) {
        this.issueNumber = issueNumber;
    }
    public Set<IssueUser> getIssueUsers() {
        return issueUsers;
    }
    public void setIssueUsers(Set<IssueUser> issueUsers) {
        this.issueUsers = issueUsers;
    }
    public Set<AttachedFile> getAttachedFiles() {
        return attachedFiles;
    }
    public void setAttachedFiles(Set<AttachedFile> attachedFiles) {
        this.attachedFiles = attachedFiles;
    }
    public Set<UserLikeIssue> getUserLikeIssues() {
        return userLikeIssues;
    }
    public void setUserLikeIssues(Set<UserLikeIssue> userLikeIssues) {
        this.userLikeIssues = userLikeIssues;
    }
    public Set<IssueHistory> getIssueHistory() {
        return issueHistory;
    }
    public void setIssueHistory(Set<IssueHistory> issueHistory) {
        this.issueHistory = issueHistory;
    }
    public Set<IssueComment> getIssueComments() {
        return issueComments;
    }
    public void setIssueComments(Set<IssueComment> issueComments) {
        this.issueComments = issueComments;
    }
    public Set<IssueCustomFieldValue> getIssueCustomFieldValues() {
        return issueCustomFieldValues;
    }
    public void setIssueCustomFieldValues(Set<IssueCustomFieldValue> issueCustomFieldValues) {
        this.issueCustomFieldValues = issueCustomFieldValues;
    }
    public String getStartDate() {
        return startDate;
    }
    public void setStartDate(String startDate) {
        this.startDate = startDate;
    }
    public String getCompleteDate() {
        return completeDate;
    }
    public void setCompleteDate(String completeDate) {
        this.completeDate = completeDate;
    }
    public IssueReservation getIssueReservation() {
        return issueReservation;
    }
    public void setIssueReservation(IssueReservation issueReservation) {
        this.issueReservation = issueReservation;
    }
    public Set<IssueVersion> getIssueVersions() {
        return issueVersions;
    }
    public void setIssueVersions(Set<IssueVersion> issueVersions) {
        this.issueVersions = issueVersions;
    }
    public WorkflowStatus getWorkflowStatus() {
        return workflowStatus;
    }
    public void setWorkflowStatus(WorkflowStatus workflowStatus) {
        this.workflowStatus = workflowStatus;
    }
    public Set<IssueRelation> getIssueRelations() {
        return issueRelations;
    }
    public void setIssueRelations(Set<IssueRelation> issueRelations) {
        this.issueRelations = issueRelations;
    }
    public  void clearIssueRelations() {
        this.issueRelations.clear();
    }
}
src/main/java/kr/wisestone/owl/domain/IssueComment.java
New file
@@ -0,0 +1,63 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
import java.util.*;
@Entity
public class IssueComment extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String description;
    @ManyToOne(fetch= FetchType.LAZY)
    @JoinColumn(name="issue_id")
    private Issue issue;
    @ManyToOne(fetch= FetchType.LAZY)
    @JoinColumn(name="workspace_id")
    private Workspace workspace;
    public IssueComment() {
    }
    public IssueComment(String description) {
        this.description = description;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public Issue getIssue() {
        return issue;
    }
    public void setIssue(Issue issue) {
        this.issue = issue;
    }
    public Workspace getWorkspace() {
        return workspace;
    }
    public void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }
}
src/main/java/kr/wisestone/owl/domain/IssueCustomFieldValue.java
New file
@@ -0,0 +1,78 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
/**
 * Created by wisestone on 2018-03-07.
 */
@Entity
public class IssueCustomFieldValue extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String useValue;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "issue_type_custom_field_id")
    private IssueTypeCustomField issueTypeCustomField;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "issue_id")
    private Issue issue;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "custom_field_id")
    private CustomField customField;
    public IssueCustomFieldValue(){}
    public IssueCustomFieldValue(Issue issue, CustomField customField, IssueTypeCustomField issueTypeCustomField, String userValue){
        this.issue = issue;
        this.customField = customField;
        this.issueTypeCustomField = issueTypeCustomField;
        this.useValue = userValue;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getUseValue() {
        return useValue;
    }
    public void setUseValue(String useValue) {
        this.useValue = useValue;
    }
    public IssueTypeCustomField getIssueTypeCustomField() {
        return issueTypeCustomField;
    }
    public void setIssueTypeCustomField(IssueTypeCustomField issueTypeCustomField) {
        this.issueTypeCustomField = issueTypeCustomField;
    }
    public Issue getIssue() {
        return issue;
    }
    public void setIssue(Issue issue) {
        this.issue = issue;
    }
    public CustomField getCustomField() {
        return customField;
    }
    public void setCustomField(CustomField customField) {
        this.customField = customField;
    }
}
src/main/java/kr/wisestone/owl/domain/IssueHistory.java
New file
@@ -0,0 +1,75 @@
package kr.wisestone.owl.domain;
import kr.wisestone.owl.domain.enumType.IssueHistoryType;
import javax.persistence.*;
import java.io.Serializable;
/**
 * Created by wisestone on 2018-01-26.
 */
@Entity
public class IssueHistory extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String description;
    @Enumerated(EnumType.STRING)
    private IssueHistoryType issueHistoryType;
    @ManyToOne(fetch= FetchType.LAZY)
    @JoinColumn(name="issue_id")
    private Issue issue;
    @ManyToOne(fetch= FetchType.LAZY)
    @JoinColumn(name="project_id")
    private Project project;
    public IssueHistory(){}
    public IssueHistory(Issue issue) {
        this.issue = issue;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public Project getProject() {
        return project;
    }
    public void setProject(Project project) {
        this.project = project;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public IssueHistoryType getIssueHistoryType() {
        return issueHistoryType;
    }
    public void setIssueHistoryType(IssueHistoryType issueHistoryType) {
        this.issueHistoryType = issueHistoryType;
    }
    public Issue getIssue() {
        return issue;
    }
    public void setIssue(Issue issue) {
        this.issue = issue;
    }
}
src/main/java/kr/wisestone/owl/domain/IssueNumberGenerator.java
New file
@@ -0,0 +1,52 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
/**
 * Created by wisestone on 2018-03-07.
 */
@Entity
public class IssueNumberGenerator extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long number;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "project_id")
    private Project project;
    public IssueNumberGenerator(){}
    public IssueNumberGenerator(Project project, Long number) {
        this.project = project;
        this.number = number;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public Project getProject() {
        return project;
    }
    public void setProject(Project project) {
        this.project = project;
    }
    public Long getNumber() {
        return number;
    }
    public void setNumber(Long number) {
        this.number = number;
    }
}
src/main/java/kr/wisestone/owl/domain/IssueRelation.java
New file
@@ -0,0 +1,56 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
/**
 * Create By maprex / 2021-05-13
 */
@Entity
public class IssueRelation extends BaseEntity implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @ManyToOne(fetch= FetchType.LAZY)
    @JoinColumn(name="issue_id")
    private Issue issue;
    @ManyToOne(fetch= FetchType.LAZY)
    @JoinColumn(name="relation_issue_id")
    private Issue relationIssue;
    private Long relationIssueType;
    public void IssueRelation(){}
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public Issue getIssue() {
        return issue;
    }
    public void setIssue(Issue issue) {
        this.issue = issue;
    }
    public Issue getRelationIssue() {
        return relationIssue;
    }
    public void setRelationIssue(Issue issue) {
        this.relationIssue = issue;
    }
    public Long getRelationIssueType() { return  relationIssueType; }
    public void setRelationIssueType(Long relationType) { this.relationIssueType = relationType; }
}
src/main/java/kr/wisestone/owl/domain/IssueReservation.java
New file
@@ -0,0 +1,72 @@
package kr.wisestone.owl.domain;
import kr.wisestone.owl.domain.enumType.IssueReservationType;
import javax.persistence.*;
import java.io.Serializable;
/**
 * Create By J E O N G - S U N / 2019-05-07
 */
@Entity
public class IssueReservation extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Enumerated(EnumType.STRING)
    private IssueReservationType issueReservationType;
    private String reservation;
    @OneToOne
    @JoinColumn(name = "issue_id")
    private Issue issue;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "workspace_id")
    private Workspace workspace;
    public IssueReservation(){}
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public IssueReservationType getIssueReservationType() {
        return issueReservationType;
    }
    public void setIssueReservationType(IssueReservationType issueReservationType) {
        this.issueReservationType = issueReservationType;
    }
    public String getReservation() {
        return reservation;
    }
    public void setReservation(String reservation) {
        this.reservation = reservation;
    }
    public Issue getIssue() {
        return issue;
    }
    public void setIssue(Issue issue) {
        this.issue = issue;
    }
    public Workspace getWorkspace() {
        return workspace;
    }
    public void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }
}
src/main/java/kr/wisestone/owl/domain/IssueRisk.java
New file
@@ -0,0 +1,77 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
/**
 * Created by wisestone on 2018-11-01.
 */
@Entity
public class IssueRisk extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long changeAssigneeCount;
    private Long changeIssueStatusCount;
    private String issueStatusIds;
    @OneToOne
    @JoinColumn(name = "issue_id")
    private Issue issue;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "workspace_id")
    private Workspace workspace;
    public IssueRisk(){}
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public Long getChangeAssigneeCount() {
        return changeAssigneeCount;
    }
    public void setChangeAssigneeCount(Long changeAssigneeCount) {
        this.changeAssigneeCount = changeAssigneeCount;
    }
    public Long getChangeIssueStatusCount() {
        return changeIssueStatusCount;
    }
    public void setChangeIssueStatusCount(Long changeIssueStatusCount) {
        this.changeIssueStatusCount = changeIssueStatusCount;
    }
    public String getIssueStatusIds() {
        return issueStatusIds;
    }
    public void setIssueStatusIds(String issueStatusIds) {
        this.issueStatusIds = issueStatusIds;
    }
    public Issue getIssue() {
        return issue;
    }
    public void setIssue(Issue issue) {
        this.issue = issue;
    }
    public Workspace getWorkspace() {
        return workspace;
    }
    public void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }
}
src/main/java/kr/wisestone/owl/domain/IssueSearch.java
New file
@@ -0,0 +1,60 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
/**
 * Created by wisestone on 2018-03-07.
 */
@Entity
public class IssueSearch extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "workspace_id")
    private Workspace workspace;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
    private String conditions;
    public IssueSearch(){}
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public Workspace getWorkspace() {
        return workspace;
    }
    public void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    public String getConditions() {
        return conditions;
    }
    public void setConditions(String conditions) {
        this.conditions = conditions;
    }
}
src/main/java/kr/wisestone/owl/domain/IssueStatus.java
New file
@@ -0,0 +1,136 @@
package kr.wisestone.owl.domain;
import kr.wisestone.owl.domain.enumType.IssueStatusType;
import org.hibernate.annotations.Type;
import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
/**
 * Created by wisestone on 2018-01-03.
 */
@Entity
public class IssueStatus extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String color;
    private Long position; //  기본 제공되는 이슈 상태에서 연결을 위해 사용
    @Type(type = "yes_no")
    private Boolean defaultYn = Boolean.FALSE;
    @Enumerated(EnumType.STRING)
    private IssueStatusType issueStatusType;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "workspace_id")
    private Workspace workspace;
    @OneToMany(mappedBy = "issueStatus", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<Issue> issues = new HashSet<>();
    @OneToMany(mappedBy="sourceIssueStatus", cascade={CascadeType.ALL}, orphanRemoval=true)
    private Set<WorkflowTransition> sourceWorkflowTransitions = new HashSet<>();
    @OneToMany(mappedBy="targetIssueStatus", cascade={CascadeType.ALL}, orphanRemoval=true)
    private Set<WorkflowTransition> targetWorkflowTransitions = new HashSet<>();
    public IssueStatus() {
    }
    public IssueStatus(Workspace workspace, String name, Boolean defaultYn, String color, IssueStatusType issueStatusType, Long position) {
        this.workspace = workspace;
        this.name = name;
        this.defaultYn = defaultYn;
        this.color = color;
        this.issueStatusType = issueStatusType;
        this.position = position;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Workspace getWorkspace() {
        return workspace;
    }
    public void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }
    public Set<Issue> getIssues() {
        return issues;
    }
    public void setIssues(Set<Issue> issues) {
        this.issues = issues;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
    public IssueStatusType getIssueStatusType() {
        return issueStatusType;
    }
    public void setIssueStatusType(IssueStatusType issueStatusType) {
        this.issueStatusType = issueStatusType;
    }
    public Boolean getDefaultYn() {
        return defaultYn;
    }
    public void setDefaultYn(Boolean defaultYn) {
        this.defaultYn = defaultYn;
    }
    public Long getPosition() {
        return position;
    }
    public void setPosition(Long position) {
        this.position = position;
    }
    public Set<WorkflowTransition> getSourceWorkflowTransitions() {
        return sourceWorkflowTransitions;
    }
    public void setSourceWorkflowTransitions(Set<WorkflowTransition> sourceWorkflowTransitions) {
        this.sourceWorkflowTransitions = sourceWorkflowTransitions;
    }
    public Set<WorkflowTransition> getTargetWorkflowTransitions() {
        return targetWorkflowTransitions;
    }
    public void setTargetWorkflowTransitions(Set<WorkflowTransition> targetWorkflowTransitions) {
        this.targetWorkflowTransitions = targetWorkflowTransitions;
    }
}
src/main/java/kr/wisestone/owl/domain/IssueTableConfig.java
New file
@@ -0,0 +1,59 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
/**
 * Created by wisestone on 2019-02-07.
 */
@Entity
public class IssueTableConfig extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String issueTableConfigs;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "workspace_id")
    private Workspace workspace;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
    public IssueTableConfig(){}
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getIssueTableConfigs() {
        return issueTableConfigs;
    }
    public void setIssueTableConfigs(String issueTableConfigs) {
        this.issueTableConfigs = issueTableConfigs;
    }
    public Workspace getWorkspace() {
        return workspace;
    }
    public void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
}
src/main/java/kr/wisestone/owl/domain/IssueType.java
New file
@@ -0,0 +1,98 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
/**
 * Created by wisestone on 2018-03-07.
 */
@Entity
public class IssueType extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String description;
    private String color;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "workspace_id")
    private Workspace workspace;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "workflow_id")
    private Workflow workflow;
    @OneToMany(mappedBy = "issueType", cascade = { CascadeType.ALL }, orphanRemoval = true)
    private Set<IssueTypeCustomField> issueTypeCustomFields = new HashSet<>();
    public IssueType(){}
    public IssueType(Workspace workspace, Workflow workflow, String name, String description, String color){
        this.workspace = workspace;
        this.workflow = workflow;
        this.name = name;
        this.description = description;
        this.color = color;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public Workspace getWorkspace() {
        return workspace;
    }
    public void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }
    public Workflow getWorkflow() {
        return workflow;
    }
    public void setWorkflow(Workflow workflow) {
        this.workflow = workflow;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
    public Set<IssueTypeCustomField> getIssueTypeCustomFields() {
        return issueTypeCustomFields;
    }
    public void setIssueTypeCustomFields(Set<IssueTypeCustomField> issueTypeCustomFields) {
        this.issueTypeCustomFields = issueTypeCustomFields;
    }
}
src/main/java/kr/wisestone/owl/domain/IssueTypeCustomField.java
New file
@@ -0,0 +1,103 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
/**
 * Created by wisestone on 2018-03-07.
 */
@Entity
public class IssueTypeCustomField extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    public static final String FIELD_OPTION_Y = "01";  //  필수로 사용
    public static final String FIELD_OPTION_N = "02";    //  옵션으로 사용
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String fieldOption;
    private Integer position = 0;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "project_id")
    private Project project;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "issue_type_id")
    private IssueType issueType;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "custom_field_id")
    private CustomField customField;
    @OneToMany(mappedBy = "issueTypeCustomField", cascade = { CascadeType.ALL }, orphanRemoval = true)
    private Set<IssueCustomFieldValue> issueCustomFieldValues = new HashSet<>();
    public IssueTypeCustomField(){}
    public IssueTypeCustomField(Project project, IssueType issueType, CustomField customField, String fieldOption){
        this.project = project;
        this.issueType = issueType;
        this.customField = customField;
        this.fieldOption = fieldOption;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getFieldOption() {
        return fieldOption;
    }
    public void setFieldOption(String fieldOption) {
        this.fieldOption = fieldOption;
    }
    public Project getProject() {
        return project;
    }
    public void setProject(Project project) {
        this.project = project;
    }
    public IssueType getIssueType() {
        return issueType;
    }
    public void setIssueType(IssueType issueType) {
        this.issueType = issueType;
    }
    public CustomField getCustomField() {
        return customField;
    }
    public void setCustomField(CustomField customField) {
        this.customField = customField;
    }
    public Set<IssueCustomFieldValue> getIssueCustomFieldValues() {
        return issueCustomFieldValues;
    }
    public void setIssueCustomFieldValues(Set<IssueCustomFieldValue> issueCustomFieldValues) {
        this.issueCustomFieldValues = issueCustomFieldValues;
    }
    public Integer getPosition() {
        return position;
    }
    public void setPosition(Integer position) {
        this.position = position;
    }
}
src/main/java/kr/wisestone/owl/domain/IssueUser.java
New file
@@ -0,0 +1,65 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
@Entity
public class IssueUser extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "issue_id")
    private Issue issue;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "workspace_id")
    private Workspace workspace;
    public IssueUser() {
    }
    public IssueUser(Issue issue, User user) {
        this.issue = issue;
        this.user = user;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    public Issue getIssue() {
        return issue;
    }
    public void setIssue(Issue issue) {
        this.issue = issue;
    }
    public Workspace getWorkspace() {
        return workspace;
    }
    public void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }
}
src/main/java/kr/wisestone/owl/domain/IssueVersion.java
New file
@@ -0,0 +1,88 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
/**
 * Created by wisestone on 2019-02-28.
 */
@Entity
public class IssueVersion extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Integer version;
    private String content;
    @ManyToOne(fetch= FetchType.LAZY)
    @JoinColumn(name="issue_id")
    private Issue issue;
    @ManyToOne(fetch= FetchType.LAZY)
    @JoinColumn(name="project_id")
    private Project project;
    @ManyToOne(fetch= FetchType.LAZY)
    @JoinColumn(name="workspace_id")
    private Workspace workspace;
    public IssueVersion() {
    }
    public IssueVersion(Issue issue, Project project, Workspace workspace, String content) {
        this.issue = issue;
        this.project = project;
        this.workspace = workspace;
        this.content = content;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public Integer getVersion() {
        return version;
    }
    public void setVersion(Integer version) {
        this.version = version;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    public Issue getIssue() {
        return issue;
    }
    public void setIssue(Issue issue) {
        this.issue = issue;
    }
    public Project getProject() {
        return project;
    }
    public void setProject(Project project) {
        this.project = project;
    }
    public Workspace getWorkspace() {
        return workspace;
    }
    public void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }
}
src/main/java/kr/wisestone/owl/domain/ManageUser.java
New file
@@ -0,0 +1,80 @@
package kr.wisestone.owl.domain;
import org.hibernate.annotations.Type;
import javax.persistence.*;
import java.io.Serializable;
/**
 * Created by wisestone on 2018-02-13.
 */
@Entity
public class ManageUser extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @ManyToOne(fetch= FetchType.EAGER)  //  스케쥴러에서 메일 전송을 위해 즉시 로딩 적용
    @JoinColumn(name="user_id")
    private User user;
    @ManyToOne(fetch=FetchType.EAGER)   //  스케쥴러에서 메일 전송을 위해 즉시 로딩 적용
    @JoinColumn(name="workspace_id")
    private Workspace workspace;
    @Type(type = "yes_no")
    private Boolean useYn = Boolean.FALSE;
    private Long disablePosition;
    public ManageUser() {}
    public ManageUser(User user, Workspace workspace, Boolean managerYn, Boolean useYn, Long disablePosition) {
        this.user = user;
        this.workspace = workspace;
        this.useYn = useYn;
        this.disablePosition = disablePosition;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    public Workspace getWorkspace() {
        return workspace;
    }
    public void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }
    public Boolean getUseYn() {
        return useYn;
    }
    public void setUseYn(Boolean useYn) {
        this.useYn = useYn;
    }
    public Long getDisablePosition() {
        return disablePosition;
    }
    public void setDisablePosition(Long disablePosition) {
        this.disablePosition = disablePosition;
    }
}
src/main/java/kr/wisestone/owl/domain/Notice.java
New file
@@ -0,0 +1,47 @@
package kr.wisestone.owl.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.io.Serializable;
/**
 * Create By J E O N G - S U N / 2019-05-22
 */
@Entity
public class Notice extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String description;
    public Notice(){}
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
}
src/main/java/kr/wisestone/owl/domain/Payment.java
New file
@@ -0,0 +1,83 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
 * Created by wisestone on 2018-02-13.
 */
@Entity
public class Payment extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    public static final String MONTH = "MONTH";
    public static final String YEAR = "YEAR";
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String type;
    private Integer price;
    private Integer buyUser;
    @OneToOne
    @JoinColumn(name = "workspace_id")
    private Workspace workspace;
    @OneToOne(mappedBy = "payment", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private ReservationDisableUser reservationDisableUser;
    public Payment() {
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getType() {
        return type;
    }
    public void setType(String type) {
        this.type = type;
    }
    public Integer getPrice() {
        return price;
    }
    public void setPrice(Integer price) {
        this.price = price;
    }
    public Integer getBuyUser() {
        return buyUser;
    }
    public void setBuyUser(Integer buyUser) {
        this.buyUser = buyUser;
    }
    public Workspace getWorkspace() {
        return workspace;
    }
    public void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }
    public ReservationDisableUser getReservationDisableUser() {
        return reservationDisableUser;
    }
    public void setReservationDisableUser(ReservationDisableUser reservationDisableUser) {
        this.reservationDisableUser = reservationDisableUser;
    }
}
src/main/java/kr/wisestone/owl/domain/PaymentHistory.java
New file
@@ -0,0 +1,139 @@
package kr.wisestone.owl.domain;
import kr.wisestone.owl.service.impl.PaymentServiceImpl;
import kr.wisestone.owl.util.ConvertUtil;
import org.apache.commons.text.StringEscapeUtils;
import org.springframework.util.StringUtils;
import javax.persistence.*;
import java.io.Serializable;
/**
 * Created by wisestone on 2018-02-13.
 */
@Entity
public class PaymentHistory extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    public static final String PAYMENT_RESULT_SUCCESS = "success";
    public static final String PAYMENT_RESULT_FAILED = "failed";
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String type;
    private Integer price;
    private Integer buyUser;
    private String customerUid;
    private String merchantUid;
    private String paymentResult;
    private String paymentResponse;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "workspace_id")
    private Workspace workspace;
    public PaymentHistory() {
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getType() {
        return type;
    }
    public void setType(String type) {
        this.type = type;
    }
    public Integer getPrice() {
        return price;
    }
    public void setPrice(Integer price) {
        this.price = price;
    }
    public Integer getBuyUser() {
        return buyUser;
    }
    public void setBuyUser(Integer buyUser) {
        this.buyUser = buyUser;
    }
    public String getCustomerUid() {
        return customerUid;
    }
    public void setCustomerUid(String customerUid) {
        this.customerUid = customerUid;
    }
    public String getMerchantUid() {
        return merchantUid;
    }
    public void setMerchantUid(String merchantUid) {
        this.merchantUid = merchantUid;
    }
    public String getPaymentResult() {
        return paymentResult;
    }
    public void setPaymentResult(String paymentResult) {
        this.paymentResult = paymentResult;
    }
    public String getPaymentResponse() {
        return paymentResponse;
    }
    public void setPaymentResponse(String paymentResponse) {
        this.paymentResponse = paymentResponse;
    }
    public Workspace getWorkspace() {
        return workspace;
    }
    public void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }
    public PaymentHistory bindPaymentResult(PaymentServiceImpl.RestClientResultObject resultObject) {
        if (resultObject.isValidResult()) {
            this.setPaymentResult(PAYMENT_RESULT_SUCCESS);
            this.setPaymentResponse(ConvertUtil.convertObjectToJson(resultObject.getResponse()));
        }
        else {
            this.setPaymentResult(PAYMENT_RESULT_FAILED);
            if (resultObject.isHttpRequestFailed()) {
                this.setPaymentResponse(resultObject.getHttpStatus().getReasonPhrase());
            }
            else if (resultObject.isIamportResultFailed()) {
                this.setPaymentResponse(resultObject.getMessage());
            }
            else if (resultObject.isIamportPaymentFailed()) {
                if (StringUtils.hasText(resultObject.getMessage())) {
                    this.setPaymentResponse(resultObject.getMessage());
                }
                else {
                    this.setPaymentResponse(StringEscapeUtils.unescapeJava(resultObject.getIamportFailReason()));
                }
            }
        }
        return this;
    }
}
src/main/java/kr/wisestone/owl/domain/Permission.java
New file
@@ -0,0 +1,80 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
@Entity
public class Permission extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    public static final String ROLE_TYPE_USER = "01";   //  일반 사용자 권한
    public static final String ROLE_TYPE_WORKSPACE_MANAGER = "02"; //  워크스페이스 관리자 권한
    public static final String ROLE_TYPE_PROJECT_JOIN = "03";   //  프로젝트 참여자 권한
    public static final String ROLE_TYPE_PROJECT_MANAGER = "04";    //  프로젝트 관리자 권한
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String action;
    private String roleType;
    @OneToMany(mappedBy="permission", cascade={CascadeType.ALL})
    private Set<SystemRolePermission> systemRolePermissions = new HashSet<>();
    @OneToMany(mappedBy="permission", cascade={CascadeType.ALL})
    private Set<ProjectRolePermission> projectRolePermissions = new HashSet<>();
    public Permission() {
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAction() {
        return action;
    }
    public void setAction(String action) {
        this.action = action;
    }
    public String getRoleType() {
        return roleType;
    }
    public void setRoleType(String roleType) {
        this.roleType = roleType;
    }
    public Set<SystemRolePermission> getSystemRolePermissions() {
        return systemRolePermissions;
    }
    public void setSystemRolePermissions(Set<SystemRolePermission> systemRolePermissions) {
        this.systemRolePermissions = systemRolePermissions;
    }
    public Set<ProjectRolePermission> getProjectRolePermissions() {
        return projectRolePermissions;
    }
    public void setProjectRolePermissions(Set<ProjectRolePermission> projectRolePermissions) {
        this.projectRolePermissions = projectRolePermissions;
    }
}
src/main/java/kr/wisestone/owl/domain/Priority.java
New file
@@ -0,0 +1,86 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
/**
 * Created by wisestone on 2018-01-03.
 */
@Entity
public class Priority extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private Integer position;
    private String color;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "workspace_id")
    private Workspace workspace;
    @OneToMany(mappedBy = "priority", cascade = { CascadeType.ALL }, orphanRemoval = true)
    private Set<Issue> issues = new HashSet<>();
    public Priority() {}
    public Priority(String name, Integer position, String color, Workspace workspace) {
        this.name = name;
        this.position = position;
        this.color = color;
        this.workspace = workspace;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getPosition() {
        return position;
    }
    public void setPosition(Integer position) {
        this.position = position;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
    public Workspace getWorkspace() {
        return workspace;
    }
    public void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }
    public Set<Issue> getIssues() {
        return issues;
    }
    public void setIssues(Set<Issue> issues) {
        this.issues = issues;
    }
}
src/main/java/kr/wisestone/owl/domain/Project.java
New file
@@ -0,0 +1,200 @@
package kr.wisestone.owl.domain;
import kr.wisestone.owl.domain.enumType.ProjectType;
import org.hibernate.annotations.Type;
import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
 * Created by jeong on 2017-12-30.
 */
@Entity
public class Project extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    public static final String PROJECT_READY = "01";
    public static final String PROJECT_OPEN = "02";
    public static final String PROJECT_CLOSE = "03";
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String projectKey;
    private String description;
    private String status;
    private String startDate;
    private String endDate;
    @Type(type="yes_no")
    private Boolean defaultYn = Boolean.FALSE;
    @Enumerated(EnumType.STRING)
    private ProjectType projectType;
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "workspace_id")
    private Workspace workspace;
    @OneToMany(mappedBy = "project", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<Issue> issues = new HashSet<>();
    @OneToMany(mappedBy = "project", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<IssueNumberGenerator> issueNumberGenerators = new HashSet<>();
    @OneToMany(mappedBy = "project", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<IssueTypeCustomField> issueTypeCustomFields = new HashSet<>();
    @OneToMany(mappedBy = "project", cascade={CascadeType.ALL}, orphanRemoval=true)
    private Set<ProjectRole> projectRoles = new HashSet<>();
    @OneToMany(mappedBy = "project", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<UserInviteProject> userInviteProjects = new HashSet<>();
    @OneToMany(mappedBy = "project", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<ProjectClosure> projectClosures = new HashSet<>();
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getProjectKey() {
        return projectKey;
    }
    public void setProjectKey(String projectKey) {
        this.projectKey = projectKey;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public String getStatus() {
        return status;
    }
    public void setStatus(String status) {
        this.status = status;
    }
    public String getStartDate() {
        return startDate;
    }
    public void setStartDate(String startDate) {
        this.startDate = startDate;
    }
    public String getEndDate() {
        return endDate;
    }
    public void setEndDate(String endDate) {
        this.endDate = endDate;
    }
    public Boolean getDefaultYn() {
        return defaultYn;
    }
    public void setDefaultYn(Boolean defaultYn) {
        this.defaultYn = defaultYn;
    }
    public ProjectType getProjectType() {
        return projectType;
    }
    public void setProjectType(ProjectType projectType) {
        this.projectType = projectType;
    }
    public Workspace getWorkspace() {
        return workspace;
    }
    public void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }
    public Set<Issue> getIssues() {
        return issues;
    }
    public void setIssues(Set<Issue> issues) {
        this.issues = issues;
    }
    public Set<IssueNumberGenerator> getIssueNumberGenerators() {
        return issueNumberGenerators;
    }
    public void setIssueNumberGenerators(Set<IssueNumberGenerator> issueNumberGenerators) {
        this.issueNumberGenerators = issueNumberGenerators;
    }
    public Set<IssueTypeCustomField> getIssueTypeCustomFields() {
        return issueTypeCustomFields;
    }
    public void setIssueTypeCustomFields(Set<IssueTypeCustomField> issueTypeCustomFields) {
        this.issueTypeCustomFields = issueTypeCustomFields;
    }
    public Set<ProjectRole> getProjectRoles() {
        return projectRoles;
    }
    public void setProjectRoles(Set<ProjectRole> projectRoles) {
        this.projectRoles = projectRoles;
    }
    public Set<UserInviteProject> getUserInviteProjects() {
        return userInviteProjects;
    }
    public void setUserInviteProjects(Set<UserInviteProject> userInviteProjects) {
        this.userInviteProjects = userInviteProjects;
    }
    public Set<ProjectClosure> getProjectClosures() {
        return projectClosures;
    }
    public  ProjectClosure getParentProjectClosure() {
        if (this.projectClosures != null && this.projectClosures.size() > 0) {
            Iterator<ProjectClosure> iter = this.projectClosures.iterator();
            return  iter.next();
        }
        return  null;
    }
    public void setProjectClosures(Set<ProjectClosure> childProjects) {
        this.projectClosures = childProjects;
    }
    public  void addParent(ProjectClosure project){
        projectClosures.add(project);
    }
}
src/main/java/kr/wisestone/owl/domain/ProjectClosure.java
New file
@@ -0,0 +1,49 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
@Entity
public class ProjectClosure extends BaseEntity implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @ManyToOne(fetch= FetchType.LAZY)
    @JoinColumn(name="project_id")
    private Project project;
    @ManyToOne(fetch= FetchType.LAZY)
    @JoinColumn(name="parent_project_id")
    private Project parentProject;
    public ProjectClosure(){}
    public ProjectClosure(Project project, Project parentProject){
        this.project = project;
        this.parentProject = parentProject;
    }
    public  ProjectClosure(Project project) {
        this.project = project;
        this.parentProject = null;
    }
    public Long getId() {return this.id; }
    public void setId(Long id) { this.id = id; }
    public Project getProject() {
        return project;
    }
    public void setProject(Project project) {
        this.project = project;
    }
    public Project getParentProject() {
        return parentProject;
    }
    public  void setParentProject(Project project) {this.parentProject = project; }
}
src/main/java/kr/wisestone/owl/domain/ProjectRole.java
New file
@@ -0,0 +1,111 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
 * Created by jeong on 2017-12-30.
 */
@Entity
public class ProjectRole extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    public static final String TYPE_DEFAULT  = "01";    //  일반 사용자
    public static final String TYPE_MANAGER  = "02";    //  관리자
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String roleType;
    @ManyToOne(fetch= FetchType.LAZY)
    @JoinColumn(name="project_id")
    private Project project;
    @OneToMany(mappedBy="projectRole", cascade={CascadeType.ALL}, orphanRemoval=true)
    private Set<ProjectRoleUser> projectRoleUsers = new HashSet<>();
    @OneToMany(mappedBy="projectRole", cascade={CascadeType.ALL}, orphanRemoval=true)
    private Set<ProjectRolePermission> projectRolePermissions = new HashSet<>();
    public ProjectRole(){}
    public ProjectRole(Project project, String name, String roleType){
        this.project = project;
        this.name = name;
        this.roleType = roleType;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getRoleType() {
        return roleType;
    }
    public void setRoleType(String roleType) {
        this.roleType = roleType;
    }
    public Project getProject() {
        return project;
    }
    public void setProject(Project project) {
        this.project = project;
    }
    public Set<ProjectRoleUser> getProjectRoleUsers() {
        return projectRoleUsers;
    }
    public void setProjectRoleUsers(Set<ProjectRoleUser> projectRoleUsers) {
        this.projectRoleUsers = projectRoleUsers;
    }
    public Set<ProjectRolePermission> getProjectRolePermissions() {
        return projectRolePermissions;
    }
    public void setProjectRolePermissions(Set<ProjectRolePermission> projectRolePermissions) {
        this.projectRolePermissions = projectRolePermissions;
    }
    public void addUser(User user) {
        if (this.projectRoleUsers == null) {
            this.projectRoleUsers = new HashSet<>();
        }
        ProjectRoleUser projectRoleUser = new ProjectRoleUser(this, user);
        this.projectRoleUsers.add(projectRoleUser);
    }
    public void removeProjectRole(User user) {
        Iterator<ProjectRoleUser> iterator = this.projectRoleUsers.iterator();
        while (iterator.hasNext()) {
            ProjectRoleUser projectRoleUser = iterator.next();
            if (user.getId().equals(projectRoleUser.getUser().getId())) {
                this.projectRoleUsers.remove(projectRoleUser);
                break;
            }
        }
    }
}
src/main/java/kr/wisestone/owl/domain/ProjectRolePermission.java
New file
@@ -0,0 +1,55 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
/**
 * Created by jeong on 2017-12-30.
 */
@Entity
public class ProjectRolePermission extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @ManyToOne(fetch= FetchType.LAZY)
    @JoinColumn(name="project_role_id")
    private ProjectRole projectRole;
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="permission_id")
    private Permission permission;
    public ProjectRolePermission(){}
    public ProjectRolePermission(ProjectRole projectRole, Permission permission) {
        this.projectRole = projectRole;
        this.permission = permission;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public ProjectRole getProjectRole() {
        return projectRole;
    }
    public void setProjectRole(ProjectRole projectRole) {
        this.projectRole = projectRole;
    }
    public Permission getPermission() {
        return permission;
    }
    public void setPermission(Permission permission) {
        this.permission = permission;
    }
}
src/main/java/kr/wisestone/owl/domain/ProjectRoleUser.java
New file
@@ -0,0 +1,56 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
/**
 * Created by jeong on 2017-12-30.
 */
@Entity
public class ProjectRoleUser extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="project_role_id")
    private ProjectRole projectRole;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="user_id")
    private User user;
    public ProjectRoleUser() {
    }
    public ProjectRoleUser(ProjectRole projectRole, User user) {
        this.projectRole = projectRole;
        this.user = user;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public ProjectRole getProjectRole() {
        return projectRole;
    }
    public void setProjectRole(ProjectRole projectRole) {
        this.projectRole = projectRole;
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
}
src/main/java/kr/wisestone/owl/domain/Qna.java
New file
@@ -0,0 +1,47 @@
package kr.wisestone.owl.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.io.Serializable;
/**
 * Create By J E O N G - S U N / 2019-05-22
 */
@Entity
public class Qna extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String description;
    public Qna(){}
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
}
src/main/java/kr/wisestone/owl/domain/ReservationDisableUser.java
New file
@@ -0,0 +1,47 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
/**
 * Created by wisestone on 2018-02-19.
 */
@Entity
public class ReservationDisableUser extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String userIds;
    @OneToOne
    @JoinColumn(name = "payment_id")
    private Payment payment;
    public ReservationDisableUser() {}
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getUserIds() {
        return userIds;
    }
    public void setUserIds(String userIds) {
        this.userIds = userIds;
    }
    public Payment getPayment() {
        return payment;
    }
    public void setPayment(Payment payment) {
        this.payment = payment;
    }
}
src/main/java/kr/wisestone/owl/domain/Severity.java
New file
@@ -0,0 +1,85 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
/**
 * Created by wisestone on 2018-03-15.
 */
@Entity
public class Severity extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private Integer position;
    private String color;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "workspace_id")
    private Workspace workspace;
    @OneToMany(mappedBy = "severity", cascade = { CascadeType.ALL }, orphanRemoval = true)
    private Set<Issue> issues = new HashSet<>();
    public Severity(){}
    public Severity(String name, Integer position, String color, Workspace workspace){
        this.name = name;
        this.position = position;
        this.color = color;
        this.workspace = workspace;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getPosition() {
        return position;
    }
    public void setPosition(Integer position) {
        this.position = position;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
    public Set<Issue> getIssues() {
        return issues;
    }
    public void setIssues(Set<Issue> issues) {
        this.issues = issues;
    }
    public Workspace getWorkspace() {
        return workspace;
    }
    public void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }
}
src/main/java/kr/wisestone/owl/domain/SystemEmail.java
New file
@@ -0,0 +1,67 @@
package kr.wisestone.owl.domain;
import kr.wisestone.owl.domain.enumType.EmailType;
import org.hibernate.annotations.Type;
import javax.persistence.*;
import java.io.Serializable;
/**
 * Created by wisestone on 2018-03-14.
 */
@Entity
public class SystemEmail extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String sendAddress;
    @Enumerated(EnumType.STRING)
    private EmailType emailType;
    private String parameter;
    @Type(type = "yes_no")
    private Boolean sendYn = Boolean.FALSE; //  N : 발송 대기, Y : 발송 완료
    public SystemEmail() {}
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getSendAddress() {
        return sendAddress;
    }
    public void setSendAddress(String sendAddress) {
        this.sendAddress = sendAddress;
    }
    public EmailType getEmailType() {
        return emailType;
    }
    public void setEmailType(EmailType emailType) {
        this.emailType = emailType;
    }
    public String getParameter() {
        return parameter;
    }
    public void setParameter(String parameter) {
        this.parameter = parameter;
    }
    public Boolean getSendYn() {
        return sendYn;
    }
    public void setSendYn(Boolean sendYn) {
        this.sendYn = sendYn;
    }
}
src/main/java/kr/wisestone/owl/domain/SystemRole.java
New file
@@ -0,0 +1,222 @@
package kr.wisestone.owl.domain;
import com.google.common.collect.Lists;
import javax.persistence.*;
import java.io.Serializable;
import java.util.*;
@Entity
public class SystemRole extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String roleType;
    @OneToMany(mappedBy="systemRole", cascade={CascadeType.ALL}, orphanRemoval=true)
    private Set<SystemRoleUser> systemRoleUsers = new HashSet<>();
    @OneToMany(mappedBy="systemRole", cascade={CascadeType.ALL}, orphanRemoval=true)
    private Set<SystemRolePermission> systemRolePermissions = new HashSet<>();
    public SystemRole() {
    }
    public Long getId() {
        return this.id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return this.name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Set<SystemRoleUser> getSystemRoleUsers() {
        return this.systemRoleUsers;
    }
    public List<User> getUsers() {
        List<User> users = Lists.newArrayList();
        Iterator<SystemRoleUser> iterator = this.systemRoleUsers.iterator();
        while (iterator.hasNext()) {
            users.add(iterator.next().getUser());
        }
        return users;
    }
    public void setSystemRoleUsers(Set<SystemRoleUser> systemRoleUsers) {
        this.systemRoleUsers = systemRoleUsers;
    }
    public Set<SystemRolePermission> getSystemRolePermissions() {
        return this.systemRolePermissions;
    }
    public List<Permission> getPermissions() {
        List<Permission> permissions = new ArrayList<Permission>();
        Iterator<SystemRolePermission> iterator = this.systemRolePermissions.iterator();
        while (iterator.hasNext()) {
            permissions.add(iterator.next().getPermission());
        }
        return permissions;
    }
    public void setSystemRolePermissions(Set<SystemRolePermission> systemRolePermissions) {
        this.systemRolePermissions = systemRolePermissions;
    }
    public void addSystemRolePermission(SystemRolePermission systemRolePermission) {
        if (this.systemRolePermissions == null) {
            this.systemRolePermissions = new HashSet<>();
        }
        this.systemRolePermissions.add(systemRolePermission);
    }
    public void removeSystemRolePermission(SystemRolePermission systemRolePermission) {
        this.systemRolePermissions.remove(systemRolePermission);
    }
    public void addSystemRoleUser(SystemRoleUser systemRoleUser) {
        if (this.systemRoleUsers == null) {
            this.systemRoleUsers = new HashSet<>();
        }
        this.systemRoleUsers.add(systemRoleUser);
    }
    public String getRoleType() {
        return roleType;
    }
    public void setRoleType(String roleType) {
        this.roleType = roleType;
    }
    public void removeSystemRoleUser(SystemRoleUser systemRoleUser) {
        this.systemRoleUsers.remove(systemRoleUser);
    }
    public boolean contains(User user) {
        for (SystemRoleUser systemRoleUser : this.systemRoleUsers) {
            if (user.getId().equals(systemRoleUser.getUser().getId())) {
                return true;
            }
        }
        return false;
    }
    public boolean contains(Long userId) {
        for (SystemRoleUser systemRoleUser : this.systemRoleUsers) {
            if (userId.equals(systemRoleUser.getUser().getId())) {
                return true;
            }
        }
        return false;
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + (this.id == null ? 0 : this.id.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (this.getClass() != obj.getClass()) {
            return false;
        }
        SystemRole other = (SystemRole) obj;
        if (this.id == null) {
            if (other.id != null) {
                return false;
            }
        } else if (!this.id.equals(other.id)) {
            return false;
        }
        return true;
    }
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("SystemRole [");
        if (this.id != null) {
            builder.append("id=");
            builder.append(this.id);
            builder.append(", ");
        }
        if (this.name != null) {
            builder.append("name=");
            builder.append(this.name);
            builder.append(", ");
        }
        if (this.systemRoleUsers != null) {
            builder.append("systemRoleUsers=");
            builder.append(this.systemRoleUsers);
            builder.append(", ");
        }
        if (this.systemRolePermissions != null) {
            builder.append("systemRolePermissions=");
            builder.append(this.systemRolePermissions);
        }
        builder.append("]");
        return builder.toString();
    }
    public void addUser(User user) {
        if (this.systemRoleUsers == null) {
            this.systemRoleUsers = new HashSet<>();
        }
        SystemRoleUser systemRoleUser = new SystemRoleUser();
        systemRoleUser.setSystemRole(this);
        systemRoleUser.setUser(user);
        this.systemRoleUsers.add(systemRoleUser);
    }
    public void removeUser(User user) {
        for (SystemRoleUser systemRoleUser : this.systemRoleUsers) {
            if (user.getId().equals(systemRoleUser.getUser().getId())) {
                this.systemRoleUsers.remove(systemRoleUser);
                break;
            }
        }
    }
    public void addPermission(Permission permission) {
        SystemRolePermission systemRolePermission = new SystemRolePermission();
        systemRolePermission.setSystemRole(this);
        systemRolePermission.setPermission(permission);
        systemRolePermissions.add(systemRolePermission);
    }
}
src/main/java/kr/wisestone/owl/domain/SystemRolePermission.java
New file
@@ -0,0 +1,92 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
@Entity
public class SystemRolePermission extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="system_role_id")
    private SystemRole systemRole;
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="permission_id")
    private Permission permission;
    public SystemRolePermission() {
    }
    public SystemRolePermission(SystemRole systemRole, Permission permission) {
        this.systemRole = systemRole;
        this.permission = permission;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    /**
     * @return systemRole
     */
    public SystemRole getSystemRole() {
        return this.systemRole;
    }
    /**
     * @param systemRole
     */
    public void setSystemRole(SystemRole systemRole) {
        this.systemRole = systemRole;
    }
    /**
     * @return permission
     */
    public Permission getPermission() {
        return this.permission;
    }
    /**
     * @param permission
     */
    public void setPermission(Permission permission) {
        this.permission = permission;
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        SystemRolePermission other = (SystemRolePermission) obj;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        return true;
    }
}
src/main/java/kr/wisestone/owl/domain/SystemRoleUser.java
New file
@@ -0,0 +1,54 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
@Entity
public class SystemRoleUser extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "system_role_id")
    private SystemRole systemRole;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
    public SystemRoleUser() {
    }
    public SystemRoleUser(SystemRole systemRole, User user) {
        this.systemRole = systemRole;
        this.user = user;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public SystemRole getSystemRole() {
        return systemRole;
    }
    public void setSystemRole(SystemRole systemRole) {
        this.systemRole = systemRole;
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
}
src/main/java/kr/wisestone/owl/domain/User.java
New file
@@ -0,0 +1,376 @@
package kr.wisestone.owl.domain;
import kr.wisestone.owl.domain.enumType.SocialType;
import kr.wisestone.owl.util.CommonUtil;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import java.io.Serializable;
import java.security.Principal;
import java.util.*;
@Entity
public class User extends BaseEntity implements UserDetails, Principal, Serializable {
    private static final long serialVersionUID = 1L;
    public static final String USER_STATUS_ACTIVE = "01";  //  사용자 활성
    public static final String USER_STATUS_DEL = "02";    //  사용자 탈퇴
    public static final String DEFAULT_PROFILE = "assets/images/default_profile.png";    //  기본 프로필
    public static final String DEFAULT_RESERVATION_NOTIFY_TIME = "09:00";    //  기본 이메일 알림 예정 시간
    public static final String DEFAULT_LANGUAGE = "ko"; //  기본 언어
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String account;
    private String password;
    private String status;
    private String phone;
    private String profile;
    private String awsKey;
    @Enumerated(EnumType.STRING)
    private SocialType socialType;
    private Long lastWorkspaceId;
    private Long lastProjectId;
    private Date lastLoginDate;
    private String reservationNotifyTime;   //  이메일 알림 시간 예정
    private String language;
    private Integer permission;
    private String licensekey;
    @OneToMany(mappedBy = "user", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<SystemRoleUser> systemRoleUsers = new HashSet<>();
    @OneToMany(mappedBy = "user", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<ProjectRoleUser> projectRoleUsers = new HashSet<>();
    @OneToMany(mappedBy = "user", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<IssueUser> issueUsers = new HashSet<>();
    @OneToMany(mappedBy = "user", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<UserLikeIssue> userLikeIssues = new HashSet<>();
    @OneToMany(mappedBy = "user", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<UserWorkspace> userWorkspaces = new HashSet<>();
    @OneToMany(mappedBy = "user", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<IssueSearch> issueSearches = new HashSet<>();
    @OneToMany(mappedBy = "user", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<IssueTableConfig> issueTableConfigs = new HashSet<>();
    public User() {
    }
    public User(Long id, String name, String account) {
        this.id = id;
        this.name = name;
        this.account = account;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getAccount() {
        return account;
    }
    public void setAccount(String account) {
        this.account = account;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getProfile() {
        return profile;
    }
    public void setProfile(String profile) {
        this.profile = profile;
    }
    public String getStatus() {
        return status;
    }
    public void setStatus(String status) {
        this.status = status;
    }
    public SocialType getSocialType() {
        return socialType;
    }
    public void setSocialType(SocialType socialType) {
        this.socialType = socialType;
    }
    public String getPhone() {
        return phone;
    }
    public void setPhone(String phone) {
        this.phone = phone;
    }
    public Long getLastWorkspaceId() {
        return lastWorkspaceId;
    }
    public Long getLastProjectId() {return lastProjectId; }
    public void setLastProjectId(Long lastProjectId) {
        this.lastProjectId = lastProjectId;
    }
    public String getAwsKey() {
        return awsKey;
    }
    public void setAwsKey(String awsKey) {
        this.awsKey = awsKey;
    }
    public void setLastWorkspaceId(Long lastWorkspaceId) {
        this.lastWorkspaceId = lastWorkspaceId;
    }
    public Set<SystemRoleUser> getSystemRoleUsers() {
        return systemRoleUsers;
    }
    public Set<IssueSearch> getIssueSearches() {
        return issueSearches;
    }
    public void setIssueSearches(Set<IssueSearch> issueSearches) {
        this.issueSearches = issueSearches;
    }
    public void setSystemRoleUsers(Set<SystemRoleUser> systemRoleUsers) {
        this.systemRoleUsers = systemRoleUsers;
    }
    public void addSystemRole(SystemRole systemRole) {
        if (this.systemRoleUsers == null) {
            this.systemRoleUsers = new HashSet<>();
        }
        SystemRoleUser systemRoleUser = new SystemRoleUser(systemRole, this);
        this.systemRoleUsers.add(systemRoleUser);
    }
    public Set<ProjectRoleUser> getProjectRoleUsers() {
        return projectRoleUsers;
    }
    public void setProjectRoleUsers(Set<ProjectRoleUser> projectRoleUsers) {
        this.projectRoleUsers = projectRoleUsers;
    }
    public void addProjectRole(ProjectRole projectRole) {
        if (this.projectRoleUsers == null) {
            this.projectRoleUsers = new HashSet<>();
        }
        ProjectRoleUser projectRoleUser = new ProjectRoleUser(projectRole, this);
        this.projectRoleUsers.add(projectRoleUser);
    }
    public void removeProjectRole(ProjectRole projectRole) {
        Iterator<ProjectRoleUser> iterator = this.projectRoleUsers.iterator();
        while (iterator.hasNext()) {
            ProjectRoleUser projectRoleUser = iterator.next();
            if (projectRole.getId().equals(projectRoleUser.getProjectRole().getId())) {
                this.projectRoleUsers.remove(projectRoleUser);
                break;
            }
        }
    }
    public Set<IssueUser> getIssueUsers() {
        return issueUsers;
    }
    public void setIssueUsers(Set<IssueUser> issueUsers) {
        this.issueUsers = issueUsers;
    }
    public void addIssue(Issue issue) {
        if (this.issueUsers == null) {
            this.issueUsers = new HashSet<>();
        }
        IssueUser issueUser = new IssueUser(issue, this);
        this.issueUsers.add(issueUser);
    }
    public void removeIssue(Issue issue) {
        Iterator<IssueUser> iterator = this.issueUsers.iterator();
        while (iterator.hasNext()) {
            IssueUser issueUser = iterator.next();
            if (issue.getId().equals(issueUser.getIssue().getId())) {
                this.issueUsers.remove(issueUser);
                break;
            }
        }
    }
    public Set<UserWorkspace> getUserWorkspaces() {
        return userWorkspaces;
    }
    public void setUserWorkspaces(Set<UserWorkspace> userWorkspaces) {
        this.userWorkspaces = userWorkspaces;
    }
    public Set<UserLikeIssue> getUserLikeIssues() {
        return userLikeIssues;
    }
    public void setUserLikeIssues(Set<UserLikeIssue> userLikeIssues) {
        this.userLikeIssues = userLikeIssues;
    }
    public String getReservationNotifyTime() {
        return reservationNotifyTime;
    }
    public void setReservationNotifyTime(String reservationNotifyTime) {
        this.reservationNotifyTime = reservationNotifyTime;
    }
    public Set<IssueTableConfig> getIssueTableConfigs() {
        return issueTableConfigs;
    }
    public void setIssueTableConfigs(Set<IssueTableConfig> issueTableConfigs) {
        this.issueTableConfigs = issueTableConfigs;
    }
    public String getLanguage() {
        return language;
    }
    public void setLanguage(String language) {
        this.language = language;
    }
    public Integer getPermission() {
        return permission;
    }
    public void setPermission(Integer permission) {
        this.permission = permission;
    }
    public String getLicensekey() {
        return licensekey;
    }
    public void setLicensekey(String licensekey) {
        this.licensekey = licensekey;
    }
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // TODO Auto-generated method stub
        return null;
    }
    @Override
    public String getUsername() {
        return this.account;
    }
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @Override
    public boolean isEnabled() {
        return true;
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((this.account == null) ? 0 : this.account.hashCode());
        result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        User other = (User) obj;
        if (this.account == null) {
            if (other.account != null) {
                return false;
            }
        }
        else if (!this.account.equals(other.account)) {
            return false;
        }
        if (this.id == null) {
            if (other.id != null) {
                return false;
            }
        }
        else if (!this.id.equals(other.id)) {
            return false;
        }
        return true;
    }
    public void setLastLoginDate(Date date) { this.lastLoginDate = date; }
    public Date getLastLoginDate() { return  this.lastLoginDate; }
    /*@Override
    public String toString() {
        return CommonUtil.decryptAES128(this.account);
    }*/
}
src/main/java/kr/wisestone/owl/domain/UserHistory.java
New file
@@ -0,0 +1,30 @@
package kr.wisestone.owl.domain;
import kr.wisestone.owl.domain.enumType.SocialType;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import java.io.Serializable;
import java.security.Principal;
@Entity
public class UserHistory extends BaseEntity implements Serializable {
    public static final String HISTORY_LOGIN = "LOGIN";
    public static final String HISTORY_LOGOUT = "LOGOUT";
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String historyType;
    public Long getId(){ return  this.id; }
    public void setId(Long id) { this.id = id; }
    public String getHistoryType() { return  this.historyType; }
    public void setHistoryType(String historyType) { this.historyType = historyType; }
    public UserHistory() { }
}
src/main/java/kr/wisestone/owl/domain/UserInvite.java
New file
@@ -0,0 +1,89 @@
package kr.wisestone.owl.domain;
import com.google.common.collect.Lists;
import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
 * Created by wisestone on 2018-03-14.
 */
@Entity
public class UserInvite extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    public static final String WORKSPACE_JOIN_READY = "01";     //  초대중
    public static final String WORKSPACE_JOIN_COMPLETE = "02";  //  가입 완료
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String email;
    private String status;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "workspace_id")
    private Workspace workspace;
    @OneToMany(mappedBy="userInvite", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<UserInviteProject> userInviteProjects = new HashSet<>();
    public UserInvite() {
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public String getStatus() {
        return status;
    }
    public void setStatus(String status) {
        this.status = status;
    }
    public Workspace getWorkspace() {
        return workspace;
    }
    public void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }
    public Set<UserInviteProject> getUserInviteProjects() {
        return userInviteProjects;
    }
    public void setUserInviteProjects(Set<UserInviteProject> userInviteProjects) {
        this.userInviteProjects = userInviteProjects;
    }
    public void removeUserInviteProject() {
        Iterator<UserInviteProject> iterator = this.userInviteProjects.iterator();
        while (iterator.hasNext()) {
            this.userInviteProjects.remove(iterator.next());
        }
    }
    public void addUserInviteProjects(UserInviteProject userInviteProject) {
        this.userInviteProjects.add(userInviteProject);
    }
}
src/main/java/kr/wisestone/owl/domain/UserInviteProject.java
New file
@@ -0,0 +1,55 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
/**
 * Created by wisestone on 2018-03-15.
 */
@Entity
public class UserInviteProject extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @ManyToOne(fetch= FetchType.LAZY)
    @JoinColumn(name="user_invite_id")
    private UserInvite userInvite;
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="project_id")
    private Project project;
    public UserInviteProject(){}
    public UserInviteProject(UserInvite userInvite, Project project){
        this.userInvite = userInvite;
        this.project = project;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public UserInvite getUserInvite() {
        return userInvite;
    }
    public void setUserInvite(UserInvite userInvite) {
        this.userInvite = userInvite;
    }
    public Project getProject() {
        return project;
    }
    public void setProject(Project project) {
        this.project = project;
    }
}
src/main/java/kr/wisestone/owl/domain/UserLikeIssue.java
New file
@@ -0,0 +1,62 @@
package kr.wisestone.owl.domain;
import javax.persistence.*;
import java.io.Serializable;
/**
 * Created by wisestone on 2018-03-07.
 */
@Entity
public class UserLikeIssue extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "issue_id")
    private Issue issue;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "workspace_id")
    private Workspace workspace;
    public UserLikeIssue(){}
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    public Issue getIssue() {
        return issue;
    }
    public void setIssue(Issue issue) {
        this.issue = issue;
    }
    public Workspace getWorkspace() {
        return workspace;
    }
    public void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }
}
src/main/java/kr/wisestone/owl/domain/UserWithDraw.java
New file
@@ -0,0 +1,42 @@
package kr.wisestone.owl.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.io.Serializable;
/**
 * Created by wisestone on 2018-11-19.
 */
@Entity
public class UserWithDraw extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String account;
    public UserWithDraw(){}
    public UserWithDraw(String account){
        this.account = account;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getAccount() {
        return account;
    }
    public void setAccount(String account) {
        this.account = account;
    }
}
src/main/java/kr/wisestone/owl/domain/UserWorkspace.java
New file
@@ -0,0 +1,91 @@
package kr.wisestone.owl.domain;
import org.hibernate.annotations.Type;
import javax.persistence.*;
import java.io.Serializable;
/**
 * Created by wisestone on 2018-02-13.
 */
@Entity
public class UserWorkspace extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @ManyToOne(fetch= FetchType.EAGER)  //  스케쥴러에서 메일 전송을 위해 즉시 로딩 적용
    @JoinColumn(name="user_id")
    private User user;
    @ManyToOne(fetch=FetchType.EAGER)   //  스케쥴러에서 메일 전송을 위해 즉시 로딩 적용
    @JoinColumn(name="workspace_id")
    private Workspace workspace;
    @Type(type = "yes_no")
    private Boolean managerYn = Boolean.FALSE;
    @Type(type = "yes_no")
    private Boolean useYn = Boolean.FALSE;
    private Long disablePosition;
    public UserWorkspace() {}
    public UserWorkspace(User user, Workspace workspace, Boolean managerYn, Boolean useYn, Long disablePosition) {
        this.user = user;
        this.workspace = workspace;
        this.managerYn = managerYn;
        this.useYn = useYn;
        this.disablePosition = disablePosition;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    public Workspace getWorkspace() {
        return workspace;
    }
    public void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }
    public Boolean getManagerYn() {
        return managerYn;
    }
    public void setManagerYn(Boolean managerYn) {
        this.managerYn = managerYn;
    }
    public Boolean getUseYn() {
        return useYn;
    }
    public void setUseYn(Boolean useYn) {
        this.useYn = useYn;
    }
    public Long getDisablePosition() {
        return disablePosition;
    }
    public void setDisablePosition(Long disablePosition) {
        this.disablePosition = disablePosition;
    }
}
src/main/java/kr/wisestone/owl/domain/Workflow.java
New file
@@ -0,0 +1,92 @@
package kr.wisestone.owl.domain;
import kr.wisestone.owl.domain.enumType.ProjectType;
import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
/**
 * Created by wisestone on 2018-03-07.
 */
@Entity
public class Workflow extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String description;
    @Enumerated(EnumType.STRING)
    private ProjectType projectType;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "workspace_id")
    private Workspace workspace;
    @OneToMany(mappedBy = "workflow", cascade = {CascadeType.ALL})
    private Set<IssueType> issueTypes = new HashSet<>();
    @OneToMany(mappedBy = "workflow", cascade = { CascadeType.ALL }, orphanRemoval = true)
    private Set<WorkflowTransition> workflowTransitions = new HashSet<>();
    public Workflow(){}
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public Workspace getWorkspace() {
        return workspace;
    }
    public void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }
    public Set<IssueType> getIssueTypes() {
        return issueTypes;
    }
    public void setIssueTypes(Set<IssueType> issueTypes) {
        this.issueTypes = issueTypes;
    }
    public Set<WorkflowTransition> getWorkflowTransitions() {
        return workflowTransitions;
    }
    public void setWorkflowTransitions(Set<WorkflowTransition> workflowTransitions) {
        this.workflowTransitions = workflowTransitions;
    }
    public ProjectType getProjectType() {
        return projectType;
    }
    public void setProjectType(ProjectType projectType) {
        this.projectType = projectType;
    }
}
src/main/java/kr/wisestone/owl/domain/WorkflowStatus.java
New file
@@ -0,0 +1,119 @@
package kr.wisestone.owl.domain;
import org.hibernate.annotations.Type;
import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
/**
 * Created by wisestone on 2018-01-03.
 */
@Entity
public class WorkflowStatus extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @Type(type="yes_no")
    private Boolean firstYn = Boolean.FALSE;
    @Type(type="yes_no")
    private Boolean lastYn = Boolean.FALSE;
    private String color;
    private Long progress;
    private Long position;
    @ManyToOne(fetch= FetchType.LAZY)
    @JoinColumn(name="project_id")
    private Project project;
    @OneToMany(mappedBy="workflowStatus", cascade={CascadeType.ALL}, orphanRemoval=true)
    private Set<Issue> tasks = new HashSet<Issue>();
    public WorkflowStatus() {}
    public WorkflowStatus(Project project, String name, Boolean firstYn, Boolean lastYn, String color, Long progress, Long position) {
        this.project = project;
        this.name = name;
        this.firstYn = firstYn;
        this.lastYn = lastYn;
        this.color = color;
        this.progress = progress;
        this.position = position;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Boolean getFirstYn() {
        return firstYn;
    }
    public void setFirstYn(Boolean firstYn) {
        this.firstYn = firstYn;
    }
    public Boolean getLastYn() {
        return lastYn;
    }
    public void setLastYn(Boolean lastYn) {
        this.lastYn = lastYn;
    }
    public Long getProgress() {
        return progress;
    }
    public void setProgress(Long progress) {
        this.progress = progress;
    }
    public Long getPosition() {
        return position;
    }
    public void setPosition(Long position) {
        this.position = position;
    }
    public Project getProject() {
        return project;
    }
    public void setProject(Project project) {
        this.project = project;
    }
    public Set<Issue> getTasks() {
        return tasks;
    }
    public void setTasks(Set<Issue> tasks) {
        this.tasks = tasks;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
}
src/main/java/kr/wisestone/owl/domain/WorkflowTransition.java
New file
@@ -0,0 +1,154 @@
package kr.wisestone.owl.domain;
import org.hibernate.annotations.Type;
import javax.persistence.*;
import java.io.Serializable;
/**
 * Created by wisestone on 2018-03-07.
 */
@Entity
public class WorkflowTransition extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "workflow_id")
    private Workflow workflow;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "source_issue_status_id")
    private IssueStatus sourceIssueStatus;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "target_issue_status_id")
    private IssueStatus targetIssueStatus;
    private Long sourceX;
    private Long sourceY;
    private Long targetX;
    private Long targetY;
    private Long correctX;
    private Long correctY;
    @Type(type="yes_no")
    private Boolean direct = Boolean.FALSE;
    public WorkflowTransition(){}
    public WorkflowTransition(Workflow workflow, IssueStatus sourceIssueStatus, IssueStatus targetIssueStatus, Long sourceX, Long sourceY, Long targetX, Long targetY, Long correctX, Long correctY){
        this.workflow = workflow;
        this.sourceIssueStatus = sourceIssueStatus;
        this.targetIssueStatus = targetIssueStatus;
        this.sourceX = sourceX;
        this.sourceY = sourceY;
        this.targetX = targetX;
        this.targetY = targetY;
        this.correctX = correctX;
        this.correctY = correctY;
    }
    public WorkflowTransition(Workflow workflow, IssueStatus sourceIssueStatus, IssueStatus targetIssueStatus, Long sourceX, Long sourceY, Long targetX, Long targetY, Long correctX, Long correctY, Boolean direct){
        this.workflow = workflow;
        this.sourceIssueStatus = sourceIssueStatus;
        this.targetIssueStatus = targetIssueStatus;
        this.sourceX = sourceX;
        this.sourceY = sourceY;
        this.targetX = targetX;
        this.targetY = targetY;
        this.correctX = correctX;
        this.correctY = correctY;
        this.direct = direct;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public Workflow getWorkflow() {
        return workflow;
    }
    public void setWorkflow(Workflow workflow) {
        this.workflow = workflow;
    }
    public IssueStatus getSourceIssueStatus() {
        return sourceIssueStatus;
    }
    public void setSourceIssueStatus(IssueStatus sourceIssueStatus) {
        this.sourceIssueStatus = sourceIssueStatus;
    }
    public IssueStatus getTargetIssueStatus() {
        return targetIssueStatus;
    }
    public void setTargetIssueStatus(IssueStatus targetIssueStatus) {
        this.targetIssueStatus = targetIssueStatus;
    }
    public Long getSourceX() {
        return sourceX;
    }
    public void setSourceX(Long sourceX) {
        this.sourceX = sourceX;
    }
    public Long getSourceY() {
        return sourceY;
    }
    public void setSourceY(Long sourceY) {
        this.sourceY = sourceY;
    }
    public Long getTargetX() {
        return targetX;
    }
    public void setTargetX(Long targetX) {
        this.targetX = targetX;
    }
    public Long getTargetY() {
        return targetY;
    }
    public void setTargetY(Long targetY) {
        this.targetY = targetY;
    }
    public Long getCorrectX() {
        return correctX;
    }
    public void setCorrectX(Long correctX) {
        this.correctX = correctX;
    }
    public Long getCorrectY() {
        return correctY;
    }
    public void setCorrectY(Long correctY) {
        this.correctY = correctY;
    }
    public Boolean getDirect() {
        return direct;
    }
    public void setDirect(Boolean direct) {
        this.direct = direct;
    }
}
src/main/java/kr/wisestone/owl/domain/Workspace.java
New file
@@ -0,0 +1,343 @@
package kr.wisestone.owl.domain;
import kr.wisestone.owl.domain.enumType.ServiceType;
import kr.wisestone.owl.util.DateUtil;
import javax.persistence.*;
import java.io.Serializable;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
/**
 * Created by wisestone on 2018-02-13.
 */
@Entity
public class Workspace extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    private static final Long DEFAULT_FREE_ALLOCATE_DISK = 10737418240L;
    private static final Long DEFAULT_USER_ALLOCATE_DISK = 3221225472L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private Integer maxUser;
    private Date startDate;
    private Date expireDate;
    private Long storageSize = 0L;
    private Long useTraffic = 0L;    //  트레픽 사용량 측정
    @Enumerated(EnumType.STRING)
    private ServiceType serviceType;    //  사용자가 서비스를 취소할 경우에 UNUSED 로 값이 변경된다. - 일반적인 경우에는 USE 값이다.
    @OneToOne(mappedBy = "workspace", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Payment payment;
    @OneToMany(mappedBy = "workspace", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<AttachedFile> attachedFiles = new HashSet<>();
    @OneToMany(mappedBy = "workspace", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<Project> projects = new HashSet<>();
    @OneToMany(mappedBy = "workspace", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<CustomField> customFields = new HashSet<>();
    @OneToMany(mappedBy = "workspace", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<IssueType> issueTypes = new HashSet<>();
    @OneToMany(mappedBy = "workspace", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<Workflow> workflows = new HashSet<>();
    @OneToMany(mappedBy = "workspace", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<IssueStatus> issueStatuses = new HashSet<>();
    @OneToMany(mappedBy = "workspace", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<UserWorkspace> userWorkspaces = new HashSet<>();
    @OneToMany(mappedBy = "workspace", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<UserInvite> userInvites = new HashSet<>();
    @OneToMany(mappedBy = "workspace", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<PaymentHistory> paymentHistories = new HashSet<>();
    @OneToMany(mappedBy = "workspace", cascade = { CascadeType.ALL }, orphanRemoval = true)
    private Set<IssueUser> issueUsers = new HashSet<>();
    @OneToMany(mappedBy = "workspace", cascade = { CascadeType.ALL }, orphanRemoval = true)
    private Set<IssueRisk> issueRisks = new HashSet<>();
    @OneToMany(mappedBy = "workspace", cascade = { CascadeType.ALL }, orphanRemoval = true)
    private Set<IssueComment> issueComments = new HashSet<>();
    @OneToMany(mappedBy = "workspace", cascade = { CascadeType.ALL }, orphanRemoval = true)
    private Set<UserLikeIssue> userLikeIssues = new HashSet<>();
    @OneToMany(mappedBy = "workspace", cascade = { CascadeType.ALL }, orphanRemoval = true)
    private Set<IssueSearch> issueSearches = new HashSet<>();
    @OneToMany(mappedBy = "workspace", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<IssueTableConfig> issueTableConfigs = new HashSet<>();
    @OneToMany(mappedBy = "workspace", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private Set<Priority> priorities = new HashSet<>();
    public Workspace() {
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getMaxUser() {
        return maxUser;
    }
    public void setMaxUser(Integer maxUser) {
        this.maxUser = maxUser;
    }
    public Date getStartDate() {
        return startDate;
    }
    public void setStartDate(Date startDate) {
        this.startDate = startDate;
    }
    public Date getExpireDate() {
        return expireDate;
    }
    public void setExpireDate(Date expireDate) {
        this.expireDate = expireDate;
    }
    public Long getStorageSize() {
        return storageSize;
    }
    public void setStorageSize(Long storageSize) {
        this.storageSize = storageSize;
    }
    public Payment getPayment() {
        return payment;
    }
    public void setPayment(Payment payment) {
        this.payment = payment;
    }
    public Set<PaymentHistory> getPaymentHistories() {
        return paymentHistories;
    }
    public void setPaymentHistories(Set<PaymentHistory> paymentHistories) {
        this.paymentHistories = paymentHistories;
    }
    public Long calculateStorageSize() {
        //  10명 이하는 5GB 제공
        if (this.maxUser < 11) {
            return DEFAULT_FREE_ALLOCATE_DISK;
        }
        //  10명 이상은 10GB + 사용자수 * 3기가 제공
        return DEFAULT_FREE_ALLOCATE_DISK + (this.maxUser * DEFAULT_USER_ALLOCATE_DISK);
    }
    public Set<Project> getProjects() {
        return projects;
    }
    public void setProjects(Set<Project> projects) {
        this.projects = projects;
    }
    public Set<IssueStatus> getIssueStatuses() {
        return issueStatuses;
    }
    public void setIssueStatuses(Set<IssueStatus> issueStatuses) {
        this.issueStatuses = issueStatuses;
    }
    public Set<AttachedFile> getAttachedFiles() {
        return attachedFiles;
    }
    public void setAttachedFiles(Set<AttachedFile> attachedFiles) {
        this.attachedFiles = attachedFiles;
    }
    public Set<UserWorkspace> getUserWorkspaces() {
        return userWorkspaces;
    }
    public void setUserWorkspaces(Set<UserWorkspace> userWorkspaces) {
        this.userWorkspaces = userWorkspaces;
    }
    public Set<UserInvite> getUserInvites() {
        return userInvites;
    }
    public void setUserInvites(Set<UserInvite> userInvites) {
        this.userInvites = userInvites;
    }
    public Set<IssueType> getIssueTypes() {
        return issueTypes;
    }
    public void setIssueTypes(Set<IssueType> issueTypes) {
        this.issueTypes = issueTypes;
    }
    public Set<Workflow> getWorkflows() {
        return workflows;
    }
    public void setWorkflows(Set<Workflow> workflows) {
        this.workflows = workflows;
    }
    public Set<CustomField> getCustomFields() {
        return customFields;
    }
    public void setCustomFields(Set<CustomField> customFields) {
        this.customFields = customFields;
    }
    public ServiceType getServiceType() {
        return serviceType;
    }
    public void setServiceType(ServiceType serviceType) {
        this.serviceType = serviceType;
    }
    public Set<IssueUser> getIssueUsers() {
        return issueUsers;
    }
    public void setIssueUsers(Set<IssueUser> issueUsers) {
        this.issueUsers = issueUsers;
    }
    public Set<IssueRisk> getIssueRisks() {
        return issueRisks;
    }
    public void setIssueRisks(Set<IssueRisk> issueRisks) {
        this.issueRisks = issueRisks;
    }
    public Set<IssueComment> getIssueComments() {
        return issueComments;
    }
    public void setIssueComments(Set<IssueComment> issueComments) {
        this.issueComments = issueComments;
    }
    public Set<UserLikeIssue> getUserLikeIssues() {
        return userLikeIssues;
    }
    public void setUserLikeIssues(Set<UserLikeIssue> userLikeIssues) {
        this.userLikeIssues = userLikeIssues;
    }
    public Long getUseTraffic() {
        return useTraffic;
    }
    public void setUseTraffic(Long useTraffic) {
        this.useTraffic = useTraffic;
    }
    public Set<IssueSearch> getIssueSearches() {
        return issueSearches;
    }
    public void setIssueSearches(Set<IssueSearch> issueSearches) {
        this.issueSearches = issueSearches;
    }
    public Set<IssueTableConfig> getIssueTableConfigs() {
        return issueTableConfigs;
    }
    public void setIssueTableConfigs(Set<IssueTableConfig> issueTableConfigs) {
        this.issueTableConfigs = issueTableConfigs;
    }
    public Set<Priority> getPriorities() {
        return priorities;
    }
    public void setPriorities(Set<Priority> priorities) {
        this.priorities = priorities;
    }
    public String makeCustomerUid(String account) {
        account = account.replaceAll(".", "_");
        MessageDigest messageDigest = null;
        try {
            messageDigest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        if (messageDigest != null) {
            byte[] digest = messageDigest.digest((account + this.getId()).getBytes());
            BigInteger bigInt = new BigInteger(1, digest);
            return bigInt.toString(16);
        }
        else {
            return account + this.getId();
        }
    }
    //  OWL ITS 결제는 매달일 경우 30일, 1년일 경우 365일로 한다.
    public Date calculateNextPaymentDate(String decisionType, Date expireDate) {
        Date toDay = new Date();
        if (Payment.MONTH.equals(decisionType)) {
            int compare = toDay.compareTo(expireDate);
            if (compare > 0) {
                //  오늘 날짜가 더 클 때 - 한동안 사용안하다가 결제한 경우
                return DateUtil.addDays(toDay, 30);
            }
            else {
                //  만료 날짜가 같거나 더 클 때 - 만료일에 결제했거나 무료 사용기간에 결제했을 때
                return DateUtil.addDays(expireDate, 30);
            }
        }
        else {
            //  매년 결제
            return DateUtil.addDays(expireDate, 365);
        }
    }
}
src/main/java/kr/wisestone/owl/domain/enumType/AttachedType.java
New file
@@ -0,0 +1,10 @@
package kr.wisestone.owl.domain.enumType;
/**
 * Create By J E O N G - S U N / 2019-05-02
 */
public enum AttachedType {
    SUMMER, //  섬머노트에서 업로드
    ISSUE_ATTACHED, //  이슈에서 업로드
    TEMP_SUMMER //  섬머노트에서 업로드 했으나 이슈에 연결되지 않은 경우
}
src/main/java/kr/wisestone/owl/domain/enumType/CustomFieldType.java
New file
@@ -0,0 +1,10 @@
package kr.wisestone.owl.domain.enumType;
/**
 * Created by wisestone on 2018-05-28.
 */
public enum CustomFieldType {
    INPUT,
    MULTI_SELECT,
    SINGLE_SELECT
}
src/main/java/kr/wisestone/owl/domain/enumType/EmailType.java
New file
@@ -0,0 +1,42 @@
package kr.wisestone.owl.domain.enumType;
/**
 * Created by wisestone on 2018-03-14.
 */
public enum EmailType {
    WORKSPACE_JOIN, //  회원 가입
    USER_SEARCH_PASSWORD, //  사용자 비밀번호 찾기
    USER_WITH_DRAW, //  회원 탈퇴
    REGULAR_PAYMENT,   //  정기 결제 완료
    REGULAR_PAYMENT_CANCEL, //  정기 결제 취소
    REGULAR_PAYMENT_CANCEL_BY_ACCOUNTING_MANAGER, //  정기 결제 취소 회계 담당자에게 알림
    REGULAR_PAYMENT_MODIFY, //  정기 결제 변경
    WORKSPACE_INVITE_NEW_USER,  //  신규 사용자 초대
    WORKSPACE_MAX_USER_EXCESS,  //  업무 공간 사용자 초과
    WORKSPACE_MAX_STORAGE_EXCESS,   //  업무 공간 저장 공간 부족
    USER_JOIN_STATISTICS,   //  사용자 현황 정보
    TOTAL_STATISTICS,   //  전체 시스템 현황 정보
    WORKSPACE_EXPIRE,   //  업무 공간 사용 기간 만료
    WORKSPACE_EXPIRE_ALARM,   //  업무 공간 사용 기간 만료 예정 알림
    WORKSPACE_INVITE_SYSTEM_USER,   //  기존 사용자 초대
    PROJECT_DEFAULT_EXCLUDE,    //  프로젝트의 일반사용자에서 제외
    PROJECT_DEFAULT_INCLUDE,    //  프로젝트에 일반 사용자로 참여
    PROJECT_MANAGER_EXCLUDE,    //  프로젝트에서 관리자 제외
    PROJECT_MANAGER_INCLUDE,    //  프로젝트에 관리자로 참여
    PROJECT_MANAGER_EXCLUDE_AND_PROJECT_DEFAULT_INCLUDE,    //  프로젝트 관리자 제외 후 일반 사용자로 참여
    PROJECT_DEFAULT_EXCLUDE_AND_PROJECT_MANAGER_INCLUDE,    //  일반 사용자에서 제외 후 프로젝트 관리자로 참여
    ISSUE_ADD,  //  이슈 생성
    ISSUE_MODIFY,   //  이슈 수정
    ISSUE_REMOVE,   //  이슈 삭제
    ISSUE_SEND, //  이슈 정보 이메일로 전달
    PROJECT_ADD,    //  프로젝트 생성
    PROJECT_MODIFY, //  프로젝트 수정
    PROJECT_REMOVE, //  프로젝트 삭제
    ISSUE_ASSIGNEE_INCLUDE,  //  이슈 담당자 지정
    ISSUE_ASSIGNEE_EXCLUDE, //  이슈 담당자 제외
}
src/main/java/kr/wisestone/owl/domain/enumType/FileType.java
New file
@@ -0,0 +1,11 @@
package kr.wisestone.owl.domain.enumType;
/**
 * Created by wisestone on 2018-10-11.
 */
public enum FileType {
    IMAGE,  //  이미지
    MEDIA,  //  미디어
    DOC,    //  문서
    ETC //  기타
}
src/main/java/kr/wisestone/owl/domain/enumType/IssueHistoryType.java
New file
@@ -0,0 +1,11 @@
package kr.wisestone.owl.domain.enumType;
/**
 * Created by wisestone on 2018-10-08.
 */
public enum IssueHistoryType {
    ADD,
    MODIFY,
    DELETE,
    TOTAL
}
src/main/java/kr/wisestone/owl/domain/enumType/IssueModifyType.java
New file
@@ -0,0 +1,16 @@
package kr.wisestone.owl.domain.enumType;
/**
 * Created by wisestone on 2018-10-10.
 */
public enum IssueModifyType {
    SEVERITY,
    PRIORITY,
    ISSUE_STATUS,
    ISSUE_TYPE,
    PERIOD,
    ASSIGNEE,
    CUSTOM_FIELD,
    TITLE,
    DESCRIPTION
}
src/main/java/kr/wisestone/owl/domain/enumType/IssueReservationType.java
New file
@@ -0,0 +1,11 @@
package kr.wisestone.owl.domain.enumType;
/**
 * Create By J E O N G - S U N / 2019-05-07
 */
public enum IssueReservationType {
    DAY,
    WEEK,
    MONTH,
    YEAR
}
src/main/java/kr/wisestone/owl/domain/enumType/IssueStatusType.java
New file
@@ -0,0 +1,10 @@
package kr.wisestone.owl.domain.enumType;
/**
 * Created by wisestone on 2018-03-09.
 */
public enum IssueStatusType {
    READY,
    OPEN,
    CLOSE
}
src/main/java/kr/wisestone/owl/domain/enumType/ProjectType.java
New file
@@ -0,0 +1,10 @@
package kr.wisestone.owl.domain.enumType;
/**
 * Created by wisestone on 2018-02-23.
 */
public enum ProjectType {
    RMS_PROJECT,
    BTS_PROJECT,
    TCM_PROJECT
}
src/main/java/kr/wisestone/owl/domain/enumType/ServiceType.java
New file
@@ -0,0 +1,9 @@
package kr.wisestone.owl.domain.enumType;
/**
 * Created by wisestone on 2018-10-05.
 */
public enum ServiceType {
    USE,    //  서비스 사용중
    UNUSED, //  서비스 만료
}
src/main/java/kr/wisestone/owl/domain/enumType/SocialType.java
New file
@@ -0,0 +1,11 @@
package kr.wisestone.owl.domain.enumType;
/**
 * Created by wisestone on 2018-02-21.
 */
public enum SocialType {
    GOOGLE,
    NAVER,
    KAKAO,
    FACEBOOK
}
src/main/java/kr/wisestone/owl/domain/interceptor/AuditLogInterceptor.java
New file
@@ -0,0 +1,80 @@
package kr.wisestone.owl.domain.interceptor;
import kr.wisestone.owl.domain.BaseEntity;
import kr.wisestone.owl.util.ApplicationContextUtil;
import kr.wisestone.owl.util.WebAppUtil;
import org.hibernate.CallbackException;
import org.hibernate.EmptyInterceptor;
import org.hibernate.type.Type;
import java.io.Serializable;
import java.util.Date;
public class AuditLogInterceptor extends EmptyInterceptor {
    private static final long serialVersionUID = 1L;
    @Override
    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState,
            Object[] previousState, String[] propertyNames, Type[] types) throws CallbackException {
        this.setModifyInfoToEntity(entity, currentState, propertyNames);
        return true;
    }
    @Override
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames,
            Type[] types) throws CallbackException {
        this.setRegisterInfo(entity, state, propertyNames);
        return true;
    }
    private void setModifyInfoToEntity(Object entity, Object[] state, String[] propertyNames) {
        if (entity instanceof BaseEntity) {
            Long loginId = this.getUserId();
            if (loginId == null) {
                loginId = Long.valueOf(1);
            }
            for (int i = 0; i < propertyNames.length; i++) {
                if ("modifyId".equals(propertyNames[i])) {
                    state[i] = loginId;
                } else if ("modifyDate".equals(propertyNames[i])) {
                    state[i] = new Date();
                }
            }
        }
    }
    private void setRegisterInfo(Object entity, Object[] state, String[] propertyNames) {
        if (entity instanceof BaseEntity) {
            Long loginId = this.getUserId();
            if (loginId == null) {
                loginId = Long.valueOf(1);
            }
            Date currentDate = new Date();
            for (int i = 0; i < propertyNames.length; i++) {
                if ("registerId".equals(propertyNames[i])) {
                    state[i] = loginId;
                } else if ("modifyId".equals(propertyNames[i])) {
                    state[i] = loginId;
                } else if ("registerDate".equals(propertyNames[i])) {
                    state[i] = currentDate;
                } else if ("modifyDate".equals(propertyNames[i])) {
                    state[i] = currentDate;
                }
            }
        }
    }
    private Long getUserId() {
        WebAppUtil webAppUtil = ApplicationContextUtil.getBean("webAppUtil", WebAppUtil.class);
        if (webAppUtil == null) {
            return null;
        }
        return webAppUtil.getLoginId();
    }
}
src/main/java/kr/wisestone/owl/domain/strategy/PrefixNamingStrategy.java
New file
@@ -0,0 +1,47 @@
package kr.wisestone.owl.domain.strategy;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
public class PrefixNamingStrategy implements PhysicalNamingStrategy {
    @Override
    public Identifier toPhysicalCatalogName(Identifier identifier, JdbcEnvironment jdbcEnv) {
        return convert(identifier);
    }
    @Override
    public Identifier toPhysicalColumnName(Identifier identifier, JdbcEnvironment jdbcEnv) {
        return convert(identifier);
    }
    @Override
    public Identifier toPhysicalSchemaName(Identifier identifier, JdbcEnvironment jdbcEnv) {
        return convert(identifier);
    }
    @Override
    public Identifier toPhysicalSequenceName(Identifier identifier, JdbcEnvironment jdbcEnv) {
        return convert(identifier);
    }
    @Override
    public Identifier toPhysicalTableName(Identifier identifier, JdbcEnvironment jdbcEnv) {
        return convert(identifier);
    }
    private Identifier convert(Identifier identifier) {
        if (identifier == null || StringUtils.isBlank(identifier.getText())) {
            return identifier;
        }
        String regex = "([a-z])([A-Z])";
        String replacement = "$1_$2";
        String newName = identifier.getText().replaceAll(regex, replacement).toLowerCase();
        return Identifier.toIdentifier(newName);
    }
}
src/main/java/kr/wisestone/owl/domain/support/ZonedDateTimeTypeHandler.java
New file
@@ -0,0 +1,51 @@
package kr.wisestone.owl.domain.support;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
import java.sql.*;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.GregorianCalendar;
@MappedTypes(ZonedDateTime.class)
public class ZonedDateTimeTypeHandler extends BaseTypeHandler<ZonedDateTime> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, ZonedDateTime parameter, JdbcType jdbcType) throws SQLException {
        ps.setTimestamp(
            i,
            Timestamp.from(parameter.toInstant()),
            GregorianCalendar.from(parameter)
        );
    }
    @Override
    public ZonedDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Timestamp ts = rs.getTimestamp(columnName, Calendar.getInstance());
        if (ts != null) {
            return ZonedDateTime.ofInstant(ts.toInstant(), ZoneId.systemDefault());
        }
        return null;
    }
    @Override
    public ZonedDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        Timestamp ts = rs.getTimestamp(columnIndex, Calendar.getInstance());
        if (ts != null) {
            return ZonedDateTime.ofInstant(ts.toInstant(), ZoneId.systemDefault());
        }
        return null;
    }
    @Override
    public ZonedDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        Timestamp ts = cs.getTimestamp(columnIndex, Calendar.getInstance());
        if (ts != null) {
            return ZonedDateTime.ofInstant(ts.toInstant(), ZoneId.systemDefault());
        }
        return null;
    }
}
src/main/java/kr/wisestone/owl/exception/OwlRuntimeException.java
New file
@@ -0,0 +1,52 @@
package kr.wisestone.owl.exception;
import kr.wisestone.owl.vo.MessageVo;
public class OwlRuntimeException extends RuntimeException {
    private static final long serialVersionUID = 1L;
    private String code;
    public OwlRuntimeException() {
        super();
    }
    public OwlRuntimeException(String code, final String message,
                               final Throwable cause) {
        super(message, cause);
        this.code = code;
    }
    public OwlRuntimeException(String code, final String message) {
        super(message);
        this.code = code;
    }
    public OwlRuntimeException(MessageVo message) {
        super(message.getMessage());
        this.code = message.getCode();
    }
    public OwlRuntimeException(MessageVo message, Throwable t) {
        super(message.getMessage(), t);
        this.code = message.getCode();
    }
    public OwlRuntimeException(Exception e) {
        super(e);
    }
    public OwlRuntimeException(String code) {
        this.code = code;
    }
    public OwlRuntimeException(String code, Exception e) {
        super(e);
        this.code = code;
    }
    public String getCode() {
        return this.code;
    }
}
src/main/java/kr/wisestone/owl/initializer/AppInitializer.java
New file
@@ -0,0 +1,52 @@
package kr.wisestone.owl.initializer;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.DispatcherServlet;
import javax.servlet.*;
import java.util.EnumSet;
/**
 * Created by jeong on 2017-08-01.
 */
public class AppInitializer implements WebApplicationInitializer {
    private static final String CONFIG_LOCATION = "kr.wisestone.owl.config";
    private static final String MAPPING_URL = "/*";
    @Override
    public void onStartup(ServletContext servletContext) {
        this.registerDispatcherServlet(servletContext);
        this.registerCharacterEncodingFilter(servletContext);
        this.registerSpringSecurityFilter(servletContext);
    }
    private void registerDispatcherServlet(ServletContext servletContext) {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.setConfigLocation(CONFIG_LOCATION);
        servletContext.addListener(new ContextLoaderListener(context));
        servletContext.addListener(new HttpSessionEventPublisher());
        ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet", new DispatcherServlet(context));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping(MAPPING_URL);
        context.registerShutdownHook();
    }
    private void registerCharacterEncodingFilter(ServletContext servletContext) {
        FilterRegistration.Dynamic characterEncodingFilter = servletContext.addFilter("characterEncodingFilter", new CharacterEncodingFilter());
        characterEncodingFilter.setInitParameter("encoding", "UTF-8");
        characterEncodingFilter.setInitParameter("forceEncoding", "true");
        characterEncodingFilter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
    }
    private void registerSpringSecurityFilter(ServletContext servletContext) {
        FilterRegistration.Dynamic springSecurityFilter = servletContext.addFilter("springSecurityFilterChain", new DelegatingFilterProxy());
        springSecurityFilter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
    }
}
src/main/java/kr/wisestone/owl/mapper/AttachedFileMapper.java
New file
@@ -0,0 +1,25 @@
package kr.wisestone.owl.mapper;
import kr.wisestone.owl.web.condition.AttachedFileCondition;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
 * Created by wisestone on 2018-02-27.
 */
@Repository
public interface AttachedFileMapper {
    Long findUseStorage(AttachedFileCondition attachedFileCondition);
    List<Map<String, Object>> findByWorkspaceId(Long workspaceId);
    List<Map<String, Object>> findByIssueIds(AttachedFileCondition attachedFileCondition);
    void deleteAttachedFileByWorkspaceId(Long workspaceId);
    void deleteAttachedFileByIssueIds(AttachedFileCondition attachedFileCondition);
    void deleteAttachedFileNotId();
}
src/main/java/kr/wisestone/owl/mapper/CustomFieldMapper.java
New file
@@ -0,0 +1,17 @@
package kr.wisestone.owl.mapper;
import kr.wisestone.owl.web.condition.CustomFieldCondition;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
 * Created by wisestone on 2018-05-28.
 */
@Repository
public interface CustomFieldMapper {
    List<Map<String, Object>> find(CustomFieldCondition customFieldCondition);
    Long count(CustomFieldCondition customFieldCondition);
}
src/main/java/kr/wisestone/owl/mapper/EventMapper.java
New file
@@ -0,0 +1,14 @@
package kr.wisestone.owl.mapper;
import kr.wisestone.owl.web.condition.EventCondition;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
@Repository
public interface EventMapper {
    List<Map<String, Object>> find(EventCondition eventCondition);
    Long count(EventCondition eventCondition);
}
src/main/java/kr/wisestone/owl/mapper/FaqMapper.java
New file
@@ -0,0 +1,14 @@
package kr.wisestone.owl.mapper;
import kr.wisestone.owl.web.condition.FaqCondition;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
@Repository
public interface FaqMapper {
    List<Map<String, Object>> find(FaqCondition faqCondition);
    Long count(FaqCondition faqCondition);
}
src/main/java/kr/wisestone/owl/mapper/GuideMapper.java
New file
@@ -0,0 +1,14 @@
package kr.wisestone.owl.mapper;
import kr.wisestone.owl.web.condition.GuideCondition;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
@Repository
public interface GuideMapper {
    List<Map<String, Object>> find(GuideCondition guideCondition);
    Long count(GuideCondition guideCondition);
}
src/main/java/kr/wisestone/owl/mapper/IssueCustomFieldValueMapper.java
New file
@@ -0,0 +1,24 @@
package kr.wisestone.owl.mapper;
import kr.wisestone.owl.web.condition.IssueCondition;
import kr.wisestone.owl.web.condition.IssueCustomFieldValueCondition;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
 * Created by wisestone on 2018-06-07.
 */
@Repository
public interface IssueCustomFieldValueMapper {
    Map<String, Object> findLikeUseValue(IssueCustomFieldValueCondition issueCustomFieldValueCondition);
    Map<String, Object> findByUseValue(IssueCustomFieldValueCondition issueCustomFieldValueCondition);
    void deleteIssueCustomFieldValue(Long issueTypeCustomFieldId);
    List<Map<String, Object>> findInIssueIds(IssueCondition issueCondition);
    void deleteByIssueCustomFieldValueId(List<Long> ids);
}
src/main/java/kr/wisestone/owl/mapper/IssueHistoryMapper.java
New file
@@ -0,0 +1,17 @@
package kr.wisestone.owl.mapper;
import kr.wisestone.owl.web.condition.IssueHistoryCondition;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
 * Created by wisestone on 2018-10-17.
 */
@Repository
public interface IssueHistoryMapper {
    List<Map<String, Object>> find(IssueHistoryCondition issueHistoryCondition);
    Long count(IssueHistoryCondition issueHistoryCondition);
}
src/main/java/kr/wisestone/owl/mapper/IssueMapper.java
New file
@@ -0,0 +1,41 @@
package kr.wisestone.owl.mapper;
import kr.wisestone.owl.web.condition.IssueCondition;
import kr.wisestone.owl.web.form.IssueForm;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
 * Created by wisestone on 2018-01-17.
 */
@Repository
public interface IssueMapper {
    List<Map<String, Object>> find(IssueCondition issueCondition);
    Long count(IssueCondition issueCondition);
    void insertBatch(@Param("issueForms") List<IssueForm> issueForms);
    void updateBatch(@Param("issueForms") List<IssueForm> issueForms);
    void insertHistoryBatch(List<Map<String, Object>> issueHistoryMaps);
    void insertIssueRiskBatch(List<Map<String, Long>> issueRiskMaps);
    void insertIssueCustomFieldValueBatch(List<Map<String, Object>> issueCustomFieldValueMaps);
    List<Map<String, Object>> findIssueUser(IssueCondition issueCondition);
    Long countByIssueTypeId(Long issueTypeId);
    Long countByIssueStatusId(Long issueStatusId);
    List<Map<String, Object>> findByIssueTypeId(Long issueTypeId);
    List<Map<String, Object>> findByProjectId(Long projectId);
    List<Map<String, Object>> getAllTaskUser(IssueCondition taskCondition);
}
src/main/java/kr/wisestone/owl/mapper/IssueStatusMapper.java
New file
@@ -0,0 +1,17 @@
package kr.wisestone.owl.mapper;
import kr.wisestone.owl.web.condition.IssueStatusCondition;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
 * Created by wisestone on 2018-05-09.
 */
@Repository
public interface IssueStatusMapper {
    List<Map<String, Object>> find(IssueStatusCondition issueStatusCondition);
    Long count(IssueStatusCondition issueStatusCondition);
}
src/main/java/kr/wisestone/owl/mapper/IssueTypeMapper.java
New file
@@ -0,0 +1,17 @@
package kr.wisestone.owl.mapper;
import kr.wisestone.owl.web.condition.IssueTypeCondition;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
 * Created by wisestone on 2018-05-29.
 */
@Repository
public interface IssueTypeMapper {
    List<Map<String, Object>> find(IssueTypeCondition issueTypeCondition);
    Long count(IssueTypeCondition issueTypeCondition);
}
src/main/java/kr/wisestone/owl/mapper/IssueUserMapper.java
New file
@@ -0,0 +1,21 @@
package kr.wisestone.owl.mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
 * Created by wisestone on 2018-11-27.
 */
@Repository
public interface IssueUserMapper {
    void insertIssueUser(List<Map<String, Long>> issueRoleUserMaps);
    void deleteIssueUserByIssueIdAndMultiUserId(Map<String, Object> removeIssueAssigneeMap);
    void deleteIssueUserByUserIdAndMultiIssueId(Map<String, Object> removeIssueAssigneeMap);
    List<Map<String, Object>> findByUserIdAndProjectId(Map<String, Object> issueUserMap);
}
src/main/java/kr/wisestone/owl/mapper/NoticeMapper.java
New file
@@ -0,0 +1,14 @@
package kr.wisestone.owl.mapper;
import kr.wisestone.owl.web.condition.NoticeCondition;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
@Repository
public interface NoticeMapper {
    List<Map<String, Object>> find(NoticeCondition noticeCondition);
    Long count(NoticeCondition noticeCondition);
}
src/main/java/kr/wisestone/owl/mapper/ProjectMapper.java
New file
@@ -0,0 +1,34 @@
package kr.wisestone.owl.mapper;
import kr.wisestone.owl.web.condition.ProjectCondition;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
 * Created by wisestone on 2018-02-23.
 */
@Repository
public interface ProjectMapper {
    List<Map<String, Object>> find(ProjectCondition projectCondition);
    Long count(ProjectCondition projectCondition);
    List<Map<String, Object>> findByWorkspaceManager(ProjectCondition projectCondition);
    List<Map<String, Object>> findByWorkspaceManagerAll(ProjectCondition projectCondition);
    Long countByWorkspaceManager(ProjectCondition projectCondition);
    List<Map<String, Object>> findByWorkspaceIdAndIncludeProject(ProjectCondition projectCondition);
    List<Map<String, Object>> findByWorkspaceIdAndIncludeProjectAll(ProjectCondition projectCondition);
    void deleteProject(Map<String, Object> deleteProjectMap);
    List<Map<String, Object>> checkIncludeProject(ProjectCondition projectCondition);
    List<Map<String, Object>> findChildrenProject(Long parentProjectId);
}
src/main/java/kr/wisestone/owl/mapper/ProjectRoleUserMapper.java
New file
@@ -0,0 +1,19 @@
package kr.wisestone.owl.mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
 * Created by wisestone on 2018-11-27.
 */
@Repository
public interface ProjectRoleUserMapper {
    void insertProjectRoleUser(List<Map<String, Long>> projectRoleUserMaps);
    void deleteProjectRoleUser(Map<String, Long> projectRoleUserMap);
    List<Map<String, Object>> findProjectRoleUser(Map<String, Object> projectRoleUserMap);
}
src/main/java/kr/wisestone/owl/mapper/QnaMapper.java
New file
@@ -0,0 +1,14 @@
package kr.wisestone.owl.mapper;
import kr.wisestone.owl.web.condition.QnaCondition;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
@Repository
public interface QnaMapper {
    List<Map<String, Object>> find(QnaCondition qnaCondition);
    Long count(QnaCondition qnaCondition);
}
src/main/java/kr/wisestone/owl/mapper/UserMapper.java
New file
@@ -0,0 +1,32 @@
package kr.wisestone.owl.mapper;
import kr.wisestone.owl.web.condition.UserCondition;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
 * Created by wisestone on 2018-02-26.
 */
@Repository
public interface UserMapper {
    List<Map<String, Object>> find(UserCondition userCondition);
    List<Map<String, Object>> findAdmin();
    List<Map<String, Object>> findProjectMember(UserCondition userCondition);
    Long count(UserCondition userCondition);
    void deleteCascadeUser(UserCondition userCondition);
    List<Map<String, Object>> findByReservationNotifyTime(Map<String, Object> conditions);
    List<Map<String, Object>> findByAllWorkspace(UserCondition userCondition);
    Long countByAllWorkspace(UserCondition userCondition);
    List<Map<String, Object>> findEvent();
}
src/main/java/kr/wisestone/owl/mapper/UserWorkspaceMapper.java
New file
@@ -0,0 +1,17 @@
package kr.wisestone.owl.mapper;
import kr.wisestone.owl.web.condition.UserWorkspaceCondition;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
 * Created by wisestone on 2018-10-02.
 */
@Repository
public interface UserWorkspaceMapper {
    List<Map<String, Object>> find(UserWorkspaceCondition userWorkspaceCondition);
    Long count(UserWorkspaceCondition userWorkspaceCondition);
}
src/main/java/kr/wisestone/owl/mapper/WidgetMapper.java
New file
@@ -0,0 +1,82 @@
package kr.wisestone.owl.mapper;
import kr.wisestone.owl.web.condition.WidgetCondition;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
 * Created by wisestone on 2018-01-11.
 */
@Repository
public interface WidgetMapper {
    // 완료된 이슈
    Long countCompleteIssue(WidgetCondition widgetCondition);
    //  미할당 이슈
    Long countNoAssigneeIssue(WidgetCondition widgetCondition);
    //  내가 등록한 이슈
    List<Map<String, Object>> findRegisterIssue(WidgetCondition widgetCondition);
    //  내가 오늘 등록한 이슈 갯수
    Long countTodayRegisterIssue(WidgetCondition widgetCondition);
    Long countRegisterIssue(WidgetCondition widgetCondition);
    //  할당된 이슈
    Long countAssigneeIssue(WidgetCondition widgetCondition);
    //  지연된 이슈
    List<Map<String, Object>> findDelayIssue(WidgetCondition widgetCondition);
    Long countDelayIssue(WidgetCondition widgetCondition);
    Long countTodayDelayIssue(WidgetCondition widgetCondition);
    //  잔여 이슈
    Long countRemainIssue(WidgetCondition widgetCondition);
    //  진행중인 프로젝트 현황(참여중인 프로젝트)
    List<Map<String, Object>> findProjectProgress(WidgetCondition widgetCondition);
    //  진행중인 프로젝트 현황(전체)
    List<Map<String, Object>> findProjectProgressAll(WidgetCondition widgetCondition);
    //  나에게 할당된 이슈
    List<Map<String, Object>> findMyAssigneeIssue(WidgetCondition widgetCondition);
    //  내가 오늘 할당받은 이슈 갯수
    Long countTodayMyAssigneeIssue(WidgetCondition widgetCondition);
    Long countMyAssigneeIssue(WidgetCondition widgetCondition);
    //  멤버별 진행률
    List<Map<String, Object>> findProjectMemberIssue(WidgetCondition widgetCondition);
    //  등록한 이슈 중 완료 갯수
    List<Map<String, Object>> findMyRegisterCompleteIssue(WidgetCondition widgetCondition);
    //  등록한 이슈 중 진행 갯수
    List<Map<String, Object>> findMyRegisterRemainIssue(WidgetCondition widgetCondition);
    //  담당한 이슈 중 완료 갯수
    List<Map<String, Object>> findMyAssigneeCompleteIssue(WidgetCondition widgetCondition);
    //  담당한 이슈 중 진행 갯수
    List<Map<String, Object>> findMyAssigneeRemainIssue(WidgetCondition widgetCondition);
    //  위험 관리
    List<Map<String, Object>> findRiskIssue(WidgetCondition widgetCondition);
    Map<String, Object> countChangeStatusAndAssigneeIssue(WidgetCondition widgetCondition);
    Long countRiskIssue(WidgetCondition widgetCondition);
    //  전체 이슈 처리 현황
    List<Map<String, Object>> findIssueComplete(WidgetCondition widgetCondition);
    Long countTotalIssue(WidgetCondition widgetCondition);
    //  상태별 이슈 현황
    List<Map<String, Object>> findByStandIssueStatus(WidgetCondition widgetCondition);
    List<Map<String, Object>> findByStandIssueType(WidgetCondition widgetCondition);
    //중요도 별 이슈 갯수
    List<Map<String, Object>> countSeverityIssue(WidgetCondition widgetCondition);
    //중요도 이슈 목록
    List<Map<String, Object>> findSeverityIssues(WidgetCondition widgetCondition);
    //중요도 드롭다운 별 항목 갯수
    Long countSearchIssue(WidgetCondition widgetCondition);
}
src/main/java/kr/wisestone/owl/mapper/WorkflowMapper.java
New file
@@ -0,0 +1,17 @@
package kr.wisestone.owl.mapper;
import kr.wisestone.owl.web.condition.WorkflowCondition;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
 * Created by wisestone on 2018-05-10.
 */
@Repository
public interface WorkflowMapper {
    List<Map<String, Object>> find(WorkflowCondition workflowCondition);
    Long count(WorkflowCondition workflowCondition);
}
src/main/java/kr/wisestone/owl/mapper/WorkspaceMapper.java
New file
@@ -0,0 +1,13 @@
package kr.wisestone.owl.mapper;
import org.springframework.stereotype.Repository;
import java.util.Map;
/**
 * Created by wisestone on 2018-11-29.
 */
@Repository
public interface WorkspaceMapper {
    void deleteWorkspace(Map<String, Object> deleteWorkspaceMap);
}
src/main/java/kr/wisestone/owl/monitor/Email.java
New file
@@ -0,0 +1,76 @@
package kr.wisestone.owl.monitor;
import com.google.common.collect.Lists;
import java.util.Date;
import java.util.List;
/**
 * Created by wisestone on 2018-02-09.
 */
public class Email {
    public Date received;
    public String from;
    public List<String> to = Lists.newArrayList();
    public List<String> cc = Lists.newArrayList();
    public String subject;
    public String body;
    public List<EmailAttachment> attachments = Lists.newArrayList();
    public Email(){}
    public Date getReceived() {
        return received;
    }
    public void setReceived(Date received) {
        this.received = received;
    }
    public String getFrom() {
        return from;
    }
    public void setFrom(String from) {
        this.from = from;
    }
    public List<String> getTo() {
        return to;
    }
    public void setTo(List<String> to) {
        this.to = to;
    }
    public List<String> getCc() {
        return cc;
    }
    public void setCc(List<String> cc) {
        this.cc = cc;
    }
    public String getSubject() {
        return subject;
    }
    public void setSubject(String subject) {
        this.subject = subject;
    }
    public String getBody() {
        return body;
    }
    public void setBody(String body) {
        this.body = body;
    }
    public List<EmailAttachment> getAttachments() {
        return attachments;
    }
    public void setAttachments(List<EmailAttachment> attachments) {
        this.attachments = attachments;
    }
}
src/main/java/kr/wisestone/owl/monitor/EmailAttachment.java
New file
@@ -0,0 +1,36 @@
package kr.wisestone.owl.monitor;
/**
 * Created by wisestone on 2018-02-09.
 */
public class EmailAttachment {
    public String name;
    public String path;
    public Long size;
    public EmailAttachment(){}
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPath() {
        return path;
    }
    public void setPath(String path) {
        this.path = path;
    }
    public Long getSize() {
        return size;
    }
    public void setSize(Long size) {
        this.size = size;
    }
}
src/main/java/kr/wisestone/owl/monitor/MailMonitor.java
New file
@@ -0,0 +1,226 @@
package kr.wisestone.owl.monitor;
import com.google.common.collect.Lists;
import kr.wisestone.owl.util.WebAppUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import javax.mail.*;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeUtility;
import javax.mail.search.FlagTerm;
import java.io.*;
import java.util.List;
import java.util.Properties;
/**
 * Created by jeong on 2018-02-08.
 */
public class MailMonitor {
    private static final Logger log = LoggerFactory.getLogger(MailMonitor.class);
    private volatile static MailMonitor uniqueInstance;
    private Store store;
    private String downloadDirectory = "";
    private String userName = "";
    private String password = "";
    private MailMonitor() {
        //  시스템 프로퍼티를 읽어 다운로드 경로를 지정
        this.initMailProperties();
        this.connectEmail();
    }
    public static MailMonitor getInstance() {
        if (uniqueInstance == null) {
            synchronized (MailMonitor.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new MailMonitor();
                }
            }
        }
        return uniqueInstance;
    }
    private void initMailProperties() {
        Properties properties = new Properties();
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        InputStream inputStream = classLoader.getResourceAsStream("system.properties");
        try {
            properties.load(inputStream);
            this.downloadDirectory = properties.getProperty("mail.file.path");
            this.userName = properties.getProperty("mail.account");
            this.password = properties.getProperty("mail.password");
        } catch (IOException e) {
            log.debug("initMailProperties() : " + e.getMessage());
        }
    }
    private void connectEmail() {
        Properties properties = new Properties();
        properties.setProperty("mail.host", "imap.gmail.com");
        properties.setProperty("mail.port", "995");
        properties.setProperty("mail.transport.protocol", "imaps");
        Session session = Session.getInstance(properties,
                new javax.mail.Authenticator() {
                    protected PasswordAuthentication getPasswordAuthentication() {
                        return new PasswordAuthentication(userName, password);
                    }
                });
        try {
            this.store = session.getStore("imaps");
            this.store.connect();
        } catch (MessagingException e) {
            log.debug("connectEmail() : " + e.getMessage());
        }
    }
    //  TODO - 이메일 연동 시스템에서 추가적으로 사용자가 이메일을 보내고 싶은 프로젝트를 선택하는 설정 화면 개발이 필요하다.
    public List<Email> readMails() {
        Folder inbox = null;
        List<Email> emails = Lists.newArrayList();
        try {
            inbox = store.getFolder("INBOX");
            inbox.open(Folder.READ_WRITE);
            Message messages[] = inbox.search(new FlagTerm(new Flags(Flags.Flag.SEEN), false));
            System.out.println("Number of mails = " + messages.length);
            for (Message message : messages) {
                Email email = new Email();
                Address[] from = message.getFrom();
                /*System.out.println("-------------------------------");
                System.out.println("Date : " + message.getSentDate());
                System.out.println("From : " + MimeUtility.decodeText(from[0].toString()));
                System.out.println("Subject: " + MimeUtility.decodeText(message.getSubject()));
                System.out.println("Content :");*/
                String contentType = message.getContentType();
                String messageContent = "";
                List<EmailAttachment> emailAttachments = Lists.newArrayList();
                if (contentType.contains("multipart")) {
                    Multipart multiPart = (Multipart) message.getContent();
                    int numberOfParts = multiPart.getCount();
                    for (int partCount = 0; partCount < numberOfParts; partCount++) {
                        MimeBodyPart part = (MimeBodyPart) multiPart.getBodyPart(partCount);
                        if (Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition())) {
                            // 첨부 파일
                            String fileName = MimeUtility.decodeText(part.getFileName());
                            EmailAttachment emailAttachment = new EmailAttachment();
                            emailAttachment.setName(fileName);
                            emailAttachment.setPath(this.downloadDirectory + fileName);
                            emailAttachments.add(emailAttachment);
                            part.saveFile(this.downloadDirectory + fileName);
                        }
                        else {
                            // 본문 텍스트
                            messageContent = this.getText(part);
                        }
                    }
                }
                else if (contentType.contains("text/plain") || contentType.contains("text/html")) {
                    Object content = message.getContent();
                    if (content != null) {
                        // 본문 텍스트
                        messageContent = content.toString();
                    }
                }
                /*System.out.println("\t Message: " + messageContent);
                System.out.println("--------------------------------");*/
                email.setSubject(message.getSubject());
                email.setFrom(MimeUtility.decodeText(from[0].toString()));
                email.setBody(messageContent);
                email.setAttachments(emailAttachments);
                emails.add(email);
            }
            //inbox.setFlags(messages, new Flags(Flags.Flag.SEEN), true);
            /*inbox.close(true);
            this.store.close();*/
        } catch (NoSuchProviderException e) {
            log.debug("readMails() : "+ e.getMessage());
            e.printStackTrace();
        } catch (MessagingException e) {
            log.debug("readMails() : "+ e.getMessage());
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            log.debug("readMails() : "+ e.getMessage());
            e.printStackTrace();
        } catch (IOException e) {
            log.debug("readMails() : "+ e.getMessage());
            e.printStackTrace();
        }
        finally{
            this.closeFolder(inbox);
        }
        return emails;
    }
    private String getText(Part part) throws MessagingException, IOException {
        if (part.isMimeType("text/*")) {
            return (String) part.getContent();
        }
        if (part.isMimeType("multipart/alternative")) {
            // prefer html text over plain text
            Multipart mp = (Multipart) part.getContent();
            String text = null;
            for (int i = 0; i < mp.getCount(); i++) {
                Part bp = mp.getBodyPart(i);
                if (bp.isMimeType("text/plain")) {
                    if (text == null) {
                        text = this.getText(bp);
                    }
                    continue;
                }
                else if (bp.isMimeType("text/html")) {
                    String content = this.getText(bp);
                    if (content != null) {
                        return content;
                    }
                }
                else {
                    return this.getText(bp);
                }
            }
            return text;
        }
        else if (part.isMimeType("multipart/*")) {
            Multipart mp = (Multipart) part.getContent();
            for (int i = 0; i < mp.getCount(); i++) {
                String content = this.getText(mp.getBodyPart(i));
                if (content != null) {
                    return content;
                }
            }
        }
        return null;
    }
    private void closeFolder(Folder inbox) {
        try {
            if (inbox != null) {
                inbox.close(false);
            }
        } catch (MessagingException e) {
            log.debug("closeFolder() : "+ e.getMessage());
        }
    }
}
src/main/java/kr/wisestone/owl/repository/AttachedFileRepository.java
New file
@@ -0,0 +1,10 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.AttachedFile;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface AttachedFileRepository extends JpaRepository<AttachedFile, Long> {
    List<AttachedFile> findByIssueId(@Param("issueId") Long issueId);
}
src/main/java/kr/wisestone/owl/repository/CustomFieldRepository.java
New file
@@ -0,0 +1,16 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.CustomField;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface CustomFieldRepository extends JpaRepository<CustomField, Long> {
    CustomField findByNameAndWorkspaceId(@Param("name") String name, @Param("workspaceId") Long workspaceId);
    CustomField findByNameAndWorkspaceIdAndIdNot(@Param("name") String name, @Param("workspaceId") Long workspaceId, @Param("id") Long id);
    List<CustomField> findByWorkspaceId(@Param("workspaceId") Long workspaceId);
}
src/main/java/kr/wisestone/owl/repository/CustomFieldValueRepository.java
New file
@@ -0,0 +1,7 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.CustomFieldValue;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CustomFieldValueRepository extends JpaRepository<CustomFieldValue, Long> {
}
src/main/java/kr/wisestone/owl/repository/EventRepository.java
New file
@@ -0,0 +1,15 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.Event;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
public interface EventRepository extends JpaRepository<Event, Long> {
    @Modifying
    @Transactional
    @Query(name = "Event.updateInActivation")
    void updateInActivation(@Param("eventId") Long eventId);
}
src/main/java/kr/wisestone/owl/repository/FaqRepository.java
New file
@@ -0,0 +1,7 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.Faq;
import org.springframework.data.jpa.repository.JpaRepository;
public interface FaqRepository extends JpaRepository<Faq, Long> {
}
src/main/java/kr/wisestone/owl/repository/GuideRepository.java
New file
@@ -0,0 +1,15 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.Guide;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
public interface GuideRepository extends JpaRepository<Guide, Long> {
    @Modifying
    @Transactional
    @Query(name = "Guide.updateInActivation")
    void updateInActivation(@Param("guideId") Long guideId);
}
src/main/java/kr/wisestone/owl/repository/IssueCommentRepository.java
New file
@@ -0,0 +1,14 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.IssueComment;
import kr.wisestone.owl.vo.IssueCommentVo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface IssueCommentRepository extends JpaRepository<IssueComment, Long> {
    @Query(name="IssueComment.findByIssueId")
    List<IssueCommentVo> findByIssueId(@Param("issueId") Long issueId);
}
src/main/java/kr/wisestone/owl/repository/IssueCustomFieldValueRepository.java
New file
@@ -0,0 +1,13 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.IssueCustomFieldValue;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface IssueCustomFieldValueRepository extends JpaRepository<IssueCustomFieldValue, Long> {
    List<IssueCustomFieldValue> findByIssueId(@Param("issueId") Long issueId);
    List<IssueCustomFieldValue> findByCustomFieldId(@Param("customFieldId") Long customFieldId);
}
src/main/java/kr/wisestone/owl/repository/IssueHistoryRepository.java
New file
@@ -0,0 +1,13 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.IssueHistory;
import kr.wisestone.owl.vo.IssueHistoryVo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface IssueHistoryRepository extends JpaRepository<IssueHistory, Long> {
    @Query(name="IssueHistory.findByIssueId")
    List<IssueHistoryVo> findByIssueId(@Param("issueId") Long issueId);
}
src/main/java/kr/wisestone/owl/repository/IssueNumberGeneratorRepository.java
New file
@@ -0,0 +1,9 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.IssueNumberGenerator;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
public interface IssueNumberGeneratorRepository extends JpaRepository<IssueNumberGenerator, Long> {
    IssueNumberGenerator findByProjectId(@Param("projectId") Long projectId);
}
src/main/java/kr/wisestone/owl/repository/IssueRelationRepository.java
New file
@@ -0,0 +1,10 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.IssueRelation;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface IssueRelationRepository extends JpaRepository<IssueRelation, Long> {
    List<IssueRelation> findAllByIssueId(Long issueId);
}
src/main/java/kr/wisestone/owl/repository/IssueRepository.java
New file
@@ -0,0 +1,7 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.Issue;
import org.springframework.data.jpa.repository.JpaRepository;
public interface IssueRepository extends JpaRepository<Issue, Long> {
}
src/main/java/kr/wisestone/owl/repository/IssueReservationRepository.java
New file
@@ -0,0 +1,10 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.IssueReservation;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface IssueReservationRepository extends JpaRepository<IssueReservation, Long> {
    List<IssueReservation> findByIssueReservationTypeNotNull();
}
src/main/java/kr/wisestone/owl/repository/IssueRiskRepository.java
New file
@@ -0,0 +1,7 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.IssueRisk;
import org.springframework.data.jpa.repository.JpaRepository;
public interface IssueRiskRepository extends JpaRepository<IssueRisk, Long> {
}
src/main/java/kr/wisestone/owl/repository/IssueSearchRepository.java
New file
@@ -0,0 +1,8 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.IssueSearch;
import org.springframework.data.jpa.repository.JpaRepository;
public interface IssueSearchRepository extends JpaRepository<IssueSearch, Long> {
    IssueSearch findByUserIdAndWorkspaceId(Long userId, Long workspaceId);
}
src/main/java/kr/wisestone/owl/repository/IssueStatusRepository.java
New file
@@ -0,0 +1,18 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.IssueStatus;
import kr.wisestone.owl.domain.enumType.IssueStatusType;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface IssueStatusRepository extends JpaRepository<IssueStatus, Long> {
    List<IssueStatus> findByWorkspaceId(@Param("workspaceId") Long workspaceId);
    IssueStatus findByNameAndWorkspaceId(@Param("name") String name, @Param("workspaceId") Long workspaceId);
    IssueStatus findByNameAndWorkspaceIdAndIdNot(@Param("name") String name, @Param("workspaceId") Long workspaceId, @Param("id") Long id);
    IssueStatus findByWorkspaceIdAndIssueStatusType(@Param("workspaceId") Long workspaceId, @Param("issueStatusType") IssueStatusType issueStatusType);
}
src/main/java/kr/wisestone/owl/repository/IssueTableConfigRepository.java
New file
@@ -0,0 +1,9 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.IssueTableConfig;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
public interface IssueTableConfigRepository extends JpaRepository<IssueTableConfig, Long> {
    IssueTableConfig findByUserIdAndWorkspaceId(@Param("userId") Long userId, @Param("workspaceId") Long workspaceId);
}
src/main/java/kr/wisestone/owl/repository/IssueTypeCustomFieldRepository.java
New file
@@ -0,0 +1,15 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.IssueTypeCustomField;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface IssueTypeCustomFieldRepository extends JpaRepository<IssueTypeCustomField, Long> {
    List<IssueTypeCustomField> findByProjectIdAndIssueTypeId(@Param("projectId") Long projectId, @Param("issueTypeId") Long issueTypeId);
    List<IssueTypeCustomField> findByProjectIdAndIssueTypeIdOrderByPosition(@Param("projectId") Long projectId, @Param("issueTypeId") Long issueTypeId);
    IssueTypeCustomField findByProjectIdAndIssueTypeIdAndCustomFieldId(@Param("projectId") Long projectId, @Param("issueTypeId") Long issueTypeId, @Param("customFieldId") Long customFieldId);
}
src/main/java/kr/wisestone/owl/repository/IssueTypeRepository.java
New file
@@ -0,0 +1,18 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.IssueType;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface IssueTypeRepository extends JpaRepository<IssueType, Long> {
    IssueType findByNameAndWorkspaceId(@Param("name") String name, @Param("workspaceId") Long workspaceId);
    IssueType findByNameAndWorkspaceIdAndIdNot(@Param("name") String name, @Param("workspaceId") Long workspaceId, @Param("id") Long id);
    IssueType findByName(@Param("name") String name);
    List<IssueType> findByWorkspaceId(@Param("workspaceId") Long workspaceId);
}
src/main/java/kr/wisestone/owl/repository/IssueUserRepository.java
New file
@@ -0,0 +1,9 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.IssueUser;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface IssueUserRepository extends JpaRepository<IssueUser, Long> {
    List<IssueUser> findByIssueId(Long issueId);
}
src/main/java/kr/wisestone/owl/repository/IssueVersionRepository.java
New file
@@ -0,0 +1,10 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.IssueVersion;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface IssueVersionRepository extends JpaRepository<IssueVersion, Long> {
    List<IssueVersion> findByIssueId(@Param("issueId") Long issueId);
}
src/main/java/kr/wisestone/owl/repository/NoticeRepository.java
New file
@@ -0,0 +1,7 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.Notice;
import org.springframework.data.jpa.repository.JpaRepository;
public interface NoticeRepository extends JpaRepository<Notice, Long> {
}
src/main/java/kr/wisestone/owl/repository/PaymentHistoryRepository.java
New file
@@ -0,0 +1,11 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.PaymentHistory;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface PaymentHistoryRepository extends JpaRepository<PaymentHistory, Long> {
    List<PaymentHistory> findByWorkspaceId(@Param("workspaceId") Long workspaceId);
}
src/main/java/kr/wisestone/owl/repository/PaymentRepository.java
New file
@@ -0,0 +1,7 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.Payment;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PaymentRepository extends JpaRepository<Payment, Long> {
}
src/main/java/kr/wisestone/owl/repository/PermissionRepository.java
New file
@@ -0,0 +1,15 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.Permission;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface PermissionRepository extends JpaRepository<Permission, Long> {
    @Query(name="Permission.findByUserId")
    List<Permission> findByUserId(@Param("userId") Long userId);
    List<Permission> findByRoleType(@Param("roleType") String roleType);
}
src/main/java/kr/wisestone/owl/repository/PriorityRepository.java
New file
@@ -0,0 +1,12 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.Priority;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface PriorityRepository extends JpaRepository<Priority, Long> {
    List<Priority> findByWorkspaceIdOrderByPosition(@Param("workspaceId") Long workspaceId);
    List<Priority> findByWorkspaceId(@Param("workspaceId") Long workspaceId);
}
src/main/java/kr/wisestone/owl/repository/ProjectClosureRepository.java
New file
@@ -0,0 +1,14 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.Priority;
import kr.wisestone.owl.domain.ProjectClosure;
import kr.wisestone.owl.domain.ProjectRole;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface ProjectClosureRepository extends JpaRepository<ProjectClosure, Long> {
    ProjectClosure findByProjectId(@Param("project_Id") Long projectId);
    List<ProjectClosure> findByParentProjectId(@Param("parent_Project_Id") Long parentProjectId);
}
src/main/java/kr/wisestone/owl/repository/ProjectRepository.java
New file
@@ -0,0 +1,19 @@
package kr.wisestone.owl.repository;
import com.amazonaws.services.route53.model.transform.ListGeoLocationsResultStaxUnmarshaller;
import kr.wisestone.owl.domain.Project;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface ProjectRepository extends JpaRepository<Project, Long> {
    Project findByProjectKeyAndWorkspaceId(@Param("projectKey") String projectKey, @Param("workspaceId") Long workspaceId);
    Project findByNameAndWorkspaceId(@Param("name") String name, @Param("workspaceId") Long workspaceId);
    Project findByNameAndWorkspaceIdAndIdNot(@Param("name") String name, @Param("workspaceId") Long workspaceId, @Param("id") Long id);
    List<Project> findByWorkspaceId(@Param("workspaceId") Long workspaceId);
    Project findByWorkspaceIdAndDefaultYn(@Param("workspaceId") Long workspaceId, @Param("defaultYn") Boolean defaultYn);
}
src/main/java/kr/wisestone/owl/repository/ProjectRolePermissionRepository.java
New file
@@ -0,0 +1,7 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.ProjectRolePermission;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProjectRolePermissionRepository extends JpaRepository<ProjectRolePermission, Long> {
}
src/main/java/kr/wisestone/owl/repository/ProjectRoleRepository.java
New file
@@ -0,0 +1,15 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.ProjectRole;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface ProjectRoleRepository extends JpaRepository<ProjectRole, Long> {
    ProjectRole findByProjectId(@Param("projectId") Long projectId);
    ProjectRole findByProjectIdAndRoleType(@Param("projectId") Long projectId, @Param("roleType") String roleType);
    @Query(name="ProjectRole.findByUserIdAndProjectId")
    ProjectRole findByUserIdAndProjectId(@Param("userId") Long userId, @Param("projectId") Long projectId);
}
src/main/java/kr/wisestone/owl/repository/ProjectRoleUserRepository.java
New file
@@ -0,0 +1,14 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.ProjectRoleUser;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface ProjectRoleUserRepository extends JpaRepository<ProjectRoleUser, Long> {
    List<ProjectRoleUser> findByProjectRoleId(@Param("projectRoleId") Long projectRoleId);
    ProjectRoleUser findByProjectRoleIdAndUserId(@Param("projectRoleId") Long projectRoleId, @Param("userId") Long userId);
}
src/main/java/kr/wisestone/owl/repository/QnaRepository.java
New file
@@ -0,0 +1,7 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.Qna;
import org.springframework.data.jpa.repository.JpaRepository;
public interface QnaRepository extends JpaRepository<Qna, Long> {
}
src/main/java/kr/wisestone/owl/repository/ReservationDisableUserRepository.java
New file
@@ -0,0 +1,7 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.ReservationDisableUser;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ReservationDisableUserRepository extends JpaRepository<ReservationDisableUser, Long> {
}
src/main/java/kr/wisestone/owl/repository/SeverityRepository.java
New file
@@ -0,0 +1,12 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.Severity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface SeverityRepository extends JpaRepository<Severity, Long> {
    List<Severity> findByWorkspaceIdOrderByPosition(@Param("workspaceId") Long workspaceId);
    List<Severity> findByWorkspaceId(@Param("workspaceId") Long workspaceId);
}
src/main/java/kr/wisestone/owl/repository/SystemEmailRepository.java
New file
@@ -0,0 +1,11 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.SystemEmail;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface SystemEmailRepository extends JpaRepository<SystemEmail, Long> {
    List<SystemEmail> findBySendAddressAndSendYn(@Param("sendAddress") String sendAddress, @Param("sendYn") Boolean sendYn);
}
src/main/java/kr/wisestone/owl/repository/SystemRolePermissionRepository.java
New file
@@ -0,0 +1,9 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.SystemRolePermission;
import org.springframework.data.jpa.repository.JpaRepository;
public interface SystemRolePermissionRepository extends JpaRepository<SystemRolePermission, Long> {
}
src/main/java/kr/wisestone/owl/repository/SystemRoleRepository.java
New file
@@ -0,0 +1,10 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.SystemRole;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
public interface SystemRoleRepository extends JpaRepository<SystemRole, Long> {
    SystemRole findByRoleType(@Param("roleType") String roleType);
}
src/main/java/kr/wisestone/owl/repository/SystemRoleUserRepository.java
New file
@@ -0,0 +1,8 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.SystemRoleUser;
import org.springframework.data.jpa.repository.JpaRepository;
public interface SystemRoleUserRepository extends JpaRepository<SystemRoleUser, Long> {
}
src/main/java/kr/wisestone/owl/repository/UserHistoryRepository.java
New file
@@ -0,0 +1,8 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.SystemRoleUser;
import kr.wisestone.owl.domain.UserHistory;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserHistoryRepository extends JpaRepository<UserHistory, Long> {
}
src/main/java/kr/wisestone/owl/repository/UserInviteProjectRepository.java
New file
@@ -0,0 +1,10 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.UserInviteProject;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface UserInviteProjectRepository extends JpaRepository<UserInviteProject, Long> {
    List<UserInviteProject> findByUserInviteIdAndProjectId(@Param("userInviteId") Long userInviteId, @Param("projectId") Long projectId);
}
src/main/java/kr/wisestone/owl/repository/UserInviteRepository.java
New file
@@ -0,0 +1,15 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.UserInvite;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface UserInviteRepository extends JpaRepository<UserInvite, Long> {
    List<UserInvite> findByEmailAndStatus(@Param("email") String email, @Param("status") String status);
    UserInvite findByEmailAndWorkspaceId(@Param("email") String email, @Param("workspaceId") Long workspaceId);
    List<UserInvite> findByWorkspaceIdAndEmailIn(@Param("workspaceId") Long workspaceId, @Param("emails") List<String> emails);
}
src/main/java/kr/wisestone/owl/repository/UserRepository.java
New file
@@ -0,0 +1,32 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.vo.UserVo;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Date;
import java.util.List;
import java.util.Map;
public interface UserRepository extends JpaRepository<User, Long> {
    User findByAccount(String account);
    User findByIdNotAndAccount(@Param("id") Long id, @Param("account") String account);
    List<User> findByStatus(@Param("status") String status);
    @Query(name = "User.findByWorkspaceIdAndManagerYn")
    User findByWorkspaceIdAndManagerYn(@Param("workspaceId") Long workspaceId, @Param("managerYn") Boolean managerYn);
    List<User> findByIdIn(@Param("userIds") List<Long> userIds);
    @Query(name = "User.findJoinDay")
    List<User> findJoinDay(@Param("todayFrom") Date todayFrom, @Param("todayTo") Date todayTo);
    @Query(name = "User.findAdmin")
    List<User> findAdmin();
}
src/main/java/kr/wisestone/owl/repository/UserWithDrawRepository.java
New file
@@ -0,0 +1,11 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.UserWithDraw;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
public interface UserWithDrawRepository extends JpaRepository<UserWithDraw, Long> {
    UserWithDraw findByAccount(@Param("account") String account);
}
src/main/java/kr/wisestone/owl/repository/UserWorkspaceRepository.java
New file
@@ -0,0 +1,27 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.UserWorkspace;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface UserWorkspaceRepository extends JpaRepository<UserWorkspace, Long> {
    UserWorkspace findByUserIdAndWorkspaceId(@Param("userId") Long userId, @Param("workspaceId") Long workspaceId);
    List<UserWorkspace> findByWorkspaceIdAndUseYn(@Param("workspaceId") Long workspaceId, @Param("useYn") Boolean useYn);
    @Query(name = "UserWorkspace.maxDisablePosition")
    Long maxDisablePosition(@Param("workspaceId") Long workspaceId);
    List<UserWorkspace> findByWorkspaceIdAndManagerYn(@Param("workspaceId") Long workspaceId, @Param("managerYn") Boolean managerYn);
    UserWorkspace findByUserIdAndManagerYn(@Param("userId") Long userId, @Param("managerYn") Boolean managerYn);
    List<UserWorkspace> findByWorkspaceId(@Param("workspaceId") Long workspaceId);
    List<UserWorkspace> findByWorkspaceIdAndUserIdIn(@Param("workspaceId") Long workspaceId, @Param("userIds") List<Long> userIds);
}
src/main/java/kr/wisestone/owl/repository/WorkflowRepository.java
New file
@@ -0,0 +1,21 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.Workflow;
import kr.wisestone.owl.domain.enumType.ProjectType;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface WorkflowRepository extends JpaRepository<Workflow, Long> {
    Workflow findByWorkspaceIdAndProjectType(@Param("workspaceId") Long workspaceId, @Param("projectType") ProjectType projectType);
    Workflow findByNameAndWorkspaceId(@Param("name") String name, @Param("workspaceId") Long workspaceId);
    Workflow findByNameAndWorkspaceIdAndIdNot(@Param("name") String name, @Param("workspaceId") Long workspaceId, @Param("id") Long id);
    List<Workflow> findByWorkspaceId(@Param("workspaceId") Long workspaceId);
}
src/main/java/kr/wisestone/owl/repository/WorkflowStatusRepository.java
New file
@@ -0,0 +1,22 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.WorkflowStatus;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
/**
 * Created by wisestone on 2018-01-03.
 */
public interface WorkflowStatusRepository extends JpaRepository<WorkflowStatus, Long> {
    List<WorkflowStatus> findByProjectId(@Param("projectId") Long projectId);
//
//    WorkflowStatus findByIdNotAndNameAndProjectId(@Param("id") Long id, @Param("name") String name, @Param("projectId") Long projectId);
//
//    WorkflowStatus findByNameAndProjectId(@Param("name") String name, @Param("projectId") Long projectId);
//
//    @Query(name="WorkflowStatus.findMaxPosition")
//    Long findMaxPosition(@Param("projectId") Long projectId);
}
src/main/java/kr/wisestone/owl/repository/WorkflowTransitionRepository.java
New file
@@ -0,0 +1,15 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.WorkflowTransition;
import kr.wisestone.owl.vo.WorkflowTransitionVo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface WorkflowTransitionRepository extends JpaRepository<WorkflowTransition, Long> {
    List<WorkflowTransition> findByWorkflowId(@Param("workflowId") Long workflowId);
    @Query(name="WorkflowTransition.findBySourceIssueStatusIdAndWorkflowId")
    List<WorkflowTransitionVo> findBySourceIssueStatusIdAndWorkflowId(@Param("sourceIssueStatusId") Long sourceIssueStatusId, @Param("workflowId") Long workflowId);
}
src/main/java/kr/wisestone/owl/repository/WorkspaceRepository.java
New file
@@ -0,0 +1,19 @@
package kr.wisestone.owl.repository;
import kr.wisestone.owl.domain.Workspace;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Date;
import java.util.List;
public interface WorkspaceRepository extends JpaRepository<Workspace, Long> {
    @Query(name="Workspace.findSubscribeImmediateExpireDate")
    List<Workspace> findSubscribeImmediateExpireDate(@Param("todayFrom") Date todayFrom, @Param("todayTo") Date todayTo);
    @Query(name="Workspace.findExpireDate")
    List<Workspace> findExpireDate(@Param("todayFrom") Date todayFrom, @Param("todayTo") Date todayTo);
    @Query(name = "Workspace.findByUserId")
    List<Workspace> findByUserId(@Param("userId") Long userId);
}
src/main/java/kr/wisestone/owl/scheduler/Scheduler.java
New file
@@ -0,0 +1,98 @@
package kr.wisestone.owl.scheduler;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.service.*;
import kr.wisestone.owl.config.websocket.WebSocketService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class Scheduler {
    /*private static final Logger log = LoggerFactory.getLogger(Scheduler.class);
    @Autowired
    private PaymentService paymentService;
    @Autowired
    private WorkspaceService workspaceService;
    @Autowired
    private SystemEmailService systemEmailService;
    @Autowired
    private AttachedFileService attachedFileService;
    @Autowired
    private UserService userService;
    @Autowired
    private IssueService issueService;
    @Autowired
    private WebSocketService webSocketService;
    //  OWL ITS 관련자들에게 회원 가입 정보를 전달한다.
    @Scheduled(cron = "0 0 17 * * *")
    public void sendOwlManager() {
        //  OWL ITS 관련자들에게 회원 가입 정보를 전달한다.
        this.userService.sendUserJoinStatisticsEmail();
        //  OWL ITS 관련자들에게 시스템 현황 정보를 전달한다.
        this.userService.sendTotalStatisticsEmail();
    }
    @Scheduled(cron = "0 50 23 * * *")    //  매 시간 (초, 분, 시, 일, 월, 년)
    public void updateExchangeRatePayment() {
        //  변경된 환율 정보를 결제 금액에 업데이트한다.
        this.paymentService.updateExchangeRatePayment();
    }
    //   자동 결재 - 어제가 만료일인 업무 공간을 찾아서 결제한다. 새벽 1시에 실행
    @Scheduled(cron = "0 0 01 * * *")    //  매 시간 (초, 분, 시, 일, 월, 년)
    public void subscribePayment() {
        List<Workspace> workspaces = this.workspaceService.findSubscribeImmediateExpireDate();
        for (Workspace workspace : workspaces) {
            this.paymentService.subscribeImmediate(workspace);
        }
    }
    //  이슈와 연결되지 않은 첨부파일 삭제 - 이슈 생성, 수정에서 에디트 창에 첨부했다가 저장하지 않은 파일들... 새벽 1시 30분에 실행
    @Scheduled(cron = "0 30 01 * * *")
    public void deleteAttachedFileNotIdAndReservationIssue() {
        //  이슈와 연결되지 않은 첨부파일 삭제
        this.attachedFileService.deleteAttachedFileNotId();
        //  이슈 예약 발생한 항목을 찾아 이슈를 다시 생성 상태로 변경한다.
        this.issueService.reservationIssue();
    }
    //  업무 공간 자동 초기화 - 어제가 만료일인 업무 공간을 찾아서 업무 공간 사용자, 사용 용량을 초기화한다. 새벽 2시에 실행
    @Scheduled(cron = "0 0 02 * * *")
    public void expireWorkspace() {
        //  사용 기간이 만료된 업무 공간을 찾아 용량, 최대 사용자, 서비스 유형을 변경한다.
        this.workspaceService.expireWorkspace();
        //  업무 공간 만료 예정 안내
        this.workspaceService.expireAlarmWorkspace();
    }
    //  이메일 예약 발송 - 사용자가 설정한 알림 시간에 시스템에서 일어난 이벤트를 이메일로 발송한다. - 매시간 30분에 실행
    @Scheduled(cron = "0 0/30 * * * *")
    public void smartEmailSystem() {
        //  예약된 이메일 발송
        this.systemEmailService.reservationSendEmail();
    }
    //  15초 마다 접속자 확인
    @Scheduled(fixedDelay = 15000)
    public void pingUsers() {
        //  접속 사용자를 확인한다.
        this.webSocketService.checkActiveUser();
    }*/
}
src/main/java/kr/wisestone/owl/search/ElasticSearch.java
New file
@@ -0,0 +1,50 @@
package kr.wisestone.owl.search;
import kr.wisestone.owl.constant.ElasticSearchConstants;
import kr.wisestone.owl.util.ElasticSearchUtil;
import kr.wisestone.owl.vo.UserVo;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
@Component
public class ElasticSearch {
    private static final Logger log = LoggerFactory.getLogger(ElasticSearch.class);
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    /*//  사용자 시스템 사용 기록을 저장한다.
    public void updateUserActiveHistory(HttpServletRequest httpServletRequest, UserVo userVo) {
        IndexRequest request = new IndexRequest(ElasticSearchConstants.USER_SESSION_HISTORY_INDEX);
        request.source(ElasticSearchUtil.makeUserActiveHistoryDocument(httpServletRequest, userVo), XContentType.JSON);
        request.timeout("5s");
        request.setRefreshPolicy(WriteRequest.RefreshPolicy.WAIT_UNTIL);
        try {
            this.restHighLevelClient.indexAsync(request, RequestOptions.DEFAULT, new ActionListener<IndexResponse>() {
                @Override
                public void onResponse(IndexResponse indexResponse) {
                    log.debug("사용자 시스템 사용 기록 인덱스 업데이트 성공");
                }
                @Override
                public void onFailure(Exception ex) {
                    log.error("사용자 시스템 사용 기록 인덱스 업데이트 오류 : " + ex.getMessage());
                }
            });
        } catch (Exception ex) {
            log.error("사용자 시스템 사용 기록 인덱스 업데이트 오류 : " + ex.getMessage());
        }
    }*/
}
src/main/java/kr/wisestone/owl/service/AbstractService.java
New file
@@ -0,0 +1,19 @@
package kr.wisestone.owl.service;
import org.springframework.data.jpa.repository.JpaRepository;
import java.io.Serializable;
import java.util.List;
public interface AbstractService<T, ID extends Serializable, R extends JpaRepository<T, ID>> {
    long count();
    void clear();
    void detach(Object entity);
    void bulkInsert(List<T> entities);
    T findOne(ID id);
    List<T> findAll(List<ID> ids);
}
src/main/java/kr/wisestone/owl/service/AttachedFileService.java
New file
@@ -0,0 +1,45 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.AttachedFile;
import kr.wisestone.owl.domain.Issue;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.vo.AttachedFileVo;
import kr.wisestone.owl.web.condition.AttachedFileCondition;
import kr.wisestone.owl.web.form.IssueForm;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.ui.Model;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
import java.util.Map;
public interface AttachedFileService extends AbstractService<AttachedFile, Long, JpaRepository<AttachedFile, Long>> {
    void addAttachedFile(List<Map<String, Object>> convertFileMaps, Issue issue, String userAccount);
    List<AttachedFile> addAttachedFile(List<MultipartFile> multipartFiles, Map<String, Object> content);
    List<AttachedFileVo> findAttachedFile(Map<String, Object> resJsonData, AttachedFileCondition condition);
    List<AttachedFile> findByIssueId(Long issueId);
    void connectIssueIdAttachedFile(Issue issue, IssueForm issueForm);
    AttachedFile getAttachedFile(Long attachedFileId);
    void removeAttachedFiles(List<Long> removeIds);
    void deleteWorkspaceCascadeAttachedFile(Workspace workspace);
    void deleteIssueCascadeAttachedFile(List<Long> issueIds, Workspace workspace);
    Long findUseStorage(Workspace workspace);
    ModelAndView checkUseWorkspaceTraffic(Long id, Model model);
    void deleteAttachedFileNotId();
    String uploadFile(Map<String, Object> convertFileMap, String awsUploadFolder, String userAccount, int totalFileCount, int uploadFileCount);
    void removeFile(String key, Long workspaceId);
}
src/main/java/kr/wisestone/owl/service/CustomFieldService.java
New file
@@ -0,0 +1,35 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.CustomField;
import kr.wisestone.owl.vo.CustomFieldVo;
import kr.wisestone.owl.web.condition.CustomFieldCondition;
import kr.wisestone.owl.web.form.CustomFieldForm;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
public interface CustomFieldService extends AbstractService<CustomField, Long, JpaRepository<CustomField, Long>>{
    CustomField addCustomField(CustomFieldForm customFieldForm);
    List<CustomFieldVo> findCustomField(Map<String, Object> resJsonData,
                                    CustomFieldCondition condition, Pageable pageable);
    void detailCustomField(Map<String, Object> resJsonData, CustomFieldCondition customFieldCondition);
    CustomField modifyCustomField(CustomFieldForm customFieldForm);
    CustomField getCustomField(Long id);
    void removeCustomFields(CustomFieldForm customFieldForm);
    CustomField findByName(String name);
    ModelAndView downloadExcel(HttpServletRequest request, Model model);
    List<CustomField> findByWorkspaceId();
}
src/main/java/kr/wisestone/owl/service/CustomFieldValueService.java
New file
@@ -0,0 +1,13 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.CustomField;
import kr.wisestone.owl.domain.CustomFieldValue;
import kr.wisestone.owl.domain.enumType.CustomFieldType;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface CustomFieldValueService extends AbstractService<CustomFieldValue, Long, JpaRepository<CustomFieldValue, Long>>{
    void addCustomFieldValues(CustomField customField, List<String> values, CustomFieldType oldCustomFieldType);
}
src/main/java/kr/wisestone/owl/service/EventService.java
New file
@@ -0,0 +1,25 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Event;
import kr.wisestone.owl.vo.EventVo;
import kr.wisestone.owl.web.condition.EventCondition;
import kr.wisestone.owl.web.form.EventForm;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Map;
public interface EventService extends AbstractService<Event, Long, JpaRepository<Event, Long>> {
    Event addEvent(EventForm eventForm);
    List<EventVo> findEvent(Map<String, Object> resJsonData,
                            EventCondition eventCondition, Pageable pageable);
    Event getEvent(Long id);
    Event modifyEvent(EventForm eventForm);
    Event activeEvent(EventForm eventForm);
    void detailEvent(Map<String, Object> resJsonData, EventCondition eventCondition);
}
src/main/java/kr/wisestone/owl/service/FaqService.java
New file
@@ -0,0 +1,27 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Faq;
import kr.wisestone.owl.domain.Guide;
import kr.wisestone.owl.vo.FaqVo;
import kr.wisestone.owl.web.condition.FaqCondition;
import kr.wisestone.owl.web.form.FaqForm;
import kr.wisestone.owl.web.form.GuideForm;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Map;
public interface FaqService extends AbstractService<Faq, Long, JpaRepository<Faq, Long>> {
    Faq addFaq(FaqForm faqForm);
    List<FaqVo> findFaq(Map<String, Object> resJsonData,
                              FaqCondition faqCondition, Pageable pageable);
    Faq getFaq(Long id);
    Faq modifyFaq(FaqForm faqForm);
    Faq activeFaq(FaqForm faqForm);
    void detailFaq(Map<String, Object> resJsonData, FaqCondition faqCondition);
}
src/main/java/kr/wisestone/owl/service/GanttService.java
New file
@@ -0,0 +1,36 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Issue;
import kr.wisestone.owl.domain.IssueType;
import kr.wisestone.owl.domain.Workflow;
import kr.wisestone.owl.vo.IssueVo;
import kr.wisestone.owl.web.condition.IssueCondition;
import kr.wisestone.owl.web.condition.ProjectCondition;
import kr.wisestone.owl.web.form.IssueForm;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.ui.Model;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
public interface GanttService extends AbstractService<Issue, Long, JpaRepository<Issue, Long>>{
    void addIssueVersion(Long id);
    Issue addIssue(IssueForm issueForm, List<MultipartFile> files);
    List<IssueVo> findIssue(Map<String, Object> resJsonData,
                            IssueCondition condition, Pageable pageable);
    List<IssueVo> findIssue(Map<String, Object> resJsonData,
                            ProjectCondition projectCondition, Pageable pageable);
    void detailIssue(Map<String, Object> resJsonData, IssueCondition issueCondition);
    Issue modifyIssue(IssueForm issueForm, List<MultipartFile> files);
    void removeIssues(IssueForm issueForm);
}
src/main/java/kr/wisestone/owl/service/GuideService.java
New file
@@ -0,0 +1,25 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Guide;
import kr.wisestone.owl.vo.GuideVo;
import kr.wisestone.owl.web.condition.GuideCondition;
import kr.wisestone.owl.web.form.GuideForm;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Map;
public interface GuideService extends AbstractService<Guide, Long, JpaRepository<Guide, Long>> {
    Guide addGuide(GuideForm guideForm);
    List<GuideVo> findGuide(Map<String, Object> resJsonData,
                             GuideCondition guideCondition, Pageable pageable);
    Guide getGuide(Long id);
    Guide modifyGuide(GuideForm guideForm);
    Guide activeGuide(GuideForm guideForm);
    void detailGuide(Map<String, Object> resJsonData, GuideCondition guideCondition);
}
src/main/java/kr/wisestone/owl/service/IssueCommentService.java
New file
@@ -0,0 +1,20 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.IssueComment;
import kr.wisestone.owl.vo.IssueCommentVo;
import kr.wisestone.owl.web.form.IssueCommentForm;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface IssueCommentService extends AbstractService<IssueComment, Long, JpaRepository<IssueComment, Long>> {
    IssueComment addIssueComment(IssueCommentForm issueCommentForm);
    void removeIssueComments(IssueCommentForm issueCommentForm);
    IssueComment getIssueComment(Long id );
    List<IssueCommentVo> findIssueComment(Long issueId);
}
src/main/java/kr/wisestone/owl/service/IssueCustomFieldValueService.java
New file
@@ -0,0 +1,33 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.CustomField;
import kr.wisestone.owl.domain.Issue;
import kr.wisestone.owl.domain.IssueCustomFieldValue;
import kr.wisestone.owl.domain.enumType.CustomFieldType;
import kr.wisestone.owl.vo.IssueCustomFieldValueVo;
import kr.wisestone.owl.web.condition.IssueCondition;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Map;
import java.util.Set;
public interface IssueCustomFieldValueService extends AbstractService<IssueCustomFieldValue, Long, JpaRepository<IssueCustomFieldValue, Long>>{
    void modifyIssueCustomFieldValue(Issue issue, List<Map<String, Object>> issueCustomFields);
    void getCustomFieldAndIssueTypeCustomField(Map<String, Object> map, Issue issue, Map<String, Object> result);
    List<IssueCustomFieldValueVo> findByIssueId(Long issueId);
    void checkExistIssueCustomFieldValue(CustomField customField, List<String> values, CustomFieldType oldCustomFieldType);
    List<IssueCustomFieldValue> findByCustomFieldId(CustomField customField);
    void removeIssueCustomFieldValue(Long issueTypeCustomFieldId);
    boolean find(IssueCondition condition, Set<String> issueIds);
    List<Map<String, Object>> findInIssueIds(IssueCondition issueCondition);
    void removeIssueCustomFieldValuesByCustomFieldId(CustomField customField);
}
src/main/java/kr/wisestone/owl/service/IssueHistoryService.java
New file
@@ -0,0 +1,56 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.*;
import kr.wisestone.owl.domain.enumType.IssueHistoryType;
import kr.wisestone.owl.service.impl.IssueHistoryServiceImpl;
import kr.wisestone.owl.vo.IssueHistoryVo;
import kr.wisestone.owl.web.condition.IssueHistoryCondition;
import kr.wisestone.owl.web.form.IssueForm;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.Map;
public interface IssueHistoryService extends AbstractService<IssueHistory, Long, JpaRepository<IssueHistory, Long>>{
    void addIssueHistory(Issue issue, IssueHistoryType issueHistoryType, String issueChangeDescription);
    void makeDescription(StringBuilder description, IssueHistoryType issueHistoryType, String issueChangeDescription);
    void findIssueHistory(Map<String, Object> resJsonData, IssueHistoryCondition issueHistoryCondition);
    List<IssueHistoryVo> findIssueHistory(Long issueId);
    StringBuilder detectIssueChange(Issue issue, IssueForm issueForm, Project project, IssueStatus issueStatus, IssueType issueType, Priority priority, Severity severity, List<MultipartFile> files);
    void detectProject(Issue issue, IssueForm issueForm, StringBuilder description, Project project);
    void detectIssueSeverity(Issue issue, IssueForm issueForm, StringBuilder description, Severity severity);
    void detectIssuePriority(Issue issue, IssueForm issueForm, StringBuilder description, Priority priority);
    void detectIssueStatus(Issue issue, IssueForm issueForm, StringBuilder description, IssueStatus issueStatus);
    void detectReservationIssueStatus(Issue issue, StringBuilder description, IssueStatus issueStatus);
    void recordRemoveWorkflowToIssueStatus(String oldIssueStatusName, String newIssueStatusName, StringBuilder description);
    void detectIssueType(Issue issue, IssueForm issueForm, StringBuilder description, IssueType issueType);
    void detectIssuePeriod(Issue issue, IssueForm issueForm, StringBuilder description);
    void detectIssueManager(Issue issue, IssueForm issueForm, StringBuilder description);
    void detectAttachedFile(IssueForm issueForm, StringBuilder description, List<MultipartFile> files);
    void detectCustomField(Issue issue, IssueForm issueForm, StringBuilder description);
    void detectRelationIssue(IssueHistoryType type, IssueRelation issueRelation, StringBuilder description);
    void recodeRemoveCustomFieldOptionValue(CustomField customField, String oldValue, String newValue, StringBuilder description);
    void recodeChangeCustomFieldType(CustomField customField, String oldValue, StringBuilder description);
}
src/main/java/kr/wisestone/owl/service/IssueNumberGeneratorService.java
New file
@@ -0,0 +1,13 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.IssueNumberGenerator;
import kr.wisestone.owl.domain.Project;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Map;
public interface IssueNumberGeneratorService extends AbstractService<IssueNumberGenerator, Long, JpaRepository<IssueNumberGenerator, Long>>{
    Long generateIssueNumber(Project project);
    void updateIssueNumber(Map<Long, Long> issueNumberMaps);
}
src/main/java/kr/wisestone/owl/service/IssueRelationService.java
New file
@@ -0,0 +1,22 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.IssueRelation;
import kr.wisestone.owl.vo.IssueRelationVo;
import kr.wisestone.owl.vo.IssueVo;
import kr.wisestone.owl.web.condition.IssueCondition;
import kr.wisestone.owl.web.condition.IssueRelationCondition;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Map;
public interface IssueRelationService extends AbstractService<IssueRelation, Long, JpaRepository<IssueRelation, Long>>{
    void addRelationIssue(Map<String, Object> resJsonData, IssueRelationCondition condition);
    List<IssueVo> findRelationIssue(Map<String, Object> resJsonData,
                                    IssueRelationCondition condition, Pageable pageable);
    List<IssueVo> findRelationIssue(Long issueId);
    boolean removeRelationIssue(Map<String, Object> resJsonData, IssueRelationCondition condition);
}
src/main/java/kr/wisestone/owl/service/IssueReservationService.java
New file
@@ -0,0 +1,20 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.IssueReservation;
import kr.wisestone.owl.web.condition.IssueReservationCondition;
import kr.wisestone.owl.web.form.IssueReservationForm;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Map;
public interface IssueReservationService extends AbstractService<IssueReservation, Long, JpaRepository<IssueReservation, Long>>{
    void detailIssueReservation(Map<String, Object> resJsonData, IssueReservationCondition issueReservationCondition);
    IssueReservation getIssueReservation(Long id);
    void modifyIssueReservation(Map<String, Object> resJsonData, IssueReservationForm issueReservationForm);
    List<IssueReservation> findByIssueReservationTypeNotNull();
}
src/main/java/kr/wisestone/owl/service/IssueRiskService.java
New file
@@ -0,0 +1,13 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Issue;
import kr.wisestone.owl.domain.IssueRisk;
import kr.wisestone.owl.domain.Workspace;
import org.springframework.data.jpa.repository.JpaRepository;
public interface IssueRiskService extends AbstractService<IssueRisk, Long, JpaRepository<IssueRisk, Long>>{
    IssueRisk addIssueRisk(Issue issue, Workspace workspace);
    void modifyIssueRisk(Issue issue, Boolean changeIssueStatus, Boolean changeAssignee, Long issueStatusId);
}
src/main/java/kr/wisestone/owl/service/IssueSearchService.java
New file
@@ -0,0 +1,14 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.IssueSearch;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Map;
public interface IssueSearchService extends AbstractService<IssueSearch, Long, JpaRepository<IssueSearch, Long>>{
    IssueSearch addIssueSearch(Map<String, Object> params);
    IssueSearch findByUserIdAndWorkspaceId();
    void detailIssueSearch(Map<String, Object> resJsonData);
}
src/main/java/kr/wisestone/owl/service/IssueService.java
New file
@@ -0,0 +1,69 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Issue;
import kr.wisestone.owl.domain.IssueType;
import kr.wisestone.owl.domain.Workflow;
import kr.wisestone.owl.vo.IssueVo;
import kr.wisestone.owl.web.condition.IssueCondition;
import kr.wisestone.owl.web.condition.ProjectCondition;
import kr.wisestone.owl.web.form.IssueForm;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.ui.Model;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
public interface IssueService extends AbstractService<Issue, Long, JpaRepository<Issue, Long>>{
    void addIssueVersion(Long id);
    Issue addIssue(IssueForm issueForm, List<MultipartFile> files);
    List<IssueVo> findIssue(Map<String, Object> resJsonData,
                            IssueCondition condition, Pageable pageable);
    List<IssueVo> findChartIssue(Map<String, Object> resJsonData,
                            IssueCondition condition, Pageable pageable);
    List<IssueVo> findChartIssue(Map<String, Object> resJsonData,
                                 ProjectCondition condition, Pageable pageable);
    void detailIssue(Map<String, Object> resJsonData, IssueCondition issueCondition);
    Issue modifyIssue(IssueForm issueForm, List<MultipartFile> files);
    void removeIssues(IssueForm issueForm);
    void modifyIssueStatus(IssueForm issueForm);
    Issue getIssue(Long taskId);
    long countByIssueTypeId(Long issueTypeId);
    long countByIssueStatus(Long issueStatusId);
    void changeWorkflows(Workflow workflow, IssueType issueType);
    ModelAndView downloadExcel(HttpServletRequest request, Model model);
    void modifyMultiIssueStatus(IssueForm issueForm);
    void modifyIssueUser(IssueForm issueForm);
    ModelAndView downloadExcelTemplate(HttpServletRequest request, Model model);
    void importExcel(MultipartFile multipartFile) throws Exception;
    List<Long> findByProjectId(Long projectId);
    void setIssueDetail(IssueVo issueVo, Issue issue);
    void sendIssueEmail(IssueForm issueForm);
    void reservationIssue();
    Map<String, Object> findTask(IssueCondition taskCondition);
}
src/main/java/kr/wisestone/owl/service/IssueStatusService.java
New file
@@ -0,0 +1,51 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Issue;
import kr.wisestone.owl.domain.IssueStatus;
import kr.wisestone.owl.domain.Workflow;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.vo.IssueStatusVo;
import kr.wisestone.owl.web.condition.IssueStatusCondition;
import kr.wisestone.owl.web.form.IssueStatusForm;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
public interface IssueStatusService extends AbstractService<IssueStatus, Long, JpaRepository<IssueStatus, Long>>{
    List<IssueStatus> addDefaultIssueStatus(Workspace workspace);
    List<IssueStatus> findByWorkspaceId(Long workspaceId);
    IssueStatus addIssueStatus(IssueStatusForm issueStatusForm);
    List<IssueStatusVo> findIssueStatus(Map<String, Object> resJsonData,
                                        IssueStatusCondition issueStatusCondition, Pageable pageable);
    List<IssueStatusVo> findIssueStatus(Map<String, Object> resJsonData, IssueStatusCondition condition);
    void findNextIssueStatus(Map<String, Object> resJsonData, IssueStatusCondition condition);
    void detailIssueStatus(Map<String, Object> resJsonData, IssueStatusCondition issueStatusCondition);
    IssueStatus modifyIssueStatus(IssueStatusForm issueStatusForm);
    IssueStatus getIssueStatus(Long id);
    void removeIssueStatus(IssueStatusForm issueStatusForm);
    List<IssueStatusVo> findByWorkflowId(Long workflowId);
    IssueStatus findByIssueStatusTypeIsReady(Workflow workflow);
    void checkNextIssueStatus(Issue issue, IssueStatus nextIssueStatus);
    ModelAndView downloadExcel(HttpServletRequest request, Model model);
    void findNextMultiIssueStatus(Map<String, Object> resJsonData, IssueStatusCondition condition);
}
src/main/java/kr/wisestone/owl/service/IssueTableConfigService.java
New file
@@ -0,0 +1,14 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.IssueTableConfig;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Map;
public interface IssueTableConfigService extends AbstractService<IssueTableConfig, Long, JpaRepository<IssueTableConfig, Long>> {
    IssueTableConfig addIssueTableConfig(Map<String, Object> params);
    IssueTableConfig findByUserIdAndWorkspaceId();
    void detailIssueTableConfig(Map<String, Object> resJsonData);
}
src/main/java/kr/wisestone/owl/service/IssueTypeCustomFieldService.java
New file
@@ -0,0 +1,21 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.IssueTypeCustomField;
import kr.wisestone.owl.vo.IssueTypeCustomFieldVo;
import kr.wisestone.owl.web.condition.IssueTypeCustomFieldCondition;
import kr.wisestone.owl.web.form.IssueTypeCustomFieldForm;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Map;
public interface IssueTypeCustomFieldService extends AbstractService<IssueTypeCustomField, Long, JpaRepository<IssueTypeCustomField, Long>>{
    void modifyIssueTypeCustomFields(IssueTypeCustomFieldForm issueTypeCustomFieldForm);
    List<IssueTypeCustomFieldVo> findIssueTypeCustomField(Map<String, Object> resJsonData, IssueTypeCustomFieldCondition condition);
    List<IssueTypeCustomField> findByProjectIdAndIssueTypeId(Long projectId, Long issueTypeId);
    IssueTypeCustomField findByProjectIdAndIssueTypeIdAndCustomFieldId(Long projectId, Long issueTypeId, Long customFieldId);
}
src/main/java/kr/wisestone/owl/service/IssueTypeService.java
New file
@@ -0,0 +1,39 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.IssueType;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.domain.enumType.ProjectType;
import kr.wisestone.owl.vo.IssueTypeVo;
import kr.wisestone.owl.web.condition.IssueTypeCondition;
import kr.wisestone.owl.web.form.IssueTypeForm;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
public interface IssueTypeService extends AbstractService<IssueType, Long, JpaRepository<IssueType, Long>>{
    void addDefaultIssueType(Workspace workspace, List<ProjectType> projectTypes);
    IssueType addIssueType(IssueTypeForm issueTypeForm);
    List<IssueTypeVo> findIssueType(Map<String, Object> resJsonData,
                                    IssueTypeCondition condition, Pageable pageable);
    void detailIssueType(Map<String, Object> resJsonData, IssueTypeCondition issueTypeCondition);
    IssueType modifyIssueType(IssueTypeForm issueTypeForm);
    IssueType getIssueType(Long id);
    void removeIssueTypes(IssueTypeForm issueTypeForm);
    List<IssueType> findByWorkspaceId();
    ModelAndView downloadExcel(HttpServletRequest request, Model model);
}
src/main/java/kr/wisestone/owl/service/IssueUserService.java
New file
@@ -0,0 +1,20 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Issue;
import kr.wisestone.owl.domain.IssueUser;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.web.form.IssueForm;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Map;
public interface IssueUserService extends AbstractService<IssueUser, Long, JpaRepository<IssueUser, Long>>{
    void modifyIssueUser(Issue issue, Workspace workspace, List<Long> userIds);
    void insertIssueUser(List<Map<String, Long>> issueAssigneeMaps);
    void removeIssueUser(Long projectId, List<Long> excludeUserIds);
    List<IssueUser> find(Issue issue);
}
src/main/java/kr/wisestone/owl/service/IssueVersionService.java
New file
@@ -0,0 +1,16 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Issue;
import kr.wisestone.owl.domain.IssueVersion;
import kr.wisestone.owl.web.condition.IssueVersionCondition;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Map;
public interface IssueVersionService extends AbstractService<IssueVersion, Long, JpaRepository<IssueVersion, Long>>{
    void addIssueVersion(Issue issue);
    void find(Map<String, Object> resJsonData, IssueVersionCondition issueVersionCondition);
}
src/main/java/kr/wisestone/owl/service/ManageUserService.java
New file
@@ -0,0 +1,23 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.domain.UserWorkspace;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.vo.ManageUserVo;
import kr.wisestone.owl.vo.UserWorkspaceVo;
import kr.wisestone.owl.web.condition.UserWorkspaceCondition;
import kr.wisestone.owl.web.form.ManageUserForm;
import kr.wisestone.owl.web.form.UserWorkspaceForm;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Map;
public interface ManageUserService extends AbstractService<UserWorkspace, Long, JpaRepository<UserWorkspace, Long>> {
    List<ManageUserVo> findUserPermission(Map<String, Object> resJsonData,
                                         UserWorkspaceCondition condition, Pageable pageable);
    void modifyUserPermission(ManageUserForm manageUserForm);
}
src/main/java/kr/wisestone/owl/service/NoticeService.java
New file
@@ -0,0 +1,26 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Notice;
import kr.wisestone.owl.vo.NoticeVo;
import kr.wisestone.owl.web.condition.NoticeCondition;
import kr.wisestone.owl.web.form.NoticeForm;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Map;
public interface NoticeService extends AbstractService<Notice, Long, JpaRepository<Notice, Long>> {
    Notice addNotice(NoticeForm noticeForm);
    List<NoticeVo> findNotice(Map<String, Object> resJsonData,
                              NoticeCondition noticeCondition, Pageable pageable);
    Notice getNotice(Long id);
    Notice modifyNotice(NoticeForm noticeForm);
    void detailNotice(Map<String, Object> resJsonData, NoticeCondition noticeCondition);
    void sendNotice(NoticeForm noticeForm);
}
src/main/java/kr/wisestone/owl/service/PaymentHistoryService.java
New file
@@ -0,0 +1,15 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.PaymentHistory;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.service.impl.PaymentServiceImpl;
import kr.wisestone.owl.web.form.PaymentForm;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PaymentHistoryService extends AbstractService<PaymentHistory, Long, JpaRepository<PaymentHistory, Long>>{
    PaymentHistory addPaymentHistory(PaymentServiceImpl.RestClientResultObject resultObject, PaymentForm paymentForm, Workspace workspace);
    PaymentHistory cancelPaymentHistory(PaymentForm paymentForm, Workspace workspace, String reason);
    PaymentHistory findByWorkspaceLastPaymentHistory(Workspace workspace);
}
src/main/java/kr/wisestone/owl/service/PaymentService.java
New file
@@ -0,0 +1,29 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Payment;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.vo.PaymentVo;
import kr.wisestone.owl.web.form.PaymentForm;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Map;
public interface PaymentService extends AbstractService<Payment, Long, JpaRepository<Payment, Long>>{
    String getAccessToken();
    void immediateAddUser(PaymentForm paymentForm);
    void paymentOneTime(PaymentForm paymentForm);
    void cancelNextPayment(PaymentForm paymentForm);
    void detailPayment(Map<String, Object> resJsonData, PaymentForm paymentForm);
    PaymentVo modifyPayment(PaymentForm paymentForm);
    void subscribeImmediate(Workspace workspace);
    Payment getPayment(Long id);
    void updateExchangeRatePayment();
}
src/main/java/kr/wisestone/owl/service/PermissionService.java
New file
@@ -0,0 +1,13 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Permission;
import kr.wisestone.owl.vo.PermissionVo;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface PermissionService extends AbstractService<Permission, Long, JpaRepository<Permission, Long>>{
    List<PermissionVo> findByUserId();
    List<Permission> findByRoleType(String roleType);
}
src/main/java/kr/wisestone/owl/service/PriorityService.java
New file
@@ -0,0 +1,21 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Priority;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.vo.PriorityVo;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Map;
public interface PriorityService extends AbstractService<Priority, Long, JpaRepository<Priority, Long>>{
    List<Priority> findByWorkspaceIdOrderByPosition();
    Priority getPriority(Long id);
    List<PriorityVo> findPriority(Map<String, Object> resJsonData);
    void addDefaultPriority(Workspace workspace);
    List<Priority> findByWorkspaceId();
}
src/main/java/kr/wisestone/owl/service/ProjectClosureService.java
New file
@@ -0,0 +1,8 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.ProjectClosure;
import kr.wisestone.owl.domain.ProjectRole;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProjectClosureService extends AbstractService<ProjectClosure, Long, JpaRepository<ProjectClosure, Long>>{
}
src/main/java/kr/wisestone/owl/service/ProjectRolePermissionService.java
New file
@@ -0,0 +1,10 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.ProjectRole;
import kr.wisestone.owl.domain.ProjectRolePermission;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProjectRolePermissionService extends AbstractService<ProjectRolePermission, Long, JpaRepository<ProjectRolePermission, Long>>{
    void addDefaultProjectRoleAssociatedPermissions(ProjectRole projectRole, String roleType);
}
src/main/java/kr/wisestone/owl/service/ProjectRoleService.java
New file
@@ -0,0 +1,15 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Project;
import kr.wisestone.owl.domain.ProjectRole;
import kr.wisestone.owl.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface ProjectRoleService extends AbstractService<ProjectRole, Long, JpaRepository<ProjectRole, Long>>{
    void addDefaultProjectRole(Project project, List<User> managers, List<User> users);
    ProjectRole findByProjectIdAndRoleType(Long projectId, String roleType);
}
src/main/java/kr/wisestone/owl/service/ProjectRoleUserService.java
New file
@@ -0,0 +1,22 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Project;
import kr.wisestone.owl.domain.ProjectRoleUser;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.domain.Workspace;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Map;
public interface ProjectRoleUserService extends AbstractService<ProjectRoleUser, Long, JpaRepository<ProjectRoleUser, Long>>{
    List<ProjectRoleUser> findByProjectRoleId(Long projectRoleId);
    ProjectRoleUser findByProjectRoleIdAndUserId(Long projectRoleId, Long userId);
    void withDrawWorkspaceManagerModifyProjectRole(Workspace workspace, User user);
    List<Map<String, Object>> findProjectRoleUser(Map<String, Object> projectRoleUserMap);
    boolean checkProjectManager(Project project);
}
src/main/java/kr/wisestone/owl/service/ProjectService.java
New file
@@ -0,0 +1,53 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Project;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.vo.ProjectVo;
import kr.wisestone.owl.web.condition.ProjectCondition;
import kr.wisestone.owl.web.form.ProjectForm;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
public interface ProjectService extends AbstractService<Project, Long, JpaRepository<Project, Long>>{
    Project addDefaultProject(User user, Workspace workspace);
    Project addProject(ProjectForm projectForm);
    List<ProjectVo> findProject(Map<String, Object> resJsonData,
                                ProjectCondition condition, Pageable pageable);
    void detailProject(Map<String, Object> resJsonData, ProjectCondition projectCondition);
    Project findByProjectKey(String projectKey);
    Project modifyProject(ProjectForm projectForm);
    Project getProject(Long id);
    void removeProjects(ProjectForm projectForm);
    List<Project> findByWorkspaceId();
    List<Map<String, Object>> findByWorkspaceIdAndIncludeProject(List<String> statuses, String projectType);
    List<Map<String, Object>> findByWorkspaceIdAndIncludeProject(ProjectCondition projectCondition);
    List<Map<String, Object>> findByWorkspaceIdAndIncludeProjectAll(List<String> statuses, String projectType);
    List<Map<String, Object>> findByWorkspaceManagerAll();
    List<Map<String, Object>> findByWorkspaceIdAndIncludeProjectAll(ProjectCondition projectCondition);
    List<ProjectVo> findByIncludeProject(List<String> statuses, String projectType);
    ModelAndView downloadExcel(HttpServletRequest request, Model model);
    void findLastUseProject(Map<String, Object> resJsonData);
}
src/main/java/kr/wisestone/owl/service/QnaService.java
New file
@@ -0,0 +1,24 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Qna;
import kr.wisestone.owl.vo.QnaVo;
import kr.wisestone.owl.web.condition.QnaCondition;
import kr.wisestone.owl.web.form.QnaForm;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Map;
public interface QnaService extends AbstractService<Qna, Long, JpaRepository<Qna, Long>> {
    Qna addQna(QnaForm qnaForm);
    List<QnaVo> findQna(Map<String, Object> resJsonData,
                              QnaCondition qnaCondition, Pageable pageable);
    Qna getQna(Long id);
    Qna modifyQna(QnaForm qnaForm);
    void detailQna(Map<String, Object> resJsonData, QnaCondition qnaCondition);
}
src/main/java/kr/wisestone/owl/service/ReservationDisableUserService.java
New file
@@ -0,0 +1,14 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Payment;
import kr.wisestone.owl.domain.ReservationDisableUser;
import kr.wisestone.owl.web.form.PaymentForm;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Map;
public interface ReservationDisableUserService extends AbstractService<ReservationDisableUser, Long, JpaRepository<ReservationDisableUser, Long>>{
    void add(PaymentForm paymentForm, Payment payment);
    void findReservationDisableUser(Map<String, Object> params, Map<String, Object> resJsonData);
}
src/main/java/kr/wisestone/owl/service/SeverityService.java
New file
@@ -0,0 +1,22 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Severity;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.vo.SeverityVo;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Map;
public interface SeverityService extends AbstractService<Severity, Long, JpaRepository<Severity, Long>>{
    List<Severity> findByWorkspaceIdOrderByPosition();
    Severity getSeverity(Long id);
    List<SeverityVo> findSeverity(Map<String, Object> resJsonData);
    void addDefaultSeverity(Workspace workspace);
    List<Severity> findByWorkspaceId();
}
src/main/java/kr/wisestone/owl/service/SystemEmailService.java
New file
@@ -0,0 +1,26 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.SystemEmail;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.domain.enumType.EmailType;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Map;
public interface SystemEmailService extends AbstractService<SystemEmail, Long, JpaRepository<SystemEmail, Long>>{
    void directEmail(String[] sendUsers, EmailType emailType, Map<String, Object> content, String toUser);
    void sendEmail(String subject, String content, String[] to, String[] filePaths);
    List<String> notificationUserChange(List<User> totalUsers, List<User> targetUsers);
    void reservationEmail(String[] sendUsers, EmailType emailType, Map<String, Object> params);
    List<SystemEmail> findBySendAddressAndSendYn(String sendAddress);
    void reservationSendEmail();
    void information(Map<String, Object> params);
}
src/main/java/kr/wisestone/owl/service/SystemRoleService.java
New file
@@ -0,0 +1,8 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.SystemRole;
import org.springframework.data.jpa.repository.JpaRepository;
public interface SystemRoleService extends AbstractService<SystemRole, Long, JpaRepository<SystemRole, Long>>{
    SystemRole findByRoleType(String roleType);
}
src/main/java/kr/wisestone/owl/service/UserHistoryService.java
New file
@@ -0,0 +1,11 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.UserHistory;
import kr.wisestone.owl.web.condition.UserHistoryCondition;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Map;
public interface UserHistoryService extends AbstractService<UserHistory, Long, JpaRepository<UserHistory, Long>>{
    void addUserHistory(Map<String, Object> resJsonData, UserHistoryCondition userHistoryCondition);
}
src/main/java/kr/wisestone/owl/service/UserInviteProjectService.java
New file
@@ -0,0 +1,11 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.UserInvite;
import kr.wisestone.owl.domain.UserInviteProject;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface UserInviteProjectService extends AbstractService<UserInviteProject, Long, JpaRepository<UserInviteProject, Long>> {
    List<UserInviteProject> addUserInviteProject(List<Long> projectIds, UserInvite userInvite);
}
src/main/java/kr/wisestone/owl/service/UserInviteService.java
New file
@@ -0,0 +1,19 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.domain.UserInvite;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.web.form.UserInviteForm;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface UserInviteService extends AbstractService<UserInvite, Long, JpaRepository<UserInvite, Long>> {
    void inviteWorkspace(UserInviteForm userInviteForm);
    void checkInviteUser(User user);
    void deleteUserInvite(Long workspaceId, List<String> email);
    void includePrimaryWorkspace(User user, Workspace workspace);
}
src/main/java/kr/wisestone/owl/service/UserService.java
New file
@@ -0,0 +1,84 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Project;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.domain.enumType.SocialType;
import kr.wisestone.owl.vo.UserVo;
import kr.wisestone.owl.web.condition.UserCondition;
import kr.wisestone.owl.web.form.UserForm;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.ui.Model;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
public interface UserService extends AbstractService<User, Long, JpaRepository<User, Long>> {
    User findByAccount(String account);
    User addUser(UserForm userForm, MultipartFile profile);
    List<UserVo> findUser(Map<String, Object> resJsonData,
                          UserCondition condition, Pageable pageable);
    List<User> findAdmin();
    User getUser(Long userId);
    void detailUser(Map<String, Object> resJsonData, UserCondition userCondition);
    void modifyUser(UserForm userForm, MultipartFile profile);
    void modifyPassword(UserForm userForm);
    void returnEmailPassword(UserForm userForm);
    ModelAndView getOAuthToken(String code, String state, SocialType socialType, HttpServletRequest request);
    List<User> findByIdIn(List<Long> userIds);
    void updateLastDefaultWorkspace(Workspace workspace, User user);
    User findByWorkspaceIdAndManagerYn(Workspace workspace);
    void updateLastWorkspace(Map<String, Object> resJsonData, UserForm userForm);
    void updateLastProject(Map<String, Object> resJsonData, UserForm userForm);
    void updateLastMyWorkspace(User user);
    void findProjectMember(Map<String, Object> resJsonData, UserCondition userCondition);
    void withDrawUser();
    void autoLogin(String email, HttpServletRequest request);
    void updateUserSession();
    User getUserSession(Map<String, Object> resJsonData, HttpServletRequest httpServletRequest);
    List<String> findByReservationNotifyTime();
    List<Map<String, Object>> findProjectMember(Project project);
    void updateLanguage(String language);
    void sendUserJoinStatisticsEmail();
    void sendTotalStatisticsEmail();
    List<UserVo> findByAllWorkspace(Map<String, Object> resJsonData, UserCondition condition, Pageable pageable);
    ModelAndView downloadExcel(Model model);
    UserVo removeSensitiveUser(Long userId);
    void updateLastLogin();
    //ModelAndView downloadExcelEvent(Model model);
}
src/main/java/kr/wisestone/owl/service/UserWithDrawService.java
New file
@@ -0,0 +1,11 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.domain.UserWithDraw;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserWithDrawService extends AbstractService<UserWithDraw, Long, JpaRepository<UserWithDraw, Long>> {
    void addUserWithDraw(User user);
    UserWithDraw findByAccount(String account);
}
src/main/java/kr/wisestone/owl/service/UserWorkspaceService.java
New file
@@ -0,0 +1,43 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.domain.UserWorkspace;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.vo.UserWorkspaceVo;
import kr.wisestone.owl.web.condition.UserWorkspaceCondition;
import kr.wisestone.owl.web.form.UserWorkspaceForm;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Map;
public interface UserWorkspaceService extends AbstractService<UserWorkspace, Long, JpaRepository<UserWorkspace, Long>> {
    UserWorkspace addUserWorkspace(User user, Workspace workspace, Boolean managerYn, Boolean useYn);
    List<UserWorkspaceVo> findUserWorkspace(Map<String, Object> resJsonData,
                                            UserWorkspaceCondition condition, Pageable pageable);
    void modifyUserWorkspace(UserWorkspaceForm userWorkspaceForm);
    UserWorkspace findByUserIdAndWorkspaceId(Long userId, Long workspaceId);
    Integer countByWorkspaceIdAndUseYn(Long workspaceId, Boolean useYn);
    List<UserWorkspace> findByWorkspaceIdAndUseYn(Long workspaceId, Boolean useYn);
    List<UserWorkspace> findByWorkspaceIdAndManagerYn(Long workspaceId, Boolean managerYn);
    UserWorkspace findMyWorkspace(Long userId);
    void disabledUserWorkspace(User user, Workspace workspace);
    UserWorkspace getUserWorkspace(Long id);
    boolean checkWorkspaceManager();
    List<UserWorkspace> findByWorkspaceId(Long workspaceId);
    void limitExpireUserWorkspace(Workspace workspace);
}
src/main/java/kr/wisestone/owl/service/WidgetService.java
New file
@@ -0,0 +1,45 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.web.condition.ProjectCondition;
import kr.wisestone.owl.web.condition.WidgetCondition;
import org.springframework.data.domain.Pageable;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
public interface WidgetService {
    WidgetCondition makeWidgetCondition();
    void findAllWidget(Map<String, Object> resJsonData);
    void findStatisticsIssue(Map<String, Object> resJsonData, WidgetCondition widgetCondition);
    void findProjectProgress(Map<String, Object> resJsonData, WidgetCondition widgetCondition);
    void findMyAssigneeIssue(Map<String, Object> resJsonData, WidgetCondition widgetCondition, Pageable pageable);
    void findDelayIssue(Map<String, Object> resJsonData, WidgetCondition widgetCondition, Pageable pageable);
    void findRegisterIssue(Map<String, Object> resJsonData, WidgetCondition widgetCondition, Pageable pageable);
    void findMemberProgress(Map<String, Object> resJsonData, WidgetCondition widgetCondition, Boolean getWidgetCondition);
    void findMyIssueDetail(Map<String, Object> resJsonData, WidgetCondition widgetCondition);
    void findRiskIssue(Map<String, Object> resJsonData, WidgetCondition widgetCondition, Pageable pageable);
    void findIssueComplete(Map<String, Object> resJsonData, WidgetCondition widgetCondition, String searchPeriod);
    void findByStandIssueStatus(Map<String, Object> resJsonData, WidgetCondition widgetCondition);
    void findByStandIssueType(Map<String, Object> resJsonData, WidgetCondition widgetCondition, Boolean getWidgetCondition);
    void findSeverityIssueWidget(Map<String, Object> resJsonData, WidgetCondition widgetCondition, Map<String, Object> parameter, Pageable pageable);
    ModelAndView downloadExcel(HttpServletRequest request, Model model);
}
src/main/java/kr/wisestone/owl/service/WorkflowService.java
New file
@@ -0,0 +1,40 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Workflow;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.domain.enumType.ProjectType;
import kr.wisestone.owl.vo.WorkflowVo;
import kr.wisestone.owl.web.condition.WorkflowCondition;
import kr.wisestone.owl.web.form.WorkflowForm;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
public interface WorkflowService extends AbstractService<Workflow, Long, JpaRepository<Workflow, Long>> {
    void addDefaultWorkflow(Workspace workspace, List<ProjectType> projectTypes);
    Workflow addWorkflow(WorkflowForm form);
    List<WorkflowVo> findWorkflow(Map<String, Object> resJsonData,
                                  WorkflowCondition condition, Pageable pageable);
    void detailWorkflow(Map<String, Object> resJsonData, WorkflowCondition workflowCondition);
    Workflow modifyWorkflow(WorkflowForm form);
    Workflow getWorkflow(Long id);
    void removeWorkflows(WorkflowForm workflowForm);
    Workflow findByWorkspaceIdAndProjectType(Long workspaceId, ProjectType projectType);
    List<Workflow> findByWorkspaceId(Long workspaceId);
    ModelAndView downloadExcel(HttpServletRequest request, Model model);
}
src/main/java/kr/wisestone/owl/service/WorkflowStatusService.java
New file
@@ -0,0 +1,29 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.Project;
import kr.wisestone.owl.domain.WorkflowStatus;
import kr.wisestone.owl.domain.enumType.ProjectType;
import org.springframework.data.jpa.repository.JpaRepository;
import kr.wisestone.owl.vo.WorkflowStatusVo;
import kr.wisestone.owl.web.condition.WorkflowStatusCondition;
//import kr.wisestone.owl.web.form.WorkflowStatusForm;
import java.util.List;
import java.util.Map;
/**
 * Created by wisestone on 2018-01-03.
 */
public interface WorkflowStatusService extends AbstractService<WorkflowStatus, Long, JpaRepository<WorkflowStatus, Long>>{
    List<WorkflowStatus> addDefaultWorkflowStatus(Project project, ProjectType projectType);
    List<WorkflowStatusVo> findWorkflowStatus(Map<String, Object> resJsonData,
                                              WorkflowStatusCondition condition);
//    WorkflowStatus addWorkflowStatus(WorkflowStatusForm workflowStatusForm);
//
//    WorkflowStatusVo detailWorkflowStatus(Map<String, Object> resJsonData, WorkflowStatusForm workflowStatusForm);
//
//    WorkflowStatus modifyWorkflowStatus(WorkflowStatusForm workflowStatusForm);
}
src/main/java/kr/wisestone/owl/service/WorkflowTransitionService.java
New file
@@ -0,0 +1,24 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.IssueStatus;
import kr.wisestone.owl.domain.Workflow;
import kr.wisestone.owl.domain.WorkflowTransition;
import kr.wisestone.owl.domain.enumType.ProjectType;
import kr.wisestone.owl.vo.IssueStatusVo;
import kr.wisestone.owl.vo.WorkflowTransitionVo;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface WorkflowTransitionService extends AbstractService<WorkflowTransition, Long, JpaRepository<WorkflowTransition, Long>>{
    void addDefaultWorkflowTransition(Workflow workflow, List<IssueStatus> issueStatuses, ProjectType projectType);
    List<WorkflowTransition> findByWorkflowId(Long workflowId);
    List<WorkflowTransitionVo> findBySourceIssueStatusIdAndWorkflowId(Long sourceIssueStatusId, Long workflowId);
    void modify(Workflow workflow, List<IssueStatusVo> issueStatusVos);
    WorkflowTransition getWorkflowTransition(Long id);
}
src/main/java/kr/wisestone/owl/service/WorkspaceService.java
New file
@@ -0,0 +1,54 @@
package kr.wisestone.owl.service;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.web.form.PaymentForm;
import kr.wisestone.owl.web.form.WorkspaceForm;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
import java.util.Map;
public interface WorkspaceService extends AbstractService<Workspace, Long, JpaRepository<Workspace, Long>>{
    Workspace addWorkspace(String workspaceName);
    Workspace updateWorkspace(Workspace workspace, PaymentForm paymentForm);
    void expireAlarmWorkspace();
    List<Workspace> findSubscribeImmediateExpireDate();
    void initMaxUserAndStorageSize(Workspace workspace);
    void cancelWorkspacePayment(Workspace workspace);
    Workspace getWorkspace(Long workspaceId);
    void find(Map<String, Object> resJsonData);
    void out(WorkspaceForm workspaceForm);
    void findPrimaryWorkspace(Map<String, Object> resJsonData);
    void findMyWorkspace(Map<String, Object> resJsonData);
    void removeWorkspace(Workspace workspace, User user);
    void modifyWorkspace(WorkspaceForm workspaceForm);
    void expireWorkspace();
    void checkUseWorkspace();
    ModelAndView checkUseExcelDownload(Model model);
    boolean checkUseTraffic(Long fileSize);
    Workspace updateWorkspaceByImmediatePayment(Workspace workspace, int buyUser);
    Map<String, Object> getWorkspaceExpireDay();
    Workspace getPrimaryWorkspace();
}
src/main/java/kr/wisestone/owl/service/impl/AbstractServiceImpl.java
New file
@@ -0,0 +1,93 @@
package kr.wisestone.owl.service.impl;
import kr.wisestone.owl.common.MessageAccessor;
import kr.wisestone.owl.service.AbstractService;
import kr.wisestone.owl.util.WebAppUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.io.Serializable;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class AbstractServiceImpl<T, ID extends Serializable, R extends JpaRepository<T, ID>>
        implements AbstractService<T, ID, R> {
    protected abstract JpaRepository<T, ID> getRepository();
    @PersistenceContext
    protected EntityManager entityManager;
    @Autowired
    protected MessageAccessor messageAccessor;
    @Autowired
    protected WebAppUtil webAppUtil;
    private static final int BATCH_COUNT = 200;    //  배치 사이즈
    @Override
    public long count() {
        return this.getRepository().count();
    }
    @Override
    public void clear() {
        this.entityManager.clear();
    }
    @Override
    public void detach(Object entity) {
        this.entityManager.detach(entity);
    }
    @Override
    @Transactional
    public void bulkInsert(List<T> entities) {
        AtomicInteger adGroupInsightIndex = new AtomicInteger(1);
        entities.forEach(entity -> {
            entity = this.entityManager.merge(entity);
            if (adGroupInsightIndex.get() > 0 && adGroupInsightIndex.get() % BATCH_COUNT == 0) {
                this.entityManager.flush();
                this.entityManager.clear();
            }
            adGroupInsightIndex.getAndIncrement();
        });
        entityManager.flush();
        entityManager.clear();
    }
    @Override
    @Transactional(readOnly = true)
    public T findOne(ID id) {
        Optional<T> entity = this.getRepository().findById(id);
        if (entity.isPresent()) {
            return entity.get();
        }
        return null;
    }
    /*@Override
    @Transactional(readOnly = true)
    public T findOne(ID id) {
        return this.getRepository().getOne(id);
    }*/
    @Override
    @Transactional(readOnly = true)
    public List<T> findAll(List<ID> ids) {
        return this.getRepository().findAllById(ids);
    }
}
src/main/java/kr/wisestone/owl/service/impl/AttachedFileServiceImpl.java
New file
@@ -0,0 +1,589 @@
package kr.wisestone.owl.service.impl;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.*;
import com.amazonaws.services.s3.transfer.*;
import com.google.common.collect.Lists;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.AttachedFile;
import kr.wisestone.owl.domain.Issue;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.domain.enumType.AttachedType;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.mapper.AttachedFileMapper;
import kr.wisestone.owl.repository.AttachedFileRepository;
import kr.wisestone.owl.service.AttachedFileService;
import kr.wisestone.owl.service.IssueService;
import kr.wisestone.owl.service.WorkspaceService;
import kr.wisestone.owl.util.CommonUtil;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.util.MapUtil;
import kr.wisestone.owl.util.WebAppUtil;
import kr.wisestone.owl.vo.AttachedFileVo;
import kr.wisestone.owl.vo.ExportExcelAttrVo;
import kr.wisestone.owl.vo.ExportExcelVo;
import kr.wisestone.owl.web.condition.AttachedFileCondition;
import kr.wisestone.owl.web.form.IssueForm;
import kr.wisestone.owl.web.view.ExcelView;
import kr.wisestone.owl.web.view.FileDownloadView;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import java.io.File;
import java.io.InputStream;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class AttachedFileServiceImpl extends AbstractServiceImpl<AttachedFile, Long, JpaRepository<AttachedFile, Long>>
        implements AttachedFileService {
    private static final Logger LOGGER = LoggerFactory.getLogger(AttachedFileServiceImpl.class);
    @Autowired
    private AttachedFileRepository attachedFileRepository;
    @Autowired
    private WorkspaceService workspaceService;
    @Autowired
    private IssueService issueService;
    @Autowired
    private AttachedFileMapper attachedFileMapper;
    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;
    @Value("${use.aws}")
    private boolean bUseAWS;
    @Value("${attached.file.path}")
    private String uploadFolder;
    @Value("${aws.bucket.name}")
    private String bucketName;
    @Value("${aws.s3.url}")
    private String awsS3Url;
    @Autowired
    private FileDownloadView fileDownloadView;
    @Autowired
    private AmazonS3 amazonS3;
    @Autowired
    private ExcelView excelView;
    @Override
    protected JpaRepository<AttachedFile, Long> getRepository() {
        return this.attachedFileRepository;
    }
    //  첨부 파일을 등록한다. - 이슈 섬머 노트에서 사용
    @Override
    @Transactional
    public List<AttachedFile> addAttachedFile(List<MultipartFile> multipartFiles, Map<String, Object> content) {
        Long workspaceId = MapUtil.getLong(content, "workspaceId");
        Long issueId = MapUtil.getLong(content, "issueId");
        Workspace workspace = this.workspaceService.getWorkspace(workspaceId);
        List<Map<String, Object>> convertFileMaps = Lists.newArrayList();
        for (MultipartFile multipartFile : multipartFiles) {
            convertFileMaps.add(CommonUtil.makeFileMap(multipartFile));
        }
        if (issueId != null) {
            Issue issue = this.issueService.getIssue(issueId);
            return this.addAttachedFiles(workspace, convertFileMaps, issue, null, AttachedType.SUMMER);
        }
        else {
            return this.addAttachedFiles(workspace, convertFileMaps, null, null, AttachedType.TEMP_SUMMER);
        }
    }
    //  첨부 파일을 등록한다. - 이슈 생성, 수정에서 사용
    @Override
    @Transactional
    public void addAttachedFile(List<Map<String, Object>> convertFileMaps, Issue issue, String userAccount) {
        Workspace workspace = issue.getIssueStatus().getWorkspace();
        if (workspace == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.WORKSPACE_NOT_EXIST));
        }
        this.addAttachedFiles(workspace, convertFileMaps, issue, userAccount, AttachedType.ISSUE_ATTACHED);
    }
    private List<AttachedFile> addAttachedFiles(Workspace workspace, List<Map<String, Object>> convertFileMaps, Issue issue, String userAccount, AttachedType attachedType) {
        List<AttachedFile> attachedFiles = Lists.newArrayList();
        AttachedFileCondition attachedFileCondition = new AttachedFileCondition();
        attachedFileCondition.setWorkspaceId(workspace.getId());
        Long useStorageSize = this.attachedFileMapper.findUseStorage(attachedFileCondition);
        if (useStorageSize == null) {
            useStorageSize = 0L;
        }
        //  용량 및 파일 확장자 허용 여부 체크
        this.checkStorageSizeAndFileType(convertFileMaps, workspace.getStorageSize(), useStorageSize);
        int totalFileCount = convertFileMaps.size();    //  전체 업로드 파일 개수
        int uploadFileCount = 1;    //  현재 업로드 파일 순서
        for (Map<String, Object> convertFileMap : convertFileMaps) {
            //  파일 업로드 후 awsKey(파일 명)을 가져온다.
            String awsKey = this.uploadFile(convertFileMap, this.uploadFolder + workspace.getId(), userAccount, totalFileCount, uploadFileCount);
            attachedFiles.add(new AttachedFile(MapUtil.getString(convertFileMap, "fileName"), MapUtil.getLong(convertFileMap, "fileSize"), MapUtil.getString(convertFileMap, "contentType"),
                    this.setMakeFilePath(awsKey, workspace), awsKey, issue, workspace, CommonUtil.getFileType(MapUtil.getString(convertFileMap, "fileName")), attachedType));
            uploadFileCount++;
        }
        if (attachedFiles.size() > 0) {
            this.attachedFileRepository.saveAll(attachedFiles);
        }
        return attachedFiles;
    }
    //  용량 및 파일 확장자 허용 여부 체크
    private void checkStorageSizeAndFileType(List<Map<String, Object>> convertFileMaps, Long totalStorageSize, Long useStorageSize) {
        for (Map<String, Object> convertFileMap : convertFileMaps) {
            Long fileSize = MapUtil.getLong(convertFileMap, "fileSize");
            String fileName = MapUtil.getString(convertFileMap, "fileName");
            if (fileSize == null) {
                fileSize = 0L;
            }
            //  용량 초과 체크
            if (totalStorageSize < (useStorageSize + fileSize)) {
                throw new OwlRuntimeException(
                        this.messageAccessor.getMessage(MsgConstants.WORKSPACE_STORAGE_SIZE_EXCESS));
            }
            //  파일 확장자 체크
            if (!CommonUtil.checkFileType(fileName)) {
                throw new OwlRuntimeException(
                        this.messageAccessor.getMessage(MsgConstants.FILE_TYPE_NOT_ALLOW));
            }
        }
    }
    //  이슈 생성, 수정에서 섬머 노트로 업로드한 이미지와 이슈를 연결시킨다.
    @Override
    @Transactional
    public void connectIssueIdAttachedFile(Issue issue, IssueForm issueForm) {
        for (Long attachedFileId : issueForm.getAttachedFileIds()) {
            AttachedFile attachedFile = this.getAttachedFile(attachedFileId);
            attachedFile.setIssue(issue);
            attachedFile.setAttachedType(AttachedType.SUMMER);
            this.attachedFileRepository.save(attachedFile);
        }
        this.attachedFileRepository.flush();
    }
    //  이슈 목록을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public List<AttachedFileVo> findAttachedFile(Map<String, Object> resJsonData, AttachedFileCondition condition) {
        List<AttachedFileVo> attachedFileVos = Lists.newArrayList();
        for (AttachedFile attachedFile : this.findByIssueId(condition.getIssueId())) {
            AttachedFileVo attachedFileVo = ConvertUtil.copyProperties(attachedFile, AttachedFileVo.class, "fileType");
            attachedFileVo.setFileType(attachedFile.getFileType().toString());
            attachedFileVos.add(attachedFileVo);
        }
        resJsonData.put(Constants.RES_KEY_CONTENTS, attachedFileVos);
        return attachedFileVos;
    }
    //  이슈 아이디로 첨부 파일을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public List<AttachedFile> findByIssueId(Long issueId) {
        return this.attachedFileRepository.findByIssueId(issueId);
    }
    //  첨부 파일 아이디로 첨부 파일을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public AttachedFile getAttachedFile(Long attachedFileId) {
        AttachedFile attachedFile = this.findOne(attachedFileId);
        if (attachedFile == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ATTACHED_FILE_NOT_EXIST));
        }
        return attachedFile;
    }
    //  첨부파일 삭제
    @Override
    @Transactional
    public void removeAttachedFiles(List<Long> removeIds) {
        for (Long attachedId : removeIds) {
            AttachedFile attachedFile = this.getAttachedFile(attachedId);
            switch (attachedFile.getAttachedType()) {
                case SUMMER:
                    //  이슈와 첨부 파일 연결을 해제한다.
                    attachedFile.setIssue(null);
                    break;
                case ISSUE_ATTACHED:
                    //  첨부 파일을 삭제한다.
                    this.removeAttachedFiles(attachedFile);
                    break;
            }
        }
    }
    //  첨부 파일을 삭제한다.
    private void removeAttachedFiles(AttachedFile attachedFile) {
        //  파일을 삭제한다.
        this.removeFile(attachedFile.getAwsKey(), attachedFile.getWorkspace().getId());
        this.attachedFileRepository.delete(attachedFile);
    }
    //  업무 공간 삭제시 이슈에 첨부된 파일을 시스템에서 삭제한다.
    @Override
    @Transactional
    public void deleteWorkspaceCascadeAttachedFile(Workspace workspace) {
        List<Map<String, Object>> attachedFiles = this.attachedFileMapper.findByWorkspaceId(workspace.getId());
        for (Map<String, Object> attachedFile : attachedFiles) {
            //  파일을 삭제한다.
            this.removeFile(MapUtil.getString(attachedFile, "awsKey"), MapUtil.getLong(attachedFile, "workspaceId"));
        }
        //  첨부 파일 삭제
        this.attachedFileMapper.deleteAttachedFileByWorkspaceId(workspace.getId());
    }
    //  프로젝트 삭제시 이슈에 첨부된 파일을 시스템에서 삭제한다.
    @Override
    @Transactional
    public void deleteIssueCascadeAttachedFile(List<Long> issueIds, Workspace workspace) {
        //  이슈가 없을 경우에는 아래 로직을 타지 않는다. -> 모든 업무공간에 첨부파일이 삭제될 위험이 있음.
        if (issueIds.size() < 1) {
            return;
        }
        AttachedFileCondition attachedFileCondition = new AttachedFileCondition();
        attachedFileCondition.setIssueIds(issueIds);
        attachedFileCondition.setWorkspaceId(workspace.getId());
        List<Map<String, Object>> attachedFiles = this.attachedFileMapper.findByIssueIds(attachedFileCondition);
        for (Map<String, Object> attachedFile : attachedFiles) {
            //  파일을 삭제한다.
            this.removeFile(MapUtil.getString(attachedFile, "awsKey"), MapUtil.getLong(attachedFile, "workspaceId"));
        }
        //  첨부 파일 삭제
        this.attachedFileMapper.deleteAttachedFileByIssueIds(attachedFileCondition);
    }
    //  업무 공간에서 사용중인 저장 용량을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public Long findUseStorage(Workspace workspace) {
        AttachedFileCondition attachedFileCondition = new AttachedFileCondition();
        attachedFileCondition.setWorkspaceId(workspace.getId());
        return this.attachedFileMapper.findUseStorage(attachedFileCondition);
    }
    //  업무 공간에서 사용 트래픽을 체크하고 트래픽 초과시 다운로드를 막는다.
    @Override
    @Transactional
    public ModelAndView checkUseWorkspaceTraffic(Long id, Model model) {
        AttachedFile attachedFile = this.getAttachedFile(id);
        //  트래픽 사용량을 저장하고 초과할 경우에는 해당 업무 공간에서 다운로드를 일시적으로 금지한다.
        if (!this.workspaceService.checkUseTraffic(attachedFile.getSize())) {
            ExportExcelVo excelInfo = new ExportExcelVo();
            excelInfo.setFileName("해당 업무 공간에서 사용할 수 있는 트래픽이 초과되었습니다. 트래픽을 추가하려면 와이즈스톤 담당자에게 문의하세요. - supportowl@wisestone.kr");
            excelInfo.addAttrInfos(new ExportExcelAttrVo("name", "", 120, ExportExcelAttrVo.ALIGN_LEFT));
            model.addAttribute(Constants.EXCEL, excelInfo);
            return new ModelAndView(this.excelView);
        }
        ModelAndView objModelView = null;
        if( this.bUseAWS )
        {
            objModelView = downloadFileFromAWS(id, model);
        }
        else
        {
            objModelView = downloadFileFromLocal(id, model);
        }
        return objModelView;
    }
    private ModelAndView downloadFileFromLocal(Long id, Model model) {
        AttachedFile attachedFile = this.getAttachedFile(id);
        InputStream objectInputStream = CommonUtil.getFileInputStream(this.bucketName + this.uploadFolder + attachedFile.getWorkspace().getId(), attachedFile.getAwsKey());
        try {
            byte[] bytes = IOUtils.toByteArray(objectInputStream);
            AttachedFileVo attachedFileVo = ConvertUtil.copyProperties(attachedFile, AttachedFileVo.class);
            attachedFileVo.setBytes(bytes);
            model.addAttribute("fileDownloadTarget", attachedFileVo);
        } catch (Exception e) {
            LOGGER.error("첨부 파일 다운로드에 실패하였습니다.");
        }
        return new ModelAndView(this.fileDownloadView);
    }
    private ModelAndView downloadFileFromAWS(Long id, Model model) {
        AttachedFile attachedFile = this.getAttachedFile(id);
        GetObjectRequest getObjectRequest = new GetObjectRequest(this.bucketName + this.uploadFolder + attachedFile.getWorkspace().getId(), attachedFile.getAwsKey());
        S3Object s3Object = this.amazonS3.getObject(getObjectRequest);
        S3ObjectInputStream objectInputStream = s3Object.getObjectContent();
        try {
            byte[] bytes = IOUtils.toByteArray(objectInputStream);
            AttachedFileVo attachedFileVo = ConvertUtil.copyProperties(attachedFile, AttachedFileVo.class);
            attachedFileVo.setBytes(bytes);
            model.addAttribute("fileDownloadTarget", attachedFileVo);
        } catch (Exception e) {
            LOGGER.error("아마존 클라우드에서 첨부 파일 다운로드에 실패하였습니다.");
        }
        return new ModelAndView(this.fileDownloadView);
    }
    //  이슈와 연결되지 않은 첨부파일 삭제
    @Override
    @Transactional
    public void deleteAttachedFileNotId() {
        this.attachedFileMapper.deleteAttachedFileNotId();
    }
    // 파일을 업로드 한다.
    @Override
    @Transactional
    public String uploadFile(Map<String, Object> convertFileMap, String awsUploadFolder, String userAccount, int totalFileCount, int uploadFileCount) {
        String strKeyName = "";
        if( this.bUseAWS )
        {
            strKeyName = uploadFileToAws(convertFileMap, awsUploadFolder, userAccount, totalFileCount, uploadFileCount);
        }
        else
        {
            strKeyName = uploadFileToLocal(convertFileMap, awsUploadFolder, userAccount, totalFileCount, uploadFileCount);
        }
        return strKeyName;
    }
    // local storage로 이동한다.
    private String uploadFileToLocal(Map<String, Object> convertFileMap, String awsUploadFolder, String userAccount, int totalFileCount, int uploadFileCount) {
        String awsKeyName = CommonUtil.getFileNameByUUID(MapUtil.getString(convertFileMap, "fileName"));
        StopWatch serviceStart = new StopWatch();
        serviceStart.start();
        File file = (File) convertFileMap.get("file");
        try {
            //  이슈 생성, 수정에서 파일을 업로드할때는 비동기로 올리며 업로드 진행률을 표시해준다.
            if (!StringUtils.isEmpty(userAccount)) {
            }
            CommonUtil.moveToSaveStorage(this.bucketName + awsUploadFolder, awsKeyName, file);
            if (file.exists()) {
                file.delete();
            }
        } catch (Exception e) {
            LOGGER.error("파일 업로드 에러 :" + e.getMessage());
        }
        serviceStart.stop();
        return awsKeyName;
    }
    //  아마존 클라우드에 파일을 업로드한다.
    private String uploadFileToAws(Map<String, Object> convertFileMap, String awsUploadFolder, String userAccount, int totalFileCount, int uploadFileCount) {
        String awsKeyName = CommonUtil.getFileNameByUUID(MapUtil.getString(convertFileMap, "fileName"));
        StopWatch serviceStart = new StopWatch();
        serviceStart.start();
        File file = (File) convertFileMap.get("file");
        TransferManager transferManager = TransferManagerBuilder
                .standard()
                .withS3Client(this.amazonS3)
                /*.withMultipartUploadThreshold((long) 5*1024*1024)*/
                /*.withExecutorFactory(() -> Executors.newFixedThreadPool(20))*/
                .build();
        /*TransferManager transferManager = TransferManagerBuilder
                .standard()
                .withS3Client(this.amazonS3)
                .withDisableParallelDownloads(false)
                .withMinimumUploadPartSize((long)(5 * MB))
                .withMultipartUploadThreshold((long)(16 * MB))
                .withMultipartCopyPartSize((long)(5 * MB))
                .withMultipartCopyThreshold((long)(100 * MB))
                .withExecutorFactory(() -> Executors.newFixedThreadPool(20))
                .build();*/
        try {
            PutObjectRequest putObjectRequest =
                    new PutObjectRequest(this.bucketName + awsUploadFolder, awsKeyName, file);
            putObjectRequest.setCannedAcl(CannedAccessControlList.PublicRead); // file permission
            //this.amazonS3.putObject(putObjectRequest); // upload file
            long fileSize = MapUtil.getLong(convertFileMap, "fileSize");
            String fileName = MapUtil.getString(convertFileMap, "fileName");
            //  이슈 생성, 수정에서 파일을 업로드할때는 비동기로 올리며 업로드 진행률을 표시해준다.
            if (!StringUtils.isEmpty(userAccount)) {
                com.amazonaws.event.ProgressListener progressListener = new com.amazonaws.event.ProgressListener() {
                    long bytesUploaded = 0;
                    SimpMessagingTemplate webSocket = simpMessagingTemplate;
                    @Override
                    public void progressChanged(com.amazonaws.event.ProgressEvent progressEvent) {
                        this.bytesUploaded += progressEvent.getBytesTransferred();// add counter
                        double uploadProcess = this.bytesUploaded * 100.0 / fileSize;
                        String percent = new DecimalFormat("###").format(uploadProcess);
                        Map<String, Object> fileMap = new HashMap<>();
                        fileMap.put("display", (uploadProcess < 100));
                        fileMap.put("serverFileName", fileName);
                        fileMap.put("serverProgress", percent + "%");
                        fileMap.put("totalFileCount", totalFileCount);
                        fileMap.put("uploadFileCount", uploadFileCount);
                        this.webSocket.convertAndSendToUser(userAccount, "/notification/file-upload-process", fileMap);
                    }
                };
                putObjectRequest.setGeneralProgressListener(progressListener);
            }
            Upload upload = transferManager.upload(putObjectRequest);
            upload.waitForCompletion();
            transferManager.shutdownNow(false);
            if (file.exists()) {
                file.delete();
            }
        } catch (Exception e) {
            LOGGER.error("파일 업로드 에러 :" + e.getMessage());
        }
        serviceStart.stop();
        return awsKeyName;
    }
    //  파일을 삭제한다.
    @Override
    @Transactional
    public void removeFile(String key, Long workspaceId) {
        try {
            if( this.bUseAWS )
            {
                removeFileToAws(key, workspaceId);
            }
            else
            {
                removeFileToLocal(key, workspaceId);
            }
        } catch (Exception e) {
            LOGGER.error("파일 삭제 에러 :" + e.getMessage());
        }
    }
    //  파일을 삭제한다.
    private void removeFileToLocal(String key, Long workspaceId) {
        try {
            if(workspaceId > 0 )
            {
                CommonUtil.deleteToSaveStorage(this.bucketName + this.uploadFolder, key);
            }
            else
            {
                CommonUtil.deleteToSaveStorage(this.bucketName + this.uploadFolder + workspaceId, key);
            }
        } catch (Exception e) {
            LOGGER.error("파일 삭제 에러 :" + e.getMessage());
        }
    }
    //  아마존클라우드에서 파일을 삭제한다.
    private void removeFileToAws(String key, Long workspaceId) {
        try {
            if(workspaceId > 0 )
            {
                this.amazonS3.deleteObject(this.bucketName + this.uploadFolder, key);
            }
            else
            {
                this.amazonS3.deleteObject(this.bucketName + this.uploadFolder + workspaceId, key);
            }
        } catch (Exception e) {
            LOGGER.error("파일 삭제 에러 :" + e.getMessage());
        }
    }
    //  업로드되는 전체 경로를 가져온다.
    private String setMakeFilePath(String path, Workspace workspace) {
        String strFilePath = "";
        if( this.bUseAWS )
        {
            strFilePath = setMakeAwsFilePath(path, workspace);
        }
        else
        {
            strFilePath = setMakeLocalFilePath(path, workspace);
        }
        return strFilePath;
    }
    //  업로드되는 전체 경로를 가져온다.
    private String setMakeLocalFilePath(String path, Workspace workspace) {
        return this.awsS3Url + this.bucketName + this.uploadFolder + workspace.getId() + "/" + path;
    }
    //  아마존 클라우드에 업로드되는 전체 경로를 가져온다.
    private String setMakeAwsFilePath(String path, Workspace workspace) {
        return this.awsS3Url + this.bucketName + this.uploadFolder + workspace.getId() + "/" + path;
    }
}
src/main/java/kr/wisestone/owl/service/impl/CustomFieldServiceImpl.java
New file
@@ -0,0 +1,408 @@
package kr.wisestone.owl.service.impl;
import com.google.common.collect.Lists;
import kr.wisestone.owl.common.ExcelConditionCheck;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.CustomField;
import kr.wisestone.owl.domain.IssueCustomFieldValue;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.domain.enumType.CustomFieldType;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.mapper.CustomFieldMapper;
import kr.wisestone.owl.repository.CustomFieldRepository;
import kr.wisestone.owl.service.*;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.util.MapUtil;
import kr.wisestone.owl.vo.*;
import kr.wisestone.owl.web.condition.CustomFieldCondition;
import kr.wisestone.owl.web.form.CustomFieldForm;
import kr.wisestone.owl.web.view.ExcelView;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class CustomFieldServiceImpl extends AbstractServiceImpl<CustomField, Long, JpaRepository<CustomField, Long>> implements CustomFieldService {
    private static final Logger log = LoggerFactory.getLogger(CustomFieldServiceImpl.class);
    public enum UseType {
        Y,
        N,
    }
    @Autowired
    private CustomFieldRepository customFieldRepository;
    @Autowired
    private CustomFieldMapper customFieldMapper;
    @Autowired
    private WorkspaceService workspaceService;
    @Autowired
    private CustomFieldValueService customFieldValueService;
    @Autowired
    private IssueCustomFieldValueService issueCustomFieldValueService;
    @Autowired
    private UserService userService;
    @Autowired
    private ExcelView excelView;
    @Autowired
    private ExcelConditionCheck excelConditionCheck;
    @Override
    protected JpaRepository<CustomField, Long> getRepository() {
        return this.customFieldRepository;
    }
    //  사용자 정의 필드를 생성한다.
    @Override
    @Transactional
    public CustomField addCustomField(CustomFieldForm customFieldForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        //  이름 유효성 체크
        this.verifyName(customFieldForm.getName(), null);
        //  옵션 값 유효 체크
        this.verifyOptions(customFieldForm);
        CustomField customField = ConvertUtil.copyProperties(customFieldForm, CustomField.class, "customFieldType");
        customField.setUse("Y");
        Workspace workspace = this.workspaceService.getWorkspace(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        customField.setWorkspace(workspace);
        customField.setCustomFieldType(CustomFieldType.valueOf(customFieldForm.getCustomFieldType()));   // 사용자 정의 필드 유형을 셋팅한다.
        this.customFieldRepository.saveAndFlush(customField);
        this.customFieldValueService.addCustomFieldValues(customField, customFieldForm.getOptions(), null);   //  사용자 정의 필드 값 추가
        return customField;
    }
    //  이름 유효성 체크
    private void verifyName(String name, Long id) {
        if (StringUtils.isEmpty(name)) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_NOT_NAME));
        }
        if (name.length() > 15) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_NAME_MAX_LENGTH_OUT));
        }
        CustomField customField;
        Long workspaceId = this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId();
        if (id == null) {
            customField = this.customFieldRepository.findByNameAndWorkspaceId(name, workspaceId);
        }
        else {
            customField = this.customFieldRepository.findByNameAndWorkspaceIdAndIdNot(name, workspaceId, id);
        }
        if (customField != null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_USED_NAME));
        }
    }
    //  옵션 값 유효 체크
    private void verifyOptions(CustomFieldForm customFieldForm) {
        //  사용자 정의 필드 유형이 텍스트 일때는 옵션 값이 존재하면 안된다.
        if (CustomFieldType.valueOf(customFieldForm.getCustomFieldType()).equals(CustomFieldType.INPUT)) {
            if (customFieldForm.getOptions().size() > 0) {
                throw new OwlRuntimeException(
                        this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_OPTIONS_NOT_USE_INPUT_FIELD));
            }
            if(customFieldForm.getDefaultValue().length() > 100) {
                throw new OwlRuntimeException(
                        this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_DEFAULT_VALUE_MAX_LENGTH_OUT));
            }
        }
        if (customFieldForm.getOptions().size() > 0) {
            //  사용자 정의 필드 옵션 값이 null 이거나 공백인 경우를 찾는다.
            for (String option : customFieldForm.getOptions()) {
                if (StringUtils.isEmpty(option)) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_OPTIONS_NOT_EMPTY_VALUE));
                }
                //  옵션 값 최대 길이 체크
                if (option.length() > 15) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_OPTION_VALUE_MAX_LENGTH_OUT));
                }
            }
            //  사용자 정의 필드 기본 값이 옵션에 존재하지 않을 경우를 찾는다.
            if (!StringUtils.isEmpty(customFieldForm.getDefaultValue())) {
                switch (CustomFieldType.valueOf(customFieldForm.getCustomFieldType())) {
                    case SINGLE_SELECT:
                        if (customFieldForm.getDefaultValue().split("#").length > 2) {
                            throw new OwlRuntimeException(
                                    this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_OPTIONS_NOT_USE_MULTI_DEFAULT_VALUE));
                        }
                        //  기본 값으로 지정한 값이 옵션에 있는지 확인한다.
                        this.verifyOptionsDefaultValue(customFieldForm);
                        break;
                    case MULTI_SELECT:
                        //  기본 값으로 지정한 값이 옵션에 있는지 확인한다.
                        this.verifyOptionsDefaultValue(customFieldForm);
                        break;
                }
            }
        }
    }
    //  기본 값으로 지정한 값이 옵션에 있는지 확인한다.
    private void verifyOptionsDefaultValue(CustomFieldForm customFieldForm) {
        //  중복된 옵션 값 찾기
        Map<String, Object> optionValueDictionary = new HashMap<>();
        for (String defaultValue : customFieldForm.getDefaultValue().split("#")) {
            if (!StringUtils.isEmpty(defaultValue)) {
                if (!customFieldForm.getOptions().contains(defaultValue)) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_OPTIONS_NOT_EXIST_DEFAULT_VALUE));
                }
                if (MapUtil.getBoolean(optionValueDictionary, defaultValue) == null) {
                    optionValueDictionary.put(defaultValue, true);
                }
                else {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_OPTIONS_USED_EXIST_DEFAULT_VALUE));
                }
            }
        }
    }
    //  사용자 정의 필드 목록을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public List<CustomFieldVo> findCustomField(Map<String, Object> resJsonData,
                                               CustomFieldCondition condition, Pageable pageable) {
        condition.setPage(pageable.getPageNumber() * pageable.getPageSize());
        condition.setPageSize(pageable.getPageSize());
        condition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        List<Map<String, Object>> results = this.customFieldMapper.find(condition);
        Long totalCount = this.customFieldMapper.count(condition);
        int totalPage = (int) Math.ceil((totalCount - 1) / pageable.getPageSize()) + 1;
        //  사용자 정의 필드 조회 결과를 CustomFieldVo 로 변환한다.
        List<CustomFieldVo> customFieldVos = this.makeCustomFieldVos(results);
        resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(),
                totalPage, totalCount));
        resJsonData.put(Constants.RES_KEY_CONTENTS, customFieldVos);
        return customFieldVos;
    }
    //  사용자 정의 필드 조회 결과를 CustomFieldVo 로 변환한다.
    private List<CustomFieldVo> makeCustomFieldVos(List<Map<String, Object>> results) {
        List<CustomFieldVo> customFieldVos = Lists.newArrayList();
        for (Map<String, Object> result : results) {
            CustomFieldVo customFieldVo = ConvertUtil.convertMapToClass(result, CustomFieldVo.class);
            CustomField customField = this.getCustomField(customFieldVo.getId());
            customFieldVo.setCustomFieldValueVos(ConvertUtil.convertObjectsToClasses(customField.getCustomFieldValues(), CustomFieldValueVo.class));
            customFieldVos.add(customFieldVo);
        }
        return customFieldVos;
    }
    //  사용자 정의 필드 상세 정보를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public void detailCustomField(Map<String, Object> resJsonData, CustomFieldCondition customFieldCondition) {
        CustomFieldVo customFieldVo = new CustomFieldVo();
        if (customFieldCondition.getId() != null) {
            CustomField customField = this.getCustomField(customFieldCondition.getId());
            customFieldVo = ConvertUtil.copyProperties(customField, CustomFieldVo.class);
            customFieldVo.setCustomFieldType(customField.getCustomFieldType().toString());
            switch (customFieldCondition.getDeep()) {
                case "01": //  사용자 정의 필드 옵션 값을 가져온다.
                    customFieldVo.setCustomFieldValueVos(ConvertUtil.convertObjectsToClasses(customField.getCustomFieldValues(), CustomFieldValueVo.class));
                    //  사용자 정의 필드 옵션 값이 이슈에서 사용되고 있는지 확인한 후 플래그 값을 설정한다.
                    List<IssueCustomFieldValue> issueCustomFieldValues = this.issueCustomFieldValueService.findByCustomFieldId(customField);
                    if (issueCustomFieldValues.size() > 0) {
                        customFieldVo.setUseCustomFieldValue(true);
                    }
                    break;
            }
        }
        resJsonData.put(Constants.RES_KEY_CONTENTS, customFieldVo);
    }
    //  사용자 정의 필드를 수정한다.
    @Override
    @Transactional
    public CustomField modifyCustomField(CustomFieldForm customFieldForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        CustomField customField = this.getCustomField(customFieldForm.getId());
        CustomFieldType oldCustomFieldType = customField.getCustomFieldType();
        //  이름 유효성 체크
        this.verifyName(customFieldForm.getName(), customFieldForm.getId());
        //  옵션 값 유효 체크
        this.verifyOptions(customFieldForm);
        //  사용자 정의 필드 유형이 단일, 다중 선택에서 문자열로 변경될 경우 사용자 정의 필드 값을 초기화한다.
        this.checkChangeCustomFieldType(customFieldForm, customField);
        ConvertUtil.copyProperties(customFieldForm, customField, "id", "customFieldType");
        customField.setCustomFieldType(CustomFieldType.valueOf(customFieldForm.getCustomFieldType()));
        this.customFieldRepository.saveAndFlush(customField);
        this.customFieldValueService.addCustomFieldValues(customField, customFieldForm.getOptions(), oldCustomFieldType);   //  사용자 정의 필드 옵션 값 추가
        return customField;
    }
    //  사용자 정의 필드 유형이 단일, 다중 선택에서 문자열로 변경될 경우 사용자 정의 필드 값을 초기화한다.
    private void checkChangeCustomFieldType(CustomFieldForm customFieldForm, CustomField customField) {
        if (!customField.getCustomFieldType().equals(CustomFieldType.INPUT)) {
            //  단일, 다중 선택에서 문자열 필드로 변경된 경우
            if (CustomFieldType.valueOf(customFieldForm.getCustomFieldType()).equals(CustomFieldType.INPUT)) {
                customField.getCustomFieldValues().clear();
            }
        }
    }
    @Override
    @Transactional(readOnly = true)
    public CustomField getCustomField(Long id) {
        if (id == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_NOT_EXIST));
        }
        CustomField customField = this.findOne(id);
        if (customField == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_NOT_EXIST));
        }
        return customField;
    }
    //  사용자 정의 필드를 삭제한다.
    @Override
    @Transactional
    public void removeCustomFields(CustomFieldForm customFieldForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        if (customFieldForm.getRemoveIds().size() < 1) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_REMOVE_NOT_SELECT));
        }
        for (Long projectId : customFieldForm.getRemoveIds()) {
            this.removeCustomFields(projectId);
        }
//        this.customFieldRepository.flush();
    }
    private void removeCustomFields(Long customFieldId) {
        CustomField customField = this.getCustomField(customFieldId);
        customField.setUse(UseType.N.toString());
        this.customFieldRepository.saveAndFlush(customField);
//        this.customFieldRepository.delete(customField);
    }
    //  이슈 엑셀 import 에서 사용한다.
    @Override
    @Transactional(readOnly = true)
    public CustomField findByName(String name) {
        return this.customFieldRepository.findByNameAndWorkspaceId(name, this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
    }
    //  사용자 정의 필드 목록을 엑셀로 다운로드 한다.
    @Override
    @Transactional
    public ModelAndView downloadExcel(HttpServletRequest request, Model model) {
        //  사용 공간에서 로그인한 사용자가 비활성인지 확인하고 비활성일 경우 엑셀 다운로드를 금지한다.
        ModelAndView modelAndView = this.workspaceService.checkUseExcelDownload(model);
        if (modelAndView != null) {
            return modelAndView;
        }
        Map<String, Object> conditions = new HashMap<>();
        //  엑셀 다운로드에 필요한 검색 조건 정보를 추출하고 검색 조건 추출에 오류가 발생하면 경고를 표시해준다.
        modelAndView = this.excelConditionCheck.checkCondition(conditions, request, model);
        if (modelAndView != null) {
            return modelAndView;
        }
        CustomFieldCondition customFieldCondition = CustomFieldCondition.make(conditions);
        customFieldCondition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        List<Map<String, Object>> results = this.customFieldMapper.find(customFieldCondition);
        for (Map<String, Object> result : results) {
            String customFieldType = MapUtil.getString(result, "customFieldType");
            String customFieldTypeName = "";
            if (customFieldType != null) {
                switch(customFieldType) {
                    case "INPUT" :
                        customFieldTypeName = this.messageAccessor.message("common.stringField"); // 문자열 필드
                        break;
                    case "SINGLE_SELECT" :
                        customFieldTypeName = this.messageAccessor.message("common.singleSelectionField"); // 단일 선택 필드
                        break;
                    case "MULTI_SELECT" :
                        customFieldTypeName = this.messageAccessor.message("common.multipleSelectionField"); // 다중 선택 필드
                        break;
                }
            }
            result.put("customFieldTypeName", customFieldTypeName);
        }
        ExportExcelVo excelInfo = new ExportExcelVo();
        excelInfo.setFileName(this.messageAccessor.message("common.customFieldList")); // 사용자 정의 필드 목록
        excelInfo.addAttrInfos(new ExportExcelAttrVo("name", this.messageAccessor.message("common.customField"), 15, ExportExcelAttrVo.ALIGN_LEFT)); // 사용자 정의 필드
        excelInfo.addAttrInfos(new ExportExcelAttrVo("customFieldTypeName", this.messageAccessor.message("common.fieldType"), 6, ExportExcelAttrVo.ALIGN_CENTER)); // 필드 유형
        //  엑셀에 넣을 데이터
        excelInfo.setDatas(results);
        model.addAttribute(Constants.EXCEL, excelInfo);
        return new ModelAndView(this.excelView);
    }
    //  업무 공간에 있는 모든 사용자 정의 필드을 조회한다. 이슈 엑셀 import 에서 사용
    @Override
    @Transactional(readOnly = true)
    public List<CustomField> findByWorkspaceId() {
        return this.customFieldRepository.findByWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
    }
}
src/main/java/kr/wisestone/owl/service/impl/CustomFieldValueServiceImpl.java
New file
@@ -0,0 +1,125 @@
package kr.wisestone.owl.service.impl;
import com.google.common.collect.Lists;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.CustomField;
import kr.wisestone.owl.domain.CustomFieldValue;
import kr.wisestone.owl.domain.enumType.CustomFieldType;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.repository.CustomFieldValueRepository;
import kr.wisestone.owl.service.CustomFieldValueService;
import kr.wisestone.owl.service.IssueCustomFieldValueService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class CustomFieldValueServiceImpl extends AbstractServiceImpl<CustomFieldValue, Long, JpaRepository<CustomFieldValue, Long>> implements CustomFieldValueService {
    private static final Logger log = LoggerFactory.getLogger(CustomFieldValueServiceImpl.class);
    @Autowired
    private CustomFieldValueRepository customFieldValueRepository;
    @Autowired
    private IssueCustomFieldValueService issueCustomFieldValueService;
    @Override
    protected JpaRepository<CustomFieldValue, Long> getRepository() {
        return this.customFieldValueRepository;
    }
    //  사용자 정의 필드 값을 추가한다.
    @Override
    @Transactional
    public void addCustomFieldValues(CustomField customField, List<String> values, CustomFieldType oldCustomFieldType) {
        if (oldCustomFieldType != null) {
            //  텍스트 입력 필드일 때는 이슈에서 사용되고 있는 사용자 정의 필드 값 확인 후 삭제 처리
            if (customField.getCustomFieldType().equals(CustomFieldType.INPUT)) {
                //  이전에도 텍스트 필드였다면 사용자 정의 필드 값을 삭제하지 않는다.
                if (CustomFieldType.INPUT.equals(oldCustomFieldType)) {
                    return;
                }
                //  사용자 정의 필드 옵션 값이 변경되었을 때 사용자 정의 필드 값을 사용하는 이슈에서 해당 값이 존재하는지 확인하고 없어졌으면 삭제해준다.
                this.issueCustomFieldValueService.checkExistIssueCustomFieldValue(customField, values, oldCustomFieldType);
                return;
            }
            //  다중선택 필드에서 단일 선택 필드로 변경할 경우 모든 값을 삭제처리한다.
            if (oldCustomFieldType.equals(CustomFieldType.MULTI_SELECT) && customField.getCustomFieldType().equals(CustomFieldType.SINGLE_SELECT)) {
                //  이슈에서 저장된 해당 사용자 정의 필드 값을 모두 삭제한다.
                this.issueCustomFieldValueService.removeIssueCustomFieldValuesByCustomFieldId(customField);
            }
        }
        else {
            //  텍스트 입력 필드일 때는 바로 종료
            if (customField.getCustomFieldType().equals(CustomFieldType.INPUT)) {
                return;
            }
        }
        //  사용자 정의 필드 값이 0개 일 경우에는 바로 종료
        if (values.size() < 1) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_OPTIONS_NOT_VALUE));
        }
        List<CustomFieldValue> customFieldValues = Lists.newArrayList();
        List<String> addValues = Lists.newArrayList();
        List<CustomFieldValue> removeValues = Lists.newArrayList();
        //  삭제해야할 대상을 추출한다.
        for (CustomFieldValue customFieldValue : customField.getCustomFieldValues()) {
            Boolean exist = false;
            for (String value : values) {
                if (value.equals(customFieldValue.getValue())) {
                    exist = true;
                    break;
                }
            }
            //  새로 올라온 값에 기존 값이 없으면 삭제대상
            if (!exist) {
                removeValues.add(customFieldValue);
            }
        }
        //  추가해야할 값을 추출한다.
        for (String value : values) {
            Boolean exist = false;
            for (CustomFieldValue customFieldValue : customField.getCustomFieldValues()) {
                if (customFieldValue.getValue().equals(value)) {
                    exist = true;
                    break;
                }
            }
            //  새로 올라온 값이 기존에 없다면 추가 값
            if (!exist) {
                addValues.add(value);
            }
        }
        //  삭제 대상 삭제
        if (removeValues.size() > 0) {
            this.customFieldValueRepository.deleteInBatch(removeValues);
        }
        //  추가 대상 저장
        for (String value : addValues) {
            CustomFieldValue customFieldValue = new CustomFieldValue(customField, value);
            customFieldValues.add(customFieldValue);
        }
        this.customFieldValueRepository.saveAll(customFieldValues);
        //  사용자 정의 필드 옵션 값이 변경되었을 때 사용자 정의 필드 값을 사용하는 이슈에서 해당 값이 존재하는지 확인하고 없어졌으면 삭제해준다.
        this.issueCustomFieldValueService.checkExistIssueCustomFieldValue(customField, values, oldCustomFieldType);
    }
}
src/main/java/kr/wisestone/owl/service/impl/EventServiceImpl.java
New file
@@ -0,0 +1,158 @@
package kr.wisestone.owl.service.impl;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.Event;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.mapper.EventMapper;
import kr.wisestone.owl.repository.EventRepository;
import kr.wisestone.owl.service.EventService;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.vo.EventVo;
import kr.wisestone.owl.vo.ResPage;
import kr.wisestone.owl.web.condition.EventCondition;
import kr.wisestone.owl.web.form.EventForm;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
@Service
public class EventServiceImpl extends AbstractServiceImpl<Event, Long, JpaRepository<Event, Long>> implements EventService {
    @Autowired
    private EventRepository eventRepository;
    @Autowired
    private EventMapper eventMapper;
    @Override
    protected JpaRepository<Event, Long> getRepository() {
        return this.eventRepository;
    }
    //  Event 등록
    @Override
    @Transactional
    public Event addEvent(EventForm eventForm) {
        //  Event 제목 및 내용 공백 체크
        this.verifyTitleAndDescription(eventForm.getTitle(), eventForm.getDescription());
        eventForm.setStatus(Event.INACTIVATION);
        Event event = ConvertUtil.copyProperties(eventForm, Event.class);
        return this.eventRepository.saveAndFlush(event);
    }
    //  Event 제목 및 내용 공백 체크
    private void verifyTitleAndDescription(String title, String description) {
        if (StringUtils.isEmpty(title) || StringUtils.isEmpty(description)) {
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.EVENT_EMPTY_CONTENT));
        }
    }
    //  Event 조회
    @Override
    @Transactional(readOnly = true)
    public List<EventVo> findEvent(Map<String, Object> resJsonData,
                                   EventCondition eventCondition, Pageable pageable) {
        eventCondition.setPage(pageable.getPageNumber() * pageable.getPageSize());
        eventCondition.setPageSize(pageable.getPageSize());
        eventCondition.setTitle(eventCondition.getTitle());
        List<Map<String, Object>> results = this.eventMapper.find(eventCondition);
        Long totalCount = this.eventMapper.count(eventCondition);
        int totalPage = (int) Math.ceil((totalCount - 1) / pageable.getPageSize()) + 1;
        List<EventVo> eventVos = ConvertUtil.convertListToListClass(results, EventVo.class);
        for (EventVo eventVo : eventVos) {
            Boolean bActivation = false;
            if(eventVo.getStatus().equals(Event.ACTIVATION)) {
                bActivation = true;
            }
            eventVo.activation =  bActivation;
        }
        resJsonData.put(Constants.RES_KEY_CONTENTS, eventVos);
        resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(),
                totalPage, totalCount));
        return eventVos;
    }
    //  Event 수정
    @Override
    @Transactional
    public Event modifyEvent(EventForm eventForm) {
        //  Event 제목 및 내용 공백 체크
        this.verifyTitleAndDescription(eventForm.getTitle(), eventForm.getDescription());
        Event event = this.getEvent(eventForm.getId());
        ConvertUtil.copyProperties(eventForm, event, "id");
        return this.eventRepository.saveAndFlush(event);
    }
    //  Event 수정
    @Override
    @Transactional
    public Event activeEvent(EventForm eventForm) {
        boolean bActivation = eventForm.getActivation();
        if(bActivation) {
            eventForm.setStatus(Event.ACTIVATION);
        } else {
            eventForm.setStatus(Event.INACTIVATION);
        }
        Event event = this.getEvent(eventForm.getId());
        ConvertUtil.copyProperties(eventForm, event, "id");
        if(bActivation) {
            this.eventRepository.updateInActivation(eventForm.getId());
        }
        return this.eventRepository.saveAndFlush(event);
    }
    //  Event을 id 로 조회한다.
    @Override
    @Transactional(readOnly = true)
    public Event getEvent(Long id) {
        if (id == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.EVENT_NOT_EXIST));
        }
        Event event = this.findOne(id);
        if (event == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.EVENT_NOT_EXIST));
        }
        return event;
    }
    //  Event 상세 정보를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public void detailEvent(Map<String, Object> resJsonData, EventCondition eventCondition) {
        EventVo eventVo = new EventVo();
        if (eventCondition.getId() != null) {
            Event event = this.getEvent(eventCondition.getId());
            eventVo = ConvertUtil.copyProperties(event, EventVo.class);
        }
        resJsonData.put(Constants.RES_KEY_CONTENTS, eventVo);
    }
}
src/main/java/kr/wisestone/owl/service/impl/FaqServiceImpl.java
New file
@@ -0,0 +1,167 @@
package kr.wisestone.owl.service.impl;
import kr.wisestone.owl.config.kafka.KafkaSender;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.Faq;
import kr.wisestone.owl.domain.Guide;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.mapper.FaqMapper;
import kr.wisestone.owl.repository.FaqRepository;
import kr.wisestone.owl.service.FaqService;
import kr.wisestone.owl.service.UserService;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.vo.FaqVo;
import kr.wisestone.owl.vo.ResPage;
import kr.wisestone.owl.web.condition.FaqCondition;
import kr.wisestone.owl.web.form.FaqForm;
import kr.wisestone.owl.web.form.GuideForm;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class FaqServiceImpl extends AbstractServiceImpl<Faq, Long, JpaRepository<Faq, Long>> implements FaqService {
    @Autowired
    private FaqRepository faqRepository;
    @Autowired
    private UserService userService;
    @Autowired
    private KafkaSender kafkaSender;
    @Autowired
    private FaqMapper faqMapper;
    @Override
    protected JpaRepository<Faq, Long> getRepository() {
        return this.faqRepository;
    }
    //  공지 사항 등록
    @Override
    @Transactional
    public Faq addFaq(FaqForm faqForm) {
        //  faq 제목 및 내용 공백 체크
        this.verifyTitleAndDescription(faqForm.getTitle(), faqForm.getDescription());
        faqForm.setStatus(Faq.INACTIVATION);
        Faq faq = ConvertUtil.copyProperties(faqForm, Faq.class);
        return this.faqRepository.saveAndFlush(faq);
    }
    //  faq 제목 및 내용 공백 체크
    private void verifyTitleAndDescription(String title, String description) {
        if (StringUtils.isEmpty(title) || StringUtils.isEmpty(description)) {
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.FAQ_EMPTY_CONTENT));
        }
    }
    //  faq 조회
    @Override
    @Transactional(readOnly = true)
    public List<FaqVo> findFaq(Map<String, Object> resJsonData,
                               FaqCondition faqCondition, Pageable pageable) {
        faqCondition.setPage(pageable.getPageNumber() * pageable.getPageSize());
        faqCondition.setPageSize(pageable.getPageSize());
        faqCondition.setTitle(faqCondition.getTitle());
        List<Map<String, Object>> results = this.faqMapper.find(faqCondition);
        Long totalCount = this.faqMapper.count(faqCondition);
        int totalPage = (int) Math.ceil((totalCount - 1) / pageable.getPageSize()) + 1;
        List<FaqVo> faqVos = ConvertUtil.convertListToListClass(results, FaqVo.class);
        //  faq 아이디 1 은 관리자 - 관리자만 수정 가능
        for (FaqVo faqVo : faqVos) {
            Boolean bActivation = false;
            if(faqVo.getStatus().equals(Faq.ACTIVATION)) {
                bActivation = true;
            }
            faqVo.activation =  bActivation;
        }
        resJsonData.put(Constants.RES_KEY_CONTENTS, faqVos);
        resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(),
                totalPage, totalCount));
        return faqVos;
    }
    //  faq 수정
    @Override
    @Transactional
    public Faq modifyFaq(FaqForm faqForm) {
        //  공지사항 제목 및 내용 공백 체크
        this.verifyTitleAndDescription(faqForm.getTitle(), faqForm.getDescription());
        Faq faq = this.getFaq(faqForm.getId());
        ConvertUtil.copyProperties(faqForm, faq, "id");
        return this.faqRepository.saveAndFlush(faq);
    }
    //  faq 수정
    @Override
    @Transactional
    public Faq activeFaq(FaqForm faqForm) {
        boolean bActivation = faqForm.getActivation();
        if(bActivation) {
            faqForm.setStatus(Faq.ACTIVATION);
        } else {
            faqForm.setStatus(Faq.INACTIVATION);
        }
        Faq faq = this.getFaq(faqForm.getId());
        ConvertUtil.copyProperties(faqForm, faq, "id");
        return this.faqRepository.saveAndFlush(faq);
    }
    //  faq id 로 조회한다.
    @Override
    @Transactional(readOnly = true)
    public Faq getFaq(Long id) {
        if (id == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.FAQ_NOT_EXIST));
        }
        Faq faq = this.findOne(id);
        if (faq == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.FAQ_NOT_EXIST));
        }
        return faq;
    }
    //  faq 상세 정보를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public void detailFaq(Map<String, Object> resJsonData, FaqCondition faqCondition) {
        FaqVo faqVo = new FaqVo();
        if (faqCondition.getId() != null) {
            Faq faq = this.getFaq(faqCondition.getId());
            faqVo = ConvertUtil.copyProperties(faq, FaqVo.class);
        }
        resJsonData.put(Constants.RES_KEY_CONTENTS, faqVo);
    }
}
src/main/java/kr/wisestone/owl/service/impl/GanttServiceImpl.java
New file
@@ -0,0 +1,115 @@
package kr.wisestone.owl.service.impl;
import com.google.common.collect.Lists;
import kr.wisestone.owl.common.ExcelConditionCheck;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.ElasticSearchConstants;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.*;
import kr.wisestone.owl.domain.enumType.CustomFieldType;
import kr.wisestone.owl.domain.enumType.EmailType;
import kr.wisestone.owl.domain.enumType.IssueHistoryType;
import kr.wisestone.owl.domain.enumType.IssueStatusType;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.mapper.IssueMapper;
import kr.wisestone.owl.mapper.ProjectMapper;
import kr.wisestone.owl.repository.IssueRepository;
import kr.wisestone.owl.service.*;
import kr.wisestone.owl.util.*;
import kr.wisestone.owl.util.DateUtil;
import kr.wisestone.owl.vo.*;
import kr.wisestone.owl.web.condition.IssueCondition;
import kr.wisestone.owl.web.condition.IssueTypeCustomFieldCondition;
import kr.wisestone.owl.web.condition.ProjectCondition;
import kr.wisestone.owl.web.form.IssueCommentForm;
import kr.wisestone.owl.web.form.IssueForm;
import kr.wisestone.owl.web.view.ExcelView;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.poi.ss.usermodel.*;
import org.jsoup.Jsoup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.*;
@Service
public class GanttServiceImpl extends AbstractServiceImpl<Issue, Long, JpaRepository<Issue, Long>> implements GanttService {
    private static final Logger log = LoggerFactory.getLogger(GanttServiceImpl.class);
    @Autowired
    private IssueRepository issueRepository;
    @Autowired
    private IssueService issueService;
    @Override
    protected JpaRepository<Issue, Long> getRepository() {
        return this.issueRepository;
    }
    @Override
    @Transactional
    public void addIssueVersion(Long id) {
        issueService.addIssueVersion(id);
    }
    //  이슈를 생성한다.
    @Override
    @Transactional
    public Issue addIssue(IssueForm issueForm, List<MultipartFile> multipartFiles) {
        return issueService.addIssue(issueForm, multipartFiles);
    }
    //  이슈 목록을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public List<IssueVo> findIssue(Map<String, Object> resJsonData,
                                   IssueCondition issueCondition, Pageable pageable) {
        return issueService.findChartIssue(resJsonData, issueCondition, pageable);
    }
    //  이슈 목록을 조회한다(프로젝트용)
    @Override
    @Transactional(readOnly = true)
    public List<IssueVo> findIssue(Map<String, Object> resJsonData,
                                   ProjectCondition projectCondition, Pageable pageable) {
        return issueService.findChartIssue(resJsonData, projectCondition, pageable);
    }
    //  이슈 상세 정보를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public void detailIssue(Map<String, Object> resJsonData, IssueCondition issueCondition) {
        issueService.detailIssue(resJsonData, issueCondition);
    }
    //  이슈를 수정한다.
    @Override
    @Transactional
    public Issue modifyIssue(IssueForm issueForm, List<MultipartFile> multipartFiles) {
        return  issueService.modifyIssue(issueForm, multipartFiles);
    }
    //  이슈를 삭제한다.
    @Override
    @Transactional
    public void removeIssues(IssueForm issueForm) {
        issueService.removeIssues(issueForm);
    }
}
src/main/java/kr/wisestone/owl/service/impl/GuideServiceImpl.java
New file
@@ -0,0 +1,161 @@
package kr.wisestone.owl.service.impl;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.Guide;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.mapper.GuideMapper;
import kr.wisestone.owl.repository.GuideRepository;
import kr.wisestone.owl.service.GuideService;
import kr.wisestone.owl.util.CommonUtil;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.vo.GuideVo;
import kr.wisestone.owl.vo.ManageUserVo;
import kr.wisestone.owl.vo.ResPage;
import kr.wisestone.owl.web.condition.GuideCondition;
import kr.wisestone.owl.web.form.GuideForm;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class GuideServiceImpl extends AbstractServiceImpl<Guide, Long, JpaRepository<Guide, Long>> implements GuideService {
    @Autowired
    private GuideRepository guideRepository;
    @Autowired
    private GuideMapper guideMapper;
    @Override
    protected JpaRepository<Guide, Long> getRepository() {
        return this.guideRepository;
    }
    //  guide 등록
    @Override
    @Transactional
    public Guide addGuide(GuideForm guideForm) {
        //  guide 제목 및 내용 공백 체크
        this.verifyTitleAndDescription(guideForm.getTitle(), guideForm.getDescription());
        guideForm.setStatus(Guide.INACTIVATION);
        Guide guide = ConvertUtil.copyProperties(guideForm, Guide.class);
        return this.guideRepository.saveAndFlush(guide);
    }
    //  guide 제목 및 내용 공백 체크
    private void verifyTitleAndDescription(String title, String description) {
        if (StringUtils.isEmpty(title) || StringUtils.isEmpty(description)) {
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.GUIDE_EMPTY_CONTENT));
        }
    }
    //  guide 조회
    @Override
    @Transactional(readOnly = true)
    public List<GuideVo> findGuide(Map<String, Object> resJsonData,
                                   GuideCondition guideCondition, Pageable pageable) {
        guideCondition.setPage(pageable.getPageNumber() * pageable.getPageSize());
        guideCondition.setPageSize(pageable.getPageSize());
        guideCondition.setTitle(guideCondition.getTitle());
        List<Map<String, Object>> results = this.guideMapper.find(guideCondition);
        Long totalCount = this.guideMapper.count(guideCondition);
        int totalPage = (int) Math.ceil((totalCount - 1) / pageable.getPageSize()) + 1;
        List<GuideVo> guideVos = ConvertUtil.convertListToListClass(results, GuideVo.class);
        for (GuideVo guideVo : guideVos) {
            Boolean bActivation = false;
            if(guideVo.getStatus().equals(Guide.ACTIVATION)) {
                bActivation = true;
            }
            guideVo.activation =  bActivation;
        }
        resJsonData.put(Constants.RES_KEY_CONTENTS, guideVos);
        resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(),
                totalPage, totalCount));
        return guideVos;
    }
    //  guide 수정
    @Override
    @Transactional
    public Guide modifyGuide(GuideForm guideForm) {
        //  guide 제목 및 내용 공백 체크
        this.verifyTitleAndDescription(guideForm.getTitle(), guideForm.getDescription());
        Guide guide = this.getGuide(guideForm.getId());
        ConvertUtil.copyProperties(guideForm, guide, "id");
        return this.guideRepository.saveAndFlush(guide);
    }
    //  guide 수정
    @Override
    @Transactional
    public Guide activeGuide(GuideForm guideForm) {
        boolean bActivation = guideForm.getActivation();
        if(bActivation) {
            guideForm.setStatus(Guide.ACTIVATION);
        } else {
            guideForm.setStatus(Guide.INACTIVATION);
        }
        Guide guide = this.getGuide(guideForm.getId());
        ConvertUtil.copyProperties(guideForm, guide, "id");
        if(bActivation) {
            this.guideRepository.updateInActivation(guideForm.getId());
        }
        return this.guideRepository.saveAndFlush(guide);
    }
    //  guide을 id 로 조회한다.
    @Override
    @Transactional(readOnly = true)
    public Guide getGuide(Long id) {
        if (id == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.GUIDE_NOT_EXIST));
        }
        Guide guide = this.findOne(id);
        if (guide == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.GUIDE_NOT_EXIST));
        }
        return guide;
    }
    //  guide 상세 정보를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public void detailGuide(Map<String, Object> resJsonData, GuideCondition guideCondition) {
        GuideVo guideVo = new GuideVo();
        if (guideCondition.getId() != null) {
            Guide guide = this.getGuide(guideCondition.getId());
            guideVo = ConvertUtil.copyProperties(guide, GuideVo.class);
        }
        resJsonData.put(Constants.RES_KEY_CONTENTS, guideVo);
    }
}
src/main/java/kr/wisestone/owl/service/impl/IssueCommentServiceImpl.java
New file
@@ -0,0 +1,139 @@
package kr.wisestone.owl.service.impl;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.Issue;
import kr.wisestone.owl.domain.IssueComment;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.repository.IssueCommentRepository;
import kr.wisestone.owl.service.IssueCommentService;
import kr.wisestone.owl.service.IssueService;
import kr.wisestone.owl.service.WorkspaceService;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.vo.IssueCommentVo;
import kr.wisestone.owl.vo.UserVo;
import kr.wisestone.owl.web.form.IssueCommentForm;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class IssueCommentServiceImpl extends AbstractServiceImpl<IssueComment, Long, JpaRepository<IssueComment, Long>>
        implements IssueCommentService {
    static final Logger log = LoggerFactory.getLogger(IssueCommentServiceImpl.class);
    @Autowired
    private IssueCommentRepository issueCommentRepository;
    @Autowired
    private IssueService issueService;
    @Autowired
    private WorkspaceService workspaceService;
    @Override
    protected JpaRepository<IssueComment, Long> getRepository() {
        return this.issueCommentRepository;
    }
    //  댓글을 등록한다.
    @Override
    @Transactional
    public IssueComment addIssueComment(IssueCommentForm issueCommentForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        IssueComment issueComment = ConvertUtil.copyProperties(issueCommentForm, IssueComment.class);
        this.verifyComment(issueCommentForm.getDescription());
        Issue issue = this.issueService.getIssue(issueCommentForm.getIssueId());
        issueComment.setIssue(issue);
        issueComment.setWorkspace(issue.getProject().getWorkspace());
        this.issueCommentRepository.saveAndFlush(issueComment);
        return issueComment;
    }
    private void verifyComment(String comment) {
        if (StringUtils.isEmpty(comment)) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_COMMENT_NOT_COMMENT));
        }
        if (comment.length() > 300) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_COMMENT_MAX_LENGTH_OUT));
        }
    }
    //  댓글을 삭제한다.
    @Override
    @Transactional
    public void removeIssueComments(IssueCommentForm issueCommentForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        if (issueCommentForm.getRemoveIds().size() < 1) {
            throw new OwlRuntimeException(MsgConstants.ISSUE_COMMENT_REMOVE_NOT_SELECT);
        }
        for (Long issueCommentId : issueCommentForm.getRemoveIds()) {
            this.removeIssueComments(issueCommentId);
        }
        this.issueCommentRepository.flush();
    }
    //  댓글을 삭제한다.
    private void removeIssueComments(Long issueCommentId) {
        IssueComment issueComment = this.getIssueComment(issueCommentId);
        //  댓글 삭제 권한 체크
        this.checkRemovePermission(issueComment);
        this.issueCommentRepository.delete(issueComment);
    }
    //  댓글 삭제 권한이 있는지 체크한다.
    private void checkRemovePermission(IssueComment issueComment) {
        UserVo userVo = this.webAppUtil.getLoginUser();
        if (!issueComment.getRegisterId().equals(userVo.getId())) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_COMMENT_NOT_REMOVE_PERMISSION));
        }
    }
    //  댓글 아이디로 댓글을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public IssueComment getIssueComment(Long id ) {
        if (id == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_COMMENT_NOT_EXIST));
        }
        IssueComment issueComment = this.findOne(id);
        if (issueComment == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_COMMENT_NOT_EXIST));
        }
        return issueComment;
    }
    //  이슈에 등록된 댓글 목록을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public List<IssueCommentVo> findIssueComment(Long issueId) {
        return this.issueCommentRepository.findByIssueId(issueId);
    }
}
src/main/java/kr/wisestone/owl/service/impl/IssueCustomFieldValueServiceImpl.java
New file
@@ -0,0 +1,355 @@
package kr.wisestone.owl.service.impl;
import com.google.common.collect.Lists;
import kr.wisestone.owl.domain.*;
import kr.wisestone.owl.domain.enumType.CustomFieldType;
import kr.wisestone.owl.domain.enumType.IssueHistoryType;
import kr.wisestone.owl.mapper.IssueCustomFieldValueMapper;
import kr.wisestone.owl.repository.IssueCustomFieldValueRepository;
import kr.wisestone.owl.service.*;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.util.MapUtil;
import kr.wisestone.owl.vo.CustomFieldVo;
import kr.wisestone.owl.vo.IssueCustomFieldValueVo;
import kr.wisestone.owl.web.condition.IssueCondition;
import kr.wisestone.owl.web.condition.IssueCustomFieldValueCondition;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
@Service
public class IssueCustomFieldValueServiceImpl extends AbstractServiceImpl<IssueCustomFieldValue, Long, JpaRepository<IssueCustomFieldValue, Long>> implements IssueCustomFieldValueService {
    private static final Logger log = LoggerFactory.getLogger(IssueCustomFieldValueServiceImpl.class);
    @Autowired
    private IssueCustomFieldValueRepository issueCustomFieldValueRepository;
    @Autowired
    private CustomFieldService customFieldService;
    @Autowired
    private IssueHistoryService issueHistoryService;
    @Autowired
    private IssueCustomFieldValueMapper issueCustomFieldValueMapper;
    @Autowired
    private IssueTypeCustomFieldService issueTypeCustomFieldService;
    @Autowired
    private UserService userService;
    @Override
    protected JpaRepository<IssueCustomFieldValue, Long> getRepository() {
        return this.issueCustomFieldValueRepository;
    }
    //  이슈에서 사용되는 사용자 정의 필드 값을 업데이트한다.
    @Override
    @Transactional
    public void modifyIssueCustomFieldValue(Issue issue, List<Map<String, Object>> issueCustomFields) {
        //  전체 이슈에 사용되는 사용자 정의 필드 값 삭제
        issue.getIssueCustomFieldValues().clear();
        List<IssueCustomFieldValue> issueCustomFieldValues = Lists.newArrayList();
        for (Map<String, Object> map : issueCustomFields) {
            Map<String, Object> result = new HashMap<>();
            //  customFieldVo 에서 사용자 정의 필드와 이슈 유형에 연결된 사용자 정의 필드 정보를 가져온다.
            this.getCustomFieldAndIssueTypeCustomField(map, issue, result);
            List<String> useValues = MapUtil.getStrings(map, "useValues");
            if (useValues != null) {
                for (String useValue : useValues) {
                    if (!StringUtils.isEmpty(useValue)) {
                        //  Xss 공격 방어를 위해 script 공백으로 치환
                        IssueCustomFieldValue issueCustomFieldValue = new IssueCustomFieldValue(issue, (CustomField)result.get("customField"), (IssueTypeCustomField)result.get("issueTypeCustomField"), useValue);
                        issueCustomFieldValues.add(issueCustomFieldValue);
                    }
                }
            }
        }
        if (issueCustomFieldValues.size() > 0) {
            this.issueCustomFieldValueRepository.saveAll(issueCustomFieldValues);
        }
    }
    //  customFieldVo 에서 사용자 정의 필드와 이슈 유형에 연결된 사용자 정의 필드 정보를 가져온다.
    @Override
    public void getCustomFieldAndIssueTypeCustomField(Map<String, Object> map, Issue issue, Map<String, Object> result) {
        Map<String, Object> customFieldMap = (Map<String, Object>) MapUtil.getObject(map, "customFieldVo");
        CustomField customField = this.customFieldService.getCustomField(MapUtil.getLong(customFieldMap, "id"));
        IssueTypeCustomField issueTypeCustomField = this.issueTypeCustomFieldService.findByProjectIdAndIssueTypeIdAndCustomFieldId(issue.getProject().getId(), issue.getIssueType().getId(), customField.getId());
        result.put("customField", customField);
        result.put("issueTypeCustomField", issueTypeCustomField);
    }
    //  이슈에 연결된 사용자 정의 필드 값을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public List<IssueCustomFieldValueVo> findByIssueId(Long issueId) {
        List<IssueCustomFieldValueVo> issueCustomFieldValueVos = Lists.newArrayList();
        List<IssueCustomFieldValue> issueCustomFieldValues = this.issueCustomFieldValueRepository.findByIssueId(issueId);
        for (IssueCustomFieldValue issueCustomFieldValue : issueCustomFieldValues) {
            IssueCustomFieldValueVo issueCustomFieldValueVo = ConvertUtil.copyProperties(issueCustomFieldValue, IssueCustomFieldValueVo.class);
            CustomFieldVo customFieldVo = ConvertUtil.copyProperties(issueCustomFieldValue.getCustomField(), CustomFieldVo.class);
            customFieldVo.setCustomFieldType(issueCustomFieldValue.getCustomField().getCustomFieldType().toString());
            issueCustomFieldValueVo.setCustomFieldVo(customFieldVo);
            issueCustomFieldValueVos.add(issueCustomFieldValueVo);
        }
        return issueCustomFieldValueVos;
    }
    //  사용자 정의 필드 옵션 값이 변경되었을 때 사용자 정의 필드 값을 사용하는 이슈에서 해당 값이 존재하는지 확인하고 없어졌으면 삭제해준다.
    @Override
    @Transactional
    public void checkExistIssueCustomFieldValue(CustomField customField, List<String> values, CustomFieldType oldCustomFieldType) {
        List<IssueCustomFieldValue> removeIssueCustomFieldValues = Lists.newArrayList();
        Map<String, Object> issueMaps = new HashMap<>(); //  이력을 남길 이슈 추출
        //  이슈에서 사용되는 사용자 정의 필드 값 정보를 추출한다.
        this.saveOriginalIssueCustomFieldValues(customField, values, issueMaps, removeIssueCustomFieldValues);
        //  삭제할 값 삭제
        if (removeIssueCustomFieldValues.size() > 0) {
            this.issueCustomFieldValueRepository.deleteInBatch(removeIssueCustomFieldValues);
            //  삭제 후 남은 이슈 사용자 정의 필드 값 정보를 issueMaps 에 저장한다.
            this.saveNewIssueCustomFieldValues(customField, issueMaps);
            //  변경된 이슈 사용자 정의 필드 값 정보를 히스토리로 남긴다.
            this.saveIssueCustomFieldValueHistory(issueMaps, customField, oldCustomFieldType);
        }
    }
    //  이슈에서 사용되는 사용자 정의 필드 값 정보를 추출한다.
    private void saveOriginalIssueCustomFieldValues(CustomField customField, List<String> values, Map<String, Object> issueMaps, List<IssueCustomFieldValue> removeIssueCustomFieldValues) {
        //  해당 사용자 정의 필드를 사용하고 있는 이슈 정보를 추출한다.
        List<IssueCustomFieldValue> issueCustomFieldValues = this.findByCustomFieldId(customField);
        //  각 이슈의 이전 값 추출
        for (IssueCustomFieldValue issueCustomFieldValue : issueCustomFieldValues) {
            Boolean existValue = false;
            for (String value : values) {
                if (issueCustomFieldValue.getUseValue().equals(value)) {
                    existValue = true;
                    break;
                }
            }
            if (!existValue) {
                //  삭제할 값
                removeIssueCustomFieldValues.add(issueCustomFieldValue);
            }
            String issueId = issueCustomFieldValue.getIssue().getId().toString();
            if (MapUtil.getObject(issueMaps, issueId) == null) {
                Map<String, Object> issue = new HashMap<>();
                issue.put("issue", issueCustomFieldValue.getIssue());
                issue.put("oldValues", Lists.newArrayList(issueCustomFieldValue.getUseValue()));
                issue.put("newValues", Lists.newArrayList());
                issueMaps.put(issueId, issue);
            }
            else {
                Map<String, Object> issue = (Map<String, Object>) issueMaps.get(issueId);
                List<String> oldValues = (List<String>) MapUtil.getObject(issue, "oldValues");
                oldValues.add(issueCustomFieldValue.getUseValue());
                issue.put("oldValues", oldValues);
            }
        }
    }
    //  삭제 후 남은 이슈 사용자 정의 필드 값 정보를 issueMaps 에 저장한다.
    private void saveNewIssueCustomFieldValues(CustomField customField, Map<String, Object> issueMaps) {
        //  삭제 후 다시 이슈 사용자 정의 필드 값을 조회한다.
        List<IssueCustomFieldValue> newIssueCustomFieldValues = this.findByCustomFieldId(customField);
        for (IssueCustomFieldValue newIssueCustomFieldValue : newIssueCustomFieldValues) {
            String issueId = newIssueCustomFieldValue.getIssue().getId().toString();
            if (MapUtil.getObject(issueMaps, issueId) != null) {
                Map<String, Object> issue = (Map<String, Object>) issueMaps.get(issueId);
                List<String> newValues = (List<String>) MapUtil.getObject(issue, "newValues");
                newValues.add(newIssueCustomFieldValue.getUseValue());
                issue.put("newValues", newValues);
            }
        }
    }
    //  변경된 이슈 사용자 정의 필드 값 정보를 히스토리로 남긴다.
    private void saveIssueCustomFieldValueHistory(Map<String, Object> issueMaps, CustomField customField, CustomFieldType oldCustomFieldType) {
        Iterator<String> iterator = issueMaps.keySet().iterator();
        while (iterator.hasNext()) {
            StringBuilder issueChangeDescription = new StringBuilder();
            Map<String, Object> issueMap = (Map<String, Object>) issueMaps.get(iterator.next());
            Issue issue = (Issue) MapUtil.getObject(issueMap, "issue");
            List<String> oldValues = (List<String>) MapUtil.getObject(issueMap, "oldValues");
            List<String> newValues = (List<String>) MapUtil.getObject(issueMap, "newValues");
            //  배열에 있는 값을 보기 좋게 추출한다.
            StringBuilder oldValueString = this.setValueString(oldValues);
            StringBuilder newValueString = this.setValueString(newValues);
            //  이슈 사용자 정의 필드 값이 변경되었는지 확인한다.
            if (this.checkChangeIssueCustomFieldValues(oldValueString, newValueString)) {
                continue;
            }
            //  값이 비었을 때 값이 없다는 글자를 남겨준다.
            this.setEmptyString(oldValueString, oldCustomFieldType);
            //  값이 비었을 때 값이 없다는 글자를 남겨준다.
            this.setEmptyString(newValueString, oldCustomFieldType);
            //  이전에 문자열필드였다가 다른 필드로 변경한 경우나 이전에 문자열 필드가 아니었다가 문자열 필드로 바뀐 경우
            if (CustomFieldType.INPUT.equals(oldCustomFieldType) && !customField.getCustomFieldType().equals(CustomFieldType.INPUT) ||
                    !CustomFieldType.INPUT.equals(oldCustomFieldType) && customField.getCustomFieldType().equals(CustomFieldType.INPUT)) {
                //  사용자 정의 필드 유형이 변경되어 이슈에서 사용된 옵션 값이 삭제되었다고 저장한다.
                this.issueHistoryService.recodeChangeCustomFieldType(customField, oldValueString.toString(), issueChangeDescription);
            }
            else {
                //  사용자 정의 필드의 옵션 값이 삭제되어 이슈에서 사용된 값이 삭제될 경우 변경 정보를 저장한다.
                this.issueHistoryService.recodeRemoveCustomFieldOptionValue(customField, oldValueString.toString(), newValueString.toString(), issueChangeDescription);
            }
            this.issueHistoryService.addIssueHistory(issue, IssueHistoryType.MODIFY, issueChangeDescription.toString());
        }
    }
    //  배열에 있는 값을 보기 좋게 추출한다.
    private StringBuilder setValueString(List<String> values) {
        StringBuilder stringBuilder = new StringBuilder();
        for (String value : values) {
            stringBuilder.append(value);
            stringBuilder.append(", ");
        }
        return stringBuilder;
    }
    //  값이 비었을 때 값이 없다는 글자를 남겨준다.
    private void setEmptyString(StringBuilder stringBuilder, CustomFieldType customFieldType) {
        if (stringBuilder.toString().equals("")) {
            if (CustomFieldType.INPUT.equals(customFieldType)) {
                stringBuilder.append("<span translate=\"common.noValueEntered\">입력한 값이 없습니다.</span>");
            }
            else {
                stringBuilder.append("<span translate=\"common.noValueSelected\">선택한 값이 없습니다.</span>");
            }
        }
    }
    //  이슈 사용자 정의 필드 값이 변경되었는지 확인한다.
    private boolean checkChangeIssueCustomFieldValues(StringBuilder oldValueString, StringBuilder newValueString) {
        if (oldValueString.toString().equals(newValueString.toString())) {
            return true;
        }
        return false;
    }
    //  사용자 정의 필드를 사용하고 있는 값 정보를 추출한다.
    @Override
    @Transactional(readOnly = true)
    public List<IssueCustomFieldValue> findByCustomFieldId(CustomField customField) {
        return this.issueCustomFieldValueRepository.findByCustomFieldId(customField.getId());
    }
    //  프로젝트에 사용자 정의 필드 연결이 해제될 경우 삭제한다.
    @Override
    @Transactional
    public void removeIssueCustomFieldValue(Long issueTypeCustomFieldId) {
        this.issueCustomFieldValueMapper.deleteIssueCustomFieldValue(issueTypeCustomFieldId);
    }
    //  사용자 정의 필드 값을 저장한 이슈를 찾는다.
    @Override
    @Transactional(readOnly = true)
    public boolean find(IssueCondition condition, Set<String> issueIds) {
        boolean customFieldSearch = false;
        int firstCount = 0;
        Set<String> tempIssueIds = new HashSet<>();
        for (Map<String, Object> customField : condition.getIssueCustomFields()) {
            IssueCustomFieldValueCondition issueCustomFieldValueCondition = IssueCustomFieldValueCondition.make(customField);
            if (issueCustomFieldValueCondition.getUseValues().size() > 0 || !StringUtils.isEmpty(issueCustomFieldValueCondition.getUseValue())) {
                issueCustomFieldValueCondition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
                issueCustomFieldValueCondition.setCustomFieldId(MapUtil.getLong(customField, "id"));
                //  사용자 정의 필드 값 검색 시작
                customFieldSearch = true;
                Map<String, Object> result = new HashMap<>();
                switch (CustomFieldType.valueOf(issueCustomFieldValueCondition.getCustomFieldType())) {
                    case INPUT:
                        result = this.issueCustomFieldValueMapper.findLikeUseValue(issueCustomFieldValueCondition);
                        break;
                    case MULTI_SELECT:
                    case SINGLE_SELECT:
                        result = this.issueCustomFieldValueMapper.findByUseValue(issueCustomFieldValueCondition);
                        break;
                }
                String issueIdList = MapUtil.getString(result, "issueIds");
                if (!StringUtils.isEmpty(issueIdList)) {
                    Set<String> results = new HashSet<>(Arrays.asList(issueIdList.split(",")));
                    if (firstCount > 0) {
                        Set<String> commonKeys = new HashSet<>(tempIssueIds);
                        commonKeys.retainAll(results);
                        tempIssueIds = commonKeys;
                    }
                    else {
                        tempIssueIds = results;
                    }
                }
                firstCount++;
            }
        }
        issueIds.addAll(tempIssueIds);
        return customFieldSearch;
    }
    //  이슈에서 저장한 사용자 정의 필드 값을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public List<Map<String, Object>> findInIssueIds(IssueCondition issueCondition) {
        return this.issueCustomFieldValueMapper.findInIssueIds(issueCondition);
    }
    //  이슈에서 저장된 해당 사용자 정의 필드 값을 모두 삭제한다.
    @Override
    @Transactional
    public void removeIssueCustomFieldValuesByCustomFieldId(CustomField customField) {
        //  사용자 정의 필드를 사용하고 있는 값 정보를 추출한다.
        List<IssueCustomFieldValue> issueCustomFieldValues = this.findByCustomFieldId(customField);
        List<Long> ids = Lists.newArrayList();
        for (IssueCustomFieldValue issueCustomFieldValue : issueCustomFieldValues) {
            ids.add(issueCustomFieldValue.getId());
        }
        if (ids.size() > 0) {
            this.issueCustomFieldValueMapper.deleteByIssueCustomFieldValueId(ids);
        }
    }
}
src/main/java/kr/wisestone/owl/service/impl/IssueHistoryServiceImpl.java
New file
@@ -0,0 +1,774 @@
package kr.wisestone.owl.service.impl;
import com.google.common.collect.Lists;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.ElasticSearchConstants;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.*;
import kr.wisestone.owl.domain.enumType.CustomFieldType;
import kr.wisestone.owl.domain.enumType.IssueHistoryType;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.mapper.IssueHistoryMapper;
import kr.wisestone.owl.repository.IssueHistoryRepository;
import kr.wisestone.owl.service.*;
import kr.wisestone.owl.util.*;
import kr.wisestone.owl.vo.IssueHistoryVo;
import kr.wisestone.owl.web.condition.IssueHistoryCondition;
import kr.wisestone.owl.web.form.IssueForm;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.util.*;
@Service
public class IssueHistoryServiceImpl extends AbstractServiceImpl<IssueHistory, Long, JpaRepository<IssueHistory, Long>> implements IssueHistoryService {
    private static final Logger log = LoggerFactory.getLogger(IssueHistoryServiceImpl.class);
    @Autowired
    private IssueHistoryRepository issueHistoryRepository;
    @Autowired
    private UserService userService;
    @Autowired
    private IssueHistoryMapper issueHistoryMapper;
    @Autowired
    private IssueRiskService issueRiskService;
    @Autowired
    private IssueCustomFieldValueService issueCustomFieldValueService;
    @Value("${email.userName}")
    private String systemEmail;
    @Override
    protected JpaRepository<IssueHistory, Long> getRepository() {
        return this.issueHistoryRepository;
    }
    //  이력 생성
    @Override
    @Transactional
    public void addIssueHistory(Issue issue, IssueHistoryType issueHistoryType, String issueChangeDescription) {
        IssueHistory issueHistory = new IssueHistory();
        issueHistory.setIssue(issue);
        issueHistory.setProject(issue.getProject());
        issueHistory.setIssueHistoryType(issueHistoryType);
        StringBuilder description = new StringBuilder();
        //  이력 정보를 만들어 낸다.
        this.makeDescription(description, issueHistoryType, issueChangeDescription);
        issueHistory.setDescription(description.toString());
        this.issueHistoryRepository.saveAndFlush(issueHistory);
        //  비슷한 시간에 연속적으로 기록을 남겼는지 확인하고 발견된 기록을 묶어서 저장한다.
        this.detectSameTimeIssueHistory(issue);
    }
    //  이력 정보를 만들어 낸다.
    @Override
    public void makeDescription(StringBuilder description, IssueHistoryType issueHistoryType, String issueChangeDescription) {
        description.append("<div class=\"activity-text\">");
        //  생성, 수정, 삭제에 대해 기록을 남긴다.
        switch (issueHistoryType) {
            case ADD:
                description.append("<h6 class=\"change\"><span class=\"dot\"></span><span translate=\"common.createIssue\">이슈 생성</span>");
                description.append("<span class='activity-timestamp'>");
                description.append(DateUtil.convertDateToStr(new Date()));
                description.append(" (");
                description.append(this.webAppUtil.getLoginUser().getName());
                description.append(" - ");
                description.append(CommonUtil.decryptAES128(this.webAppUtil.getLoginUser().getAccount()));
                description.append(")");
                description.append("</span></h6>");
                break;
            case MODIFY:
                description.append("<h6 class=\"creat\"><span class=\"dot\"></span><span translate=\"common.updateIssue\">이슈 변경</span>");
                description.append("<span class=\"activity-timestamp\">");
                description.append(DateUtil.convertDateToStr(new Date()));
                description.append(" (");
                if (this.webAppUtil.getLoginUser() != null) {
                    description.append(this.webAppUtil.getLoginUser().getName());
                    description.append(" - ");
                    description.append(CommonUtil.decryptAES128(this.webAppUtil.getLoginUser().getAccount()));
                }
                else {
                    description.append("OWL-ITS-SYSTEM");
                    description.append(" - ");
                    description.append(this.systemEmail);
                }
                description.append(")");
                description.append("</span></h6>");
                description.append(issueChangeDescription);
                break;
            case DELETE:
                description.append("<h6 class=\"delete\"><span class=\"dot\"></span><span translate=\"common.deleteIssue\">이슈 삭제</span>");
                description.append("<span class=\"activity-timestamp\">");
                description.append(DateUtil.convertDateToStr(new Date()));
                description.append(" (");
                description.append(this.webAppUtil.getLoginUser().getName());
                description.append(" - ");
                description.append(CommonUtil.decryptAES128(this.webAppUtil.getLoginUser().getAccount()));
                description.append(")");
                description.append("</span></h6>");
                break;
        }
        description.append("</div>");
    }
    //  비슷한 시간에 연속적으로 기록을 남겼는지 확인하고 발견된 기록을 묶어서 저장한다.
    private void detectSameTimeIssueHistory(Issue issue) {
        StringBuilder description = new StringBuilder();
        List<IssueHistoryVo> issueHistoryVos = this.issueHistoryRepository.findByIssueId(issue.getId());
        List<IssueHistoryVo> saveIssueHistoryVos = Lists.newArrayList();
        for (int count = 0; count < (issueHistoryVos.size() - 1); count++) {
            IssueHistoryVo issueHistoryVo = issueHistoryVos.get(count);
            IssueHistoryVo nextIssueHistoryVo = issueHistoryVos.get(count + 1);
            Date beginDate = DateUtil.convertStrToDate(issueHistoryVo.getRegisterDate());
            Date endDate = DateUtil.convertStrToDate(nextIssueHistoryVo.getRegisterDate());
            long diff = beginDate.getTime() - endDate.getTime();
            //  1분 이내에 기록된 이슈 변경 내역은 합친다.
            if ((diff/1000) < 61) {
                //  중복된 이슈 이력이 있는지 확인하고 중복되지 않았으면 배열에 추가한다.
                this.checkDuplicationHistoryList(saveIssueHistoryVos, issueHistoryVo);
                //  중복된 이슈 이력이 있는지 확인하고 중복되지 않았으면 배열에 추가한다.
                this.checkDuplicationHistoryList(saveIssueHistoryVos, nextIssueHistoryVo);
            }
        }
        List<Long> removeIds = Lists.newArrayList();
        for (IssueHistoryVo issueHistoryVo : saveIssueHistoryVos) {
            description.append(issueHistoryVo.getDescription());
            removeIds.add(issueHistoryVo.getId());
        }
        //  합친 기록 생성
        if (removeIds.size() > 0) {
            Long lastUpdateIssueHistoryId = removeIds.get(removeIds.size() - 1);
            //  마지막 대상에 기록을 옮긴다.
            IssueHistory issueHistory = this.findOne(lastUpdateIssueHistoryId);
            issueHistory.setIssueHistoryType(IssueHistoryType.TOTAL);
            issueHistory.setDescription(description.toString());
            this.issueHistoryRepository.saveAndFlush(issueHistory);
            //  이전 기록 삭제
            for (Long removeId : removeIds) {
                //  마지막 대상은 삭제하지 않는다.
                if (!lastUpdateIssueHistoryId.equals(removeId)) {
                    this.issueHistoryRepository.deleteById(removeId);
                }
            }
        }
    }
    //  중복된 이슈 이력이 있는지 확인하고 중복되지 않았으면 배열에 추가한다.
    private void checkDuplicationHistoryList(List<IssueHistoryVo> issueHistoryVos, IssueHistoryVo issueHistoryVo) {
        boolean duplicate = false;
        for (IssueHistoryVo historyVo : issueHistoryVos) {
            if (historyVo.getId().equals(issueHistoryVo.getId())) {
                duplicate = true;
                break;
            }
        }
        if (!duplicate) {
            issueHistoryVos.add(issueHistoryVo);
        }
    }
    //  해당 사용자의 이슈 기록 정보를 가져온다.
    @Override
    @Transactional(readOnly = true)
    public void findIssueHistory(Map<String, Object> resJsonData, IssueHistoryCondition issueHistoryCondition) {
        //  조회하는 사용자 정보
        issueHistoryCondition.setUserId(this.webAppUtil.getLoginId());
        if (StringUtils.isEmpty(issueHistoryCondition.getSearchPeriod())) {
            issueHistoryCondition.setSearchPeriod(DateUtil.LAST_SEVEN_DAYS);
        }
        //  검색 조건 직접 입력이 아닐 경우
        if (!issueHistoryCondition.getSearchPeriod().equals(DateUtil.CUSTOM_INPUT)) {
            //  검색 일자를 구한다.
            List<Date> searchDates = CommonUtil.findSearchPeriod(issueHistoryCondition.getSearchPeriod());
            //  날짜가 검색되지 않았으면 오류
            if (searchDates.size() < 1) {
                throw new OwlRuntimeException(
                        this.messageAccessor.getMessage(MsgConstants.WIDGET_SEARCH_DATE_NOT_FOUND));
            }
            //  검색 조건에 시작일, 종료일을 설정한다.
            issueHistoryCondition.setSearchStartDate(DateUtil.convertDateToYYYYMMDD(searchDates.get(0)));
            issueHistoryCondition.setSearchEndDate(DateUtil.convertDateToYYYYMMDD(DateUtil.addDays(searchDates.get(searchDates.size() - 1), 1)));
        }
        else {
            issueHistoryCondition.setSearchStartDate(DateUtil.convertDateToStr(DateUtil.convertStrToDate(issueHistoryCondition.getSearchStartDate(), "yyyy-MM-dd")));
            issueHistoryCondition.setSearchEndDate(DateUtil.convertDateToStr(DateUtil.addDays(DateUtil.convertStrToDate(issueHistoryCondition.getSearchEndDate(), "yyyy-MM-dd"), 1)));
        }
        List<Map<String, Object>> results = this.issueHistoryMapper.find(issueHistoryCondition);
        Map<String, Object> issueHistoryMaps = new HashMap<>();
        Map<String, Object> issueHistoryGroups = new HashMap<>();
        issueHistoryMaps.put("searchStartDate", issueHistoryCondition.getSearchStartDate());
        issueHistoryMaps.put("searchEndDate", issueHistoryCondition.getSearchEndDate());
        for (Map<String, Object> result : results) {
            IssueHistoryVo issueHistoryVo = ConvertUtil.convertMapToClass(result, IssueHistoryVo.class);
            List<IssueHistoryVo> issueHistoryVos = Lists.newArrayList();
            String recodeDate = issueHistoryVo.getRegisterDate();
            //  해당 요일에 정보가 없을 경우 배열을 만들어 이슈 기록 정보를 담는다.
            if (MapUtil.getObject(issueHistoryGroups, recodeDate) == null) {
                issueHistoryVos.add(issueHistoryVo);
                issueHistoryGroups.put(recodeDate, issueHistoryVos);
            }
            else {
                //  있을 경우에는 해당 배열을 가져와 이슈 기록 정보를 추가한다.
                issueHistoryVos =  (List<IssueHistoryVo>)MapUtil.getObject(issueHistoryGroups, recodeDate);
                issueHistoryVos.add(issueHistoryVo);
            }
            if (issueHistoryVos.size() > 0) {
                issueHistoryGroups.put(recodeDate, issueHistoryVos);
            }
        }
        issueHistoryMaps.put("issueHistoryGroups", issueHistoryGroups);
        resJsonData.put(Constants.RES_KEY_CONTENTS, issueHistoryMaps);
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_HISTORY_FIND));
    }
    //  이슈 아이디에 해당하는 기록 정보를 가져온다.
    @Override
    @Transactional(readOnly = true)
    public List<IssueHistoryVo> findIssueHistory(Long issueId) {
        return this.issueHistoryRepository.findByIssueId(issueId);
    }
    //  이슈 변경 내역을 추출한다.
    @Override
    public StringBuilder detectIssueChange(Issue issue, IssueForm issueForm, Project project, IssueStatus issueStatus, IssueType issueType, Priority priority, Severity severity, List<MultipartFile> files) {
        StringBuilder description = new StringBuilder();
        //  이슈 프로젝트 변경 정보를 기록한다.
        this.detectProject(issue, issueForm, description, project);
        //  이슈 중요도 변경 정보를 기록한다.
        this.detectIssueSeverity(issue, issueForm, description, severity);
        //  이슈 우선순위 변경 정보를 기록한다.
        this.detectIssuePriority(issue, issueForm, description, priority);
        //  이슈 상태 변경 정보를 기록한다.
        this.detectIssueStatus(issue, issueForm, description, issueStatus);
        //  이슈 타입 변경 정보를 기록한다.
        this.detectIssueType(issue, issueForm, description, issueType);
        //  이슈에 첨부된 파일에 대해 변경 정보를 기록한다.
        this.detectAttachedFile(issueForm, description, files);
        //  이슈 기간 변경 정보를 기록한다.
        this.detectIssuePeriod(issue, issueForm, description);
        //  담당자 변경 정보를 기록한다.
        this.detectIssueManager(issue, issueForm, description);
        //  사용자 정의 필드 변경 정보를 기록한다.
        this.detectCustomField(issue, issueForm, description);
        //  이슈 제목 변경 정보를 기록한다.
        if (!issue.getTitle().equals(issueForm.getTitle())) {
            description.append("<li><span translate=\"common.updateTitle\">제목이 변경되었습니다.</span> </li>");
        }
        //  이슈 내용 변경 정보를 기록한다.
        if (!issue.getDescription().equals(issueForm.getDescription())) {
            description.append("<li><span translate=\"common.updateContent\">내용이 변경되었습니다.</span> </li>");
        }
        if (description.toString().length() > 1){
            StringBuilder firstEnd = new StringBuilder();
            firstEnd.append("<ul class=\"activity-list\">");
            firstEnd.append(description.toString());
            firstEnd.append("</ul>");
            return firstEnd;
        }
        return description;
    }
    //  이슈 프로젝트 변경 정보를 기록한다.
    @Override
    public void detectProject(Issue issue, IssueForm issueForm, StringBuilder description, Project project) {
        if (!issue.getProject().getId().equals(issueForm.getProjectId())) {
            String title = "<span translate=\"common.updateProject\">프로젝트가 변경되었습니다.</span>";
            //  이력 정보를 html 태그로 만들어 준다.
            this.makeIssueHistoryHtml(description, title, issue.getProject().getName(), project.getName());
        }
    }
    //  이력 정보를 html 태그로 만들어 준다.
    private void makeIssueHistoryHtml(StringBuilder description, String title, String beforeValue, String afterValue) {
        description.append("<li>");
        description.append(title);
        description.append("&nbsp;<span class=\"fc-purple\">");
        description.append(beforeValue);
        description.append("</span>");
        description.append("<i class=\"os-icon os-icon-arrow-right fc-grey\"></i>");
        description.append("<span class=\"text-primary bold\">");
        description.append(afterValue);
        description.append("</span>");
        description.append("</li>");
    }
    //  이슈 중요도 변경 정보를 기록한다.
    @Override
    public void detectIssueSeverity(Issue issue, IssueForm issueForm, StringBuilder description, Severity severity) {
        if (!issue.getSeverity().getId().equals(issueForm.getSeverityId())) {
            String title = "<span translate=\"common.updateSeverity\">중요도가 변경되었습니다.</span>";
            //  이력 정보를 html 태그로 만들어 준다.
            this.makeIssueHistoryHtml(description, title, issue.getSeverity().getName(), severity.getName());
        }
    }
    //  이슈 우선순위 변경 정보를 기록한다.
    @Override
    public void detectIssuePriority(Issue issue, IssueForm issueForm, StringBuilder description, Priority priority) {
        if (!issue.getPriority().getId().equals(issueForm.getPriorityId())) {
            String title = "<span translate=\"common.updatePriority\">우선순위가 변경되었습니다.</span>";
            //  이력 정보를 html 태그로 만들어 준다.
            this.makeIssueHistoryHtml(description, title, issue.getPriority().getName(), priority.getName());
        }
    }
    //  이슈 상태 변경 정보를 기록한다.
    @Override
    public void detectIssueStatus(Issue issue, IssueForm issueForm, StringBuilder description, IssueStatus issueStatus) {
        if (!issue.getIssueStatus().getId().equals(issueForm.getIssueStatusId())) {
            String title = "<span translate=\"common.updateHasStatus\">상태가 변경되었습니다.</span>";
            //  이력 정보를 html 태그로 만들어 준다.
            this.makeIssueHistoryHtml(description, title, issue.getIssueStatus().getName(), issueStatus.getName());
            //  이슈 위험 관리에 상태 변경 정보를 업데이트한다. - 담당자 변경
            this.issueRiskService.modifyIssueRisk(issue, true, false, issueForm.getIssueStatusId());
        }
    }
    //  예약 발생으로 이슈 상태 변경된 정보를 기록한다.
    @Override
    public void detectReservationIssueStatus(Issue issue, StringBuilder description, IssueStatus issueStatus) {
        description.append("<ul class=\"activity-list\">");
        String title = "<span translate=\"common.updateHasStatusReservation\">이슈 발생 예약일이 되어 상태가 변경되었습니다.</span>";
        //  이력 정보를 html 태그로 만들어 준다.
        this.makeIssueHistoryHtml(description, title, issue.getIssueStatus().getName(), issueStatus.getName());
        description.append("</ul>");
    }
    //  워크플로우에서 이슈 상태가 삭제되어 상태가 변경된 정보를 기록한다.
    @Override
    public void recordRemoveWorkflowToIssueStatus(String oldIssueStatusName, String newIssueStatusName, StringBuilder description) {
        description.append("<ul class=\"activity-list\">");
        String title = "<span translate=\"common.upddetectReservationIssueStatusateWorkflowNotExist\">변경된 워크플로우에서 상태가 존재하지 않아 이슈의 상태가 변경되었습니다.</span>";
        //  이력 정보를 html 태그로 만들어 준다.
        this.makeIssueHistoryHtml(description, title, oldIssueStatusName, newIssueStatusName);
        description.append("</ul>");
    }
    //  이슈 타입 변경 정보를 기록한다.
    @Override
    public void detectIssueType(Issue issue, IssueForm issueForm, StringBuilder description, IssueType issueType) {
        if (!issue.getIssueType().getId().equals(issueForm.getIssueTypeId())) {
            String title = "<span translate=\"common.updateIssueType\">이슈 타입이 변경되었습니다.</span>";
            //  이력 정보를 html 태그로 만들어 준다.
            this.makeIssueHistoryHtml(description, title, issue.getIssueType().getName(), issueType.getName());
        }
    }
    //  이슈 기간 변경 정보를 기록한다.
    @Override
    public void detectIssuePeriod(Issue issue, IssueForm issueForm, StringBuilder description) {
        //  기간 신규 설정 또는 기간 삭제시
        if ((issue.getStartDate() == null && issueForm.getStartDate() != null) || (issue.getStartDate() != null && issueForm.getStartDate() == null)) {
            //  기간 변경 정보를 기록한다.
            this.recodeChangeIssuePeriod(issue, issueForm, description);
        }
        if (issue.getStartDate() != null && issueForm.getStartDate() != null) {
            if (!issue.getStartDate().equals(issueForm.getStartDate()) || !issue.getCompleteDate().equals(issueForm.getCompleteDate())) {
                //  기간 변경 정보를 기록한다.
                this.recodeChangeIssuePeriod(issue, issueForm, description);
            }
        }
    }
    //  기간 변경 정보를 기록한다.
    private void recodeChangeIssuePeriod(Issue issue, IssueForm issueForm, StringBuilder description) {
        String title = "<span translate=\"common.updatePeriod\">기간이 변경되었습니다.</span>";
        //  기간 정보를 기록한다.
        String beforePeriod = this.recodeIssuePeriod(issue.getStartDate(), issue.getCompleteDate());
        String afterPeriod = this.recodeIssuePeriod(issueForm.getStartDate(), issueForm.getCompleteDate());
        //  이력 정보를 html 태그로 만들어 준다.
        this.makeIssueHistoryHtml(description, title, beforePeriod, afterPeriod);
    }
    //  기간 정보를 기록한다.
    private String recodeIssuePeriod(String startDate, String completeDate) {
        StringBuilder recodeIssuePeriod = new StringBuilder();
        if (startDate != null) {
            recodeIssuePeriod.append(startDate);
            recodeIssuePeriod.append(" ~ ");
            recodeIssuePeriod.append(completeDate);
        }
        else {
            recodeIssuePeriod.append("<span translate=\"common.unspecified\">미지정</span>");
        }
        return recodeIssuePeriod.toString();
    }
    // 연관 일감 변경 정보를 기록한다.
    @Override
    public void detectRelationIssue(IssueHistoryType type, IssueRelation issueRelation, StringBuilder description) {
       if (type == IssueHistoryType.ADD) {
           description.append("<span translate=\"issue.relationIssueAddHistory\">연관 일감이 추가되었습니다. </span>");
           description.append("<span class=\"text-primary bold\">&nbsp;>&nbsp;" + issueRelation.getRelationIssue().getTitle() + "</span>");
       } else {
           description.append("<span translate=\"issue.relationIssueRemoveHistory\">연관 일감이 삭제되었습니다. " + issueRelation.getRelationIssue().getTitle() + "</span>");
           description.append("<span class=\"text-primary bold\">&nbsp;>&nbsp;" + issueRelation.getRelationIssue().getTitle() + "</span>");
       }
    }
    //  담당자 변경 정보를 기록한다.
    @Override
    public void detectIssueManager(Issue issue, IssueForm issueForm, StringBuilder description) {
        boolean saveIssueRisk = false;  //  이슈 위험 관리에 이중 저장되는 것을 방지하기 위한 구분 값
        //  담당자 수가 달려젔을 경우
        if (issue.getIssueUsers().size() != issueForm.getUserIds().size()) {
            this.recodeIssueManager(issue, issueForm, description);
            //  이슈 위험 관리에 담당자 변경 정보를 업데이트한다. - 담당자 변경
            this.issueRiskService.modifyIssueRisk(issue, false, true, null);
            saveIssueRisk = true;
        }
        //  담당자 수는 같으나 사용자가 달라졌을 경우
        if (issue.getIssueUsers().size() > 0 && issueForm.getUserIds().size() > 0) {
            //  이전 담당자 표시
            for (IssueUser issueUser : issue.getIssueUsers()) {
                boolean change = true;
                User user = issueUser.getUser();
                for (Long userId : issueForm.getUserIds()) {
                    if (user.getId().equals(userId)) {
                        change = false;
                        break;
                    }
                }
                if (change) {
                    //  담당자 변경 정보 기록
                    this.recodeIssueManager(issue, issueForm, description);
                    //  담당자수가 달라졌을 경우에 저장되지 않았다면 여기서 이슈 위험 관리 저장을 한다.
                    if (!saveIssueRisk) {
                        //  이슈 위험 관리에 담당자 변경 정보를 업데이트한다. - 담당자 변경
                        this.issueRiskService.modifyIssueRisk(issue, false, true, null);
                    }
                    break;
                }
            }
        }
    }
    //  담당자 변경 정보 기록
    private void recodeIssueManager(Issue issue, IssueForm issueForm, StringBuilder description) {
        String title = "<span translate=\"common.updateAssignee\">담당자가 변경되었습니다.</span>";
        StringBuilder beforeUser = new StringBuilder();
        //  이전 담당자 표시
        for (IssueUser issueUser : issue.getIssueUsers()) {
            beforeUser.append(issueUser.getUser().getName());
            beforeUser.append(", ");
        }
        //  담당자가 없었으면 없음으로 표시
        if (issue.getIssueUsers().size() < 1) {
            beforeUser.append("<span translate=\"common.none\">없음</span>");
        }
        StringBuilder afterUser = new StringBuilder();
        for (Long userId : issueForm.getUserIds()) {
            User user = this.userService.getUser(userId);
            afterUser.append(user.getName());
            afterUser.append(", ");
        }
        //  담당자가 없었으면 없음으로 표시
        if (issueForm.getUserIds().size() < 1) {
            afterUser.append("<span translate=\"common.none\">없음</span>");
        }
        //  이력 정보를 html 태그로 만들어 준다.
        this.makeIssueHistoryHtml(description, title, beforeUser.toString(), afterUser.toString());
    }
    //  이슈에 첨부된 파일에 대해 변경 정보를 기록한다.
    @Override
    public void detectAttachedFile(IssueForm issueForm, StringBuilder description, List<MultipartFile> files) {
        //  삭제 대상이 있거나 새로 업로드 된 파일이 존재할 경우 변경 정보 기록
        if (issueForm.getRemoveFiles().size() > 0 && files.size() > 0) {
            description.append("<li><span translate=\"common.updateAttachment\">첨부 파일이 변경되었습니다.</span> </li>");
        }
        //  삭제 대상이 있거나 새로 업로드 된 파일이 존재할 경우 변경 정보 기록
        if (issueForm.getRemoveFiles().size() > 0 && files.size() < 1) {
            description.append("<li><span translate=\"common.deleteAttachment\">첨부 파일이 삭제되었습니다.</span> </li>");
        }
        //  삭제 대상이 있거나 새로 업로드 된 파일이 존재할 경우 변경 정보 기록
        if (issueForm.getRemoveFiles().size() < 1 && files.size() > 0) {
            description.append("<li><span translate=\"common.registerAttachment\">첨부 파일이 등록되었습니다.</span> </li>");
        }
    }
    //  이슈에서 사용되는 사용자 정의 필드 변경 정보를 기록한다.
    @Override
    public void detectCustomField(Issue issue, IssueForm issueForm, StringBuilder description) {
        //  이슈 폼에서 올라온 사용자 정의 필드 변경 정보를 추출해서 객체로 만든다.
        List<IssueCustomFieldValue> formIssueCustomFieldValues = this.getIssueCustomFieldValuesByIssueForm(issue, issueForm);
        List<Long> passMultiSelectCustomFields = Lists.newArrayList();
        for (IssueCustomFieldValue issueCustomFieldValue : formIssueCustomFieldValues) {
            CustomField customField = issueCustomFieldValue.getCustomField();
            switch (customField.getCustomFieldType()) {
                case INPUT :
                case SINGLE_SELECT :
                    boolean existIssueCustomFieldValue = false;
                    for (IssueCustomFieldValue savedIssueCustomFieldValue : issue.getIssueCustomFieldValues()) {
                        if (customField.getId().equals(savedIssueCustomFieldValue.getCustomField().getId())) {
                            existIssueCustomFieldValue = true;
                            if (!issueCustomFieldValue.getUseValue().equals(savedIssueCustomFieldValue.getUseValue())) {
                                //  기존에 있던 값을 변경한 경우
                                this.recodeCustomFieldValue(savedIssueCustomFieldValue.getUseValue(), issueCustomFieldValue.getUseValue(), customField.getName(), description, customField.getCustomFieldType());
                            }
                            break;
                        }
                    }
                    if (!existIssueCustomFieldValue) {
                        //  기존에 저장된 내역이 없으면서 신규로 저장한 값이 공백일 경우는 저장하지 않는다.
                        if (!StringUtils.isEmpty(issueCustomFieldValue.getUseValue())) {
                            //  신규로 값을 저장한 경우
                            this.recodeCustomFieldValue("", issueCustomFieldValue.getUseValue(), customField.getName(), description, customField.getCustomFieldType());
                        }
                    }
                    break;
                case MULTI_SELECT :
                    this.recodeCaseMultiSelect(customField, issue, formIssueCustomFieldValues, passMultiSelectCustomFields, description);
                    break;
            }
        }
    }
    //  이슈 폼에서 올라온 사용자 정의 필드 변경 정보를 추출해서 객체로 만든다.
    private List<IssueCustomFieldValue> getIssueCustomFieldValuesByIssueForm(Issue issue, IssueForm issueForm) {
        List<IssueCustomFieldValue> formIssueCustomFieldValues = Lists.newArrayList();
        //  변경하려는 사용자 정의 필드 값을 IssueCustomFieldValue 객체로 만든다.
        for (Map<String, Object> map : issueForm.getIssueCustomFields()) {
            Map<String, Object> result = new HashMap<>();
            //  customFieldVo 에서 사용자 정의 필드와 이슈 유형에 연결된 사용자 정의 필드 정보를 가져온다.
            this.issueCustomFieldValueService.getCustomFieldAndIssueTypeCustomField(map, issue, result);
            CustomField customField = (CustomField)result.get("customField");
            IssueTypeCustomField issueTypeCustomField = (IssueTypeCustomField)result.get("issueTypeCustomField");
            List<String> useValues = MapUtil.getStrings(map, "useValues");
            if (useValues != null) {
                for (String useValue : useValues) {
                    IssueCustomFieldValue issueCustomFieldValue = new IssueCustomFieldValue(issue, customField, issueTypeCustomField, useValue);
                    formIssueCustomFieldValues.add(issueCustomFieldValue);
                }
                //  이슈 수정에서 사용자 정의 필드를 선택하지 않았을 경우에는 useValue 값을 공백으로 해서 객체를 생성한다.
                if (useValues.size() < 1) {
                    IssueCustomFieldValue issueCustomFieldValue = new IssueCustomFieldValue(issue, customField, issueTypeCustomField, "");
                    formIssueCustomFieldValues.add(issueCustomFieldValue);
                }
            }
        }
        return formIssueCustomFieldValues;
    }
    //  멀티 셀렉트 유형에 대해 변경 내역을 확인하고 기록한다.
    private void recodeCaseMultiSelect(CustomField customField, Issue issue, List<IssueCustomFieldValue> formIssueCustomFieldValues, List<Long> passMultiSelectCustomFields, StringBuilder description) {
        boolean passMultiSelect = false;
        //  한번 체크한 멀티 셀렉트는 더 이상 변경 내역을 저장하지 않는다. (issue custom field value 에는 멀티셀렉트 값 마다 저장되어있어서...)
        for (Long passMultiSelectCustomFieldId : passMultiSelectCustomFields) {
            if (customField.getId().equals(passMultiSelectCustomFieldId)) {
                passMultiSelect = true;
                break;
            }
        }
        if (passMultiSelect) {
            return;
        }
        else {
            passMultiSelectCustomFields.add(customField.getId());
        }
        //  폼에 있는 멀티 셀렉트 값 추출 -  해당 사용자 정의 필드에서 사용된 값
        List<String> formCustomFieldValues = this.getMultiSelectCustomFieldValues(customField, formIssueCustomFieldValues);
        //  저장되어 있는 멀티 셀렉트 값을 추출
        List<String> savedCustomFieldValues = this.getMultiSelectCustomFieldValues(customField, Lists.newArrayList(issue.getIssueCustomFieldValues()));
        //  변경 정보 저장
        if (formCustomFieldValues.size() != savedCustomFieldValues.size()) {
            this.recodeMultiSelectCustomFieldValue(savedCustomFieldValues, formCustomFieldValues, customField.getName(), description);
        }
        else {
            for (String formCustomFieldValue : formCustomFieldValues) {
                boolean checkValue = false;
                for (String savedCustomFieldValue : savedCustomFieldValues) {
                    if (formCustomFieldValue.equals(savedCustomFieldValue)) {
                        checkValue = true;
                        break;
                    }
                }
                if (!checkValue) {
                    //  변경 정보 저장
                    this.recodeMultiSelectCustomFieldValue(savedCustomFieldValues, formCustomFieldValues, customField.getName(), description);
                    break;
                }
            }
        }
    }
    //  멀티 셀렉트에 있는 사용자 정의 필드 값을 추출한다.
    private List<String> getMultiSelectCustomFieldValues(CustomField customField, List<IssueCustomFieldValue> issueCustomFieldValues) {
        List<String> customFieldValues = Lists.newArrayList();
        //  폼에 있는 멀티 셀렉트 값 추출
        for (IssueCustomFieldValue issueCustomFieldValue : issueCustomFieldValues) {
            if (issueCustomFieldValue.getCustomField().getCustomFieldType().equals(CustomFieldType.MULTI_SELECT) && customField.getId().equals(issueCustomFieldValue.getCustomField().getId())) {
                if (!StringUtils.isEmpty(issueCustomFieldValue.getUseValue())) {
                    customFieldValues.add(issueCustomFieldValue.getUseValue());
                }
            }
        }
        return customFieldValues;
    }
    //  멀티 셀렉트의 변경된 값을 기록한다.
    private void recodeMultiSelectCustomFieldValue(List<String> oldValues, List<String> newValues, String customFieldName, StringBuilder description) {
        StringBuilder oldValueBuilder = new StringBuilder();
        StringBuilder newValueBuilder = new StringBuilder();
        //  배열에서 값을 추출한다.
        this.setListValue(oldValues, oldValueBuilder);
        //  배열에서 값을 추출한다.
        this.setListValue(newValues, newValueBuilder);
        //  사용자 정의 필드 변경 정보를 저장한다.
        this.recodeCustomFieldValue(oldValueBuilder.toString(), newValueBuilder.toString(), customFieldName, description, CustomFieldType.MULTI_SELECT);
    }
    //  배열에서 값을 추출한다.
    private void setListValue(List<String> values, StringBuilder stringBuilder) {
        for (String value : values) {
            stringBuilder.append(value);
            stringBuilder.append(", ");
        }
    }
    //  사용자 정의 필드 변경 정보를 저장한다.
    private void recodeCustomFieldValue(String oldValue, String newValue, String customFieldName, StringBuilder description, CustomFieldType customFieldType) {
        String title = "(" + customFieldName + ")<span translate=\"common.updateCustomField\">사용자 정의 필드가 변경되었습니다.</span>";
        //  기록에 남기는 사용자 정의 필드가 공백 값이면 알아볼 수 있게 알림을 넣어준다.
        String beforeValue = this.checkEmptyCustomFieldValue(oldValue, customFieldType);
        String afterValue = this.checkEmptyCustomFieldValue(newValue,  customFieldType);
        //  이력 정보를 html 태그로 만들어 준다.
        this.makeIssueHistoryHtml(description, title, beforeValue, afterValue);
    }
    //  기록에 남기는 사용자 정의 필드가 공백 값이면 알아볼 수 있게 알림을 넣어준다.
    private String checkEmptyCustomFieldValue(String value, CustomFieldType customFieldType) {
        String result = "";
        if (StringUtils.isEmpty(value)) {
            switch(customFieldType) {
                case INPUT:
                    result = "<span translate=\"common.noValueEntered\">입력한 값이 없습니다.</span>";
                    break;
                case SINGLE_SELECT:
                case MULTI_SELECT:
                    result = "<span translate=\"common.noValueSelected\">선택한 값이 없습니다.</span>";
                    break;
            }
        }
        else {
            result = value;
        }
        return result;
    }
    //  사용자 정의 필드의 옵션 값이 삭제되어 이슈에서 사용된 값이 삭제될 경우 변경 정보를 저장한다.
    @Override
    @Transactional
    public void recodeRemoveCustomFieldOptionValue(CustomField customField, String oldValue, String newValue, StringBuilder description) {
        description.append("<ul class=\"activity-list\">");
        String title = "(" + customField.getName() + ")<span translate=\"common.updateIssueCustomField\">사용자 정의 필드 옵션 값이 변경되어 이슈의 사용자 정의 필드 값이 변경되었습니다.</span>";
        //  이력 정보를 html 태그로 만들어 준다.
        this.makeIssueHistoryHtml(description, title, oldValue, newValue);
        description.append("</ul>");
    }
    //  사용자 정의 필드 유형이 변경되어 이슈에서 사용된 옵션 값이 삭제되었다고 저장한다.
    @Override
    @Transactional
    public void recodeChangeCustomFieldType(CustomField customField, String oldValue, StringBuilder description) {
        description.append("<ul class=\"activity-list\">");
        String title = "(" + customField.getName() + ")<span translate=\"common.updateIssueCustomFieldType\">사용자 정의 필드 유형이 변경되어 이슈의 사용자 정의 필드 값이 변경되었습니다.</span>";
        String newValue = "<span translate=\"common.noValueEntered\">입력한 값이 없습니다.</span>";
        //  이력 정보를 html 태그로 만들어 준다.
        this.makeIssueHistoryHtml(description, title, oldValue, newValue);
        description.append("</ul>");
    }
}
src/main/java/kr/wisestone/owl/service/impl/IssueNumberGeneratorServiceImpl.java
New file
@@ -0,0 +1,77 @@
package kr.wisestone.owl.service.impl;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.IssueNumberGenerator;
import kr.wisestone.owl.domain.Project;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.repository.IssueNumberGeneratorRepository;
import kr.wisestone.owl.service.IssueNumberGeneratorService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Iterator;
import java.util.Map;
@Service
public class IssueNumberGeneratorServiceImpl extends AbstractServiceImpl<IssueNumberGenerator, Long, JpaRepository<IssueNumberGenerator, Long>> implements IssueNumberGeneratorService {
    private static final Logger log = LoggerFactory.getLogger(IssueNumberGeneratorServiceImpl.class);
    @Autowired
    private IssueNumberGeneratorRepository issueNumberGeneratorRepository;
    @Override
    protected JpaRepository<IssueNumberGenerator, Long> getRepository() {
        return this.issueNumberGeneratorRepository;
    }
    //  각 프로젝트의 이슈 번호를 자동으로 생성한다.
    @Override
    @Transactional(readOnly = true)
    public Long generateIssueNumber(Project project) {
        IssueNumberGenerator issueNumberGenerator = this.issueNumberGeneratorRepository.findByProjectId(project.getId());
        if (issueNumberGenerator == null) {
            issueNumberGenerator = new IssueNumberGenerator(project, 0L);
        }
        else {
            issueNumberGenerator.setNumber(issueNumberGenerator.getNumber() + 1);
        }
        this.issueNumberGeneratorRepository.saveAndFlush(issueNumberGenerator);
        return issueNumberGenerator.getNumber();
    }
    //  이슈 엑셀 import 가 완료된 후 사용된 issue number 를 업데이트해준다.
    @Override
    @Transactional
    public void updateIssueNumber(Map<Long, Long> issueNumberMaps) {
        Iterator iterator = issueNumberMaps.keySet().iterator();
        while(iterator.hasNext()) {
            Long projectId = (Long)iterator.next();
            IssueNumberGenerator issueNumberGenerator = this.issueNumberGeneratorRepository.findByProjectId(projectId);
            if (issueNumberGenerator == null) {
                throw new OwlRuntimeException(
                        this.messageAccessor.getMessage(MsgConstants.ISSUE_NUMBER_GENERATOR_NOT_EXIST));
            }
            Long issueNumber = issueNumberMaps.get(projectId);
            if (issueNumber == null) {
                throw new OwlRuntimeException(
                        this.messageAccessor.getMessage(MsgConstants.ISSUE_NUMBER_NOT_EXIST));
            }
            issueNumberGenerator.setNumber(--issueNumber);
            this.issueNumberGeneratorRepository.saveAndFlush(issueNumberGenerator);
        }
    }
}
src/main/java/kr/wisestone/owl/service/impl/IssueRelationServiceImpl.java
New file
@@ -0,0 +1,121 @@
package kr.wisestone.owl.service.impl;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.domain.Issue;
import kr.wisestone.owl.domain.IssueHistory;
import kr.wisestone.owl.domain.IssueRelation;
import kr.wisestone.owl.domain.enumType.IssueHistoryType;
import kr.wisestone.owl.repository.IssueRelationRepository;
import kr.wisestone.owl.service.IssueHistoryService;
import kr.wisestone.owl.service.IssueRelationService;
import kr.wisestone.owl.service.IssueService;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.util.MapUtil;
import kr.wisestone.owl.vo.IssueRelationVo;
import kr.wisestone.owl.vo.IssueVo;
import kr.wisestone.owl.vo.ResPage;
import kr.wisestone.owl.web.condition.IssueCondition;
import kr.wisestone.owl.web.condition.IssueRelationCondition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Service
public class IssueRelationServiceImpl extends AbstractServiceImpl<IssueRelation, Long, JpaRepository<IssueRelation, Long>> implements IssueRelationService {
    @Autowired
    private IssueRelationRepository issueRelationRepository;
    @Autowired
    private IssueService issueService;
    @Autowired
    private IssueHistoryService issueHistoryService;
    @Override
    protected IssueRelationRepository getRepository() {
        return this.issueRelationRepository;
    }
    // 연관 일감 추가
    @Override
    public void addRelationIssue(Map<String, Object> resJsonData, IssueRelationCondition condition) {
        if (condition != null) {
            Issue issue = this.issueService.getIssue(condition.getIssueId());
            IssueRelation issueRelation = new IssueRelation();
            issueRelation.setIssue(issue);
            issueRelation.setRelationIssue(issueService.getIssue(condition.getRelationIssueId()));
            issueRelation.setRelationIssueType(condition.getRelationIssueType());
            issueRelationRepository.saveAndFlush(issueRelation);
            StringBuilder sb = new StringBuilder();
            issueHistoryService.detectRelationIssue(IssueHistoryType.ADD, issueRelation, sb);
            issueHistoryService.addIssueHistory(issue, IssueHistoryType.MODIFY, sb.toString());
        }
    }
    // 연관 일감 가져오기
    @Override
    public List<IssueVo> findRelationIssue(Map<String, Object> resJsonData, IssueRelationCondition condition, Pageable pageable) {
        List<IssueRelation> issueRelations = issueRelationRepository.findAllByIssueId(condition.getIssueId());
        List<IssueVo> issueVos = new ArrayList<>();
        if (issueRelations != null) {
            for (IssueRelation issueRelation : issueRelations) {
                issueVos.add(ConvertUtil.copyProperties(issueRelation.getRelationIssue(), IssueVo.class));
            }
            int totalCount = issueVos.size();
            resJsonData.put(Constants.RES_KEY_CONTENTS, issueVos);
            resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(),
                1, totalCount));
        }
        return  issueVos;
    }
    // 연관 일감 가져오기
    @Override
    public List<IssueVo> findRelationIssue(Long issueId) {
        List<IssueRelation> issueRelations = issueRelationRepository.findAllByIssueId(issueId);
        List<IssueVo> issueVos = new ArrayList<>();
        if (issueRelations != null) {
            for (IssueRelation issueRelation : issueRelations) {
                issueVos.add(ConvertUtil.copyProperties(issueRelation.getRelationIssue(), IssueVo.class));
            }
        }
        return issueVos;
    }
    // 연관 일감 삭제
    @Override
    @Transactional
    public boolean removeRelationIssue(Map<String, Object> resJsonData, IssueRelationCondition condition) {
        Long id = condition.getId();
        if (id != null) {
            IssueRelation issueRelation = findOne(id);
            if (issueRelation != null) {
                StringBuilder sb = new StringBuilder();
                issueHistoryService.detectRelationIssue(IssueHistoryType.DELETE, issueRelation, sb);
                issueHistoryService.addIssueHistory(issueRelation.getIssue(), IssueHistoryType.MODIFY, sb.toString());
                this.issueRelationRepository.deleteById(id);
                return true;
            }
        }
        return false;
    }
}
src/main/java/kr/wisestone/owl/service/impl/IssueReservationServiceImpl.java
New file
@@ -0,0 +1,243 @@
package kr.wisestone.owl.service.impl;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.ElasticSearchConstants;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.Issue;
import kr.wisestone.owl.domain.IssueReservation;
import kr.wisestone.owl.domain.enumType.IssueReservationType;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.repository.IssueReservationRepository;
import kr.wisestone.owl.service.IssueReservationService;
import kr.wisestone.owl.service.IssueService;
import kr.wisestone.owl.service.WorkspaceService;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.util.ElasticSearchUtil;
import kr.wisestone.owl.vo.IssueReservationVo;
import kr.wisestone.owl.web.condition.IssueReservationCondition;
import kr.wisestone.owl.web.form.IssueReservationForm;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
@Service
public class IssueReservationServiceImpl extends AbstractServiceImpl<IssueReservation, Long, JpaRepository<IssueReservation, Long>> implements IssueReservationService {
    private static final Logger log = LoggerFactory.getLogger(IssueReservationServiceImpl.class);
    @Autowired
    private WorkspaceService workspaceService;
    @Autowired
    private IssueService issueService;
    @Autowired
    private IssueReservationRepository issueReservationRepository;
    @Override
    protected JpaRepository<IssueReservation, Long> getRepository() {
        return this.issueReservationRepository;
    }
    //  이슈 발생 예약 상세 정보를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public void detailIssueReservation(Map<String, Object> resJsonData, IssueReservationCondition issueReservationCondition) {
        IssueReservationVo issueReservationVo = new IssueReservationVo();
        if (issueReservationCondition.getIssueId() != null) {
            Issue issue = this.issueService.getIssue(issueReservationCondition.getIssueId());
            IssueReservation issueReservation = issue.getIssueReservation();
            if (issueReservation != null) {
                issueReservationVo = ConvertUtil.copyProperties(issueReservation, IssueReservationVo.class, "issueReservationType");
                if (issueReservation.getIssueReservationType() != null) {
                    issueReservationVo.setIssueReservationType(issueReservation.getIssueReservationType().toString());
                }
            }
        }
        resJsonData.put(Constants.RES_KEY_CONTENTS, issueReservationVo);
    }
    //  이슈 발생 이력 아이디로 이슈 발생 이력을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public IssueReservation getIssueReservation(Long id) {
        if (id == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_RESERVATION_NOT_EXIST));
        }
        IssueReservation issueReservation = this.findOne(id);
        if (issueReservation == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_RESERVATION_NOT_EXIST));
        }
        return issueReservation;
    }
    //  이슈 발생 이력 정보를 수정한다.
    @Override
    @Transactional
    public void modifyIssueReservation(Map<String, Object> resJsonData, IssueReservationForm issueReservationForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        IssueReservation issueReservation;
        //  수정
        if (issueReservationForm.getId() != null) {
            issueReservation = this.getIssueReservation(issueReservationForm.getId());
            ConvertUtil.copyProperties(issueReservationForm, issueReservation, "id", "issueReservationType");
            if (!StringUtils.isEmpty(issueReservationForm.getIssueReservationType())) {
                issueReservation.setIssueReservationType(IssueReservationType.valueOf(issueReservationForm.getIssueReservationType()));
                //  이슈 발생 예약일 체크
                this.verifyReservation(issueReservation.getIssueReservationType(), issueReservation.getReservation());
            }
            else {
                issueReservation.setIssueReservationType(null);
                issueReservation.setReservation(null);
            }
        }
        else {
            Issue issue = this.issueService.getIssue(issueReservationForm.getIssueId());
            //  생성
            issueReservation = ConvertUtil.copyProperties(issueReservationForm, IssueReservation.class, "issueReservationType");
            issueReservation.setIssue(issue);
            issueReservation.setWorkspace(issue.getProject().getWorkspace());
            if (!StringUtils.isEmpty(issueReservationForm.getIssueReservationType())) {
                issueReservation.setIssueReservationType(IssueReservationType.valueOf(issueReservationForm.getIssueReservationType()));
            }
        }
        this.issueReservationRepository.saveAndFlush(issueReservation);
        resJsonData.put(Constants.RES_KEY_CONTENTS, ConvertUtil.copyProperties(issueReservation, IssueReservationVo.class));
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_RESERVATION));
    }
    //  이슈 발생 예약일 체크
    private void verifyReservation(IssueReservationType issueReservationType, String reservation) {
        switch (issueReservationType) {
            case DAY:
                if (!StringUtils.isEmpty(reservation)) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.ISSUE_RESERVATION_VALUE_INVALID));
                }
                break;
            case WEEK:
                if (StringUtils.isEmpty(reservation)) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.ISSUE_RESERVATION_VALUE_INVALID));
                }
                boolean pass = false;
                //  일 단위 예약
                switch (reservation) {
                    case "1":
                    case "2":
                    case "3":
                    case "4":
                    case "5":
                    case "6":
                    case "7":
                        pass = true;
                        break;
                }
                if (!pass) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.ISSUE_RESERVATION_VALUE_INVALID));
                }
                break;
            case MONTH:
                if (StringUtils.isEmpty(reservation)) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.ISSUE_RESERVATION_VALUE_INVALID));
                }
                if (!reservation.matches("^[0-9]*$")) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.ISSUE_RESERVATION_VALUE_INVALID));
                }
                if (Integer.parseInt(reservation) > 31) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.ISSUE_RESERVATION_VALUE_INVALID));
                }
                break;
            case YEAR:
                if (StringUtils.isEmpty(reservation)) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.ISSUE_RESERVATION_VALUE_INVALID));
                }
                if (!reservation.matches("^[0-9]+-[0-9]+$")) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.ISSUE_RESERVATION_VALUE_INVALID));
                }
                String[] date = reservation.split("-");
                if (date.length < 2) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.ISSUE_RESERVATION_VALUE_INVALID));
                }
                if (Integer.parseInt(date[0]) > 12 || Integer.parseInt(date[0]) < 1) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.ISSUE_RESERVATION_VALUE_INVALID));
                }
                if (Integer.parseInt(date[1]) > 31 || Integer.parseInt(date[1]) < 1) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.ISSUE_RESERVATION_VALUE_INVALID));
                }
                switch (Integer.parseInt(date[0])) {
                    case 2:
                        if (Integer.parseInt(date[1]) > 29) {
                            throw new OwlRuntimeException(
                                    this.messageAccessor.getMessage(MsgConstants.ISSUE_RESERVATION_VALUE_INVALID));
                        }
                        break;
                    case 4:
                    case 6:
                    case 9:
                    case 11:
                        if (Integer.parseInt(date[1]) > 30) {
                            throw new OwlRuntimeException(
                                    this.messageAccessor.getMessage(MsgConstants.ISSUE_RESERVATION_VALUE_INVALID));
                        }
                        break;
                }
                break;
        }
    }
    //  이슈 발생 이력 정보가 저장되어 있는 항목을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public List<IssueReservation> findByIssueReservationTypeNotNull() {
        return this.issueReservationRepository.findByIssueReservationTypeNotNull();
    }
}
src/main/java/kr/wisestone/owl/service/impl/IssueRiskServiceImpl.java
New file
@@ -0,0 +1,84 @@
package kr.wisestone.owl.service.impl;
import kr.wisestone.owl.domain.Issue;
import kr.wisestone.owl.domain.IssueRisk;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.repository.IssueRiskRepository;
import kr.wisestone.owl.service.IssueRiskService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.Map;
@Service
public class IssueRiskServiceImpl extends AbstractServiceImpl<IssueRisk, Long, JpaRepository<IssueRisk, Long>> implements IssueRiskService {
    private static final Logger log = LoggerFactory.getLogger(IssueRiskServiceImpl.class);
    @Autowired
    private IssueRiskRepository issueRiskRepository;
    @Override
    protected JpaRepository<IssueRisk, Long> getRepository() {
        return this.issueRiskRepository;
    }
    //  이슈가 생성될 때 이슈 위험 관리도 같이 생성된다.
    @Override
    @Transactional
    public IssueRisk addIssueRisk(Issue issue, Workspace workspace) {
        IssueRisk issueRisk = new IssueRisk();
        issueRisk.setChangeIssueStatusCount(0L);
        issueRisk.setChangeAssigneeCount(0L);
        issueRisk.setIssue(issue);
        issueRisk.setWorkspace(workspace);
        issueRisk.setIssueStatusIds(issue.getIssueStatus().getId().toString());
        return this.issueRiskRepository.saveAndFlush(issueRisk);
    }
    //  이슈에서 담당자나 상태가 변경될 경우 이슈 위험 관리 정보를 업데이트한다.
    @Override
    @Transactional
    public void modifyIssueRisk(Issue issue, Boolean changeIssueStatus, Boolean changeAssignee, Long issueStatusId) {
        IssueRisk issueRisk = issue.getIssueRisk();
        //  상태 변경
        if (changeIssueStatus) {
            String issueStatusIds = issueRisk.getIssueStatusIds();
            issueStatusIds += "&" + issueStatusId;
            Map<String, Integer> issueStatusIdMaps = new HashMap<>();
            for (String key : issueStatusIds.split("&")) {
                if (!issueStatusIdMaps.containsKey(key)) {
                    issueStatusIdMaps.put(key, 1);
                }
                else {
                    issueStatusIdMaps.put(key, issueStatusIdMaps.get(key) + 1);
                }
            }
            Integer saveCount = 0;
            for (Integer value : issueStatusIdMaps.values()) {
                if (value > saveCount) {
                    saveCount = value;
                }
            }
            issueRisk.setChangeIssueStatusCount(saveCount.longValue());
            issueRisk.setIssueStatusIds(issueStatusIds);
        }
        //  담당자 변경
        if (changeAssignee) {
            issueRisk.setChangeAssigneeCount(issueRisk.getChangeAssigneeCount() + 1);
        }
        this.issueRiskRepository.saveAndFlush(issueRisk);
    }
}
src/main/java/kr/wisestone/owl/service/impl/IssueSearchServiceImpl.java
New file
@@ -0,0 +1,88 @@
package kr.wisestone.owl.service.impl;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.domain.IssueSearch;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.repository.IssueSearchRepository;
import kr.wisestone.owl.service.IssueSearchService;
import kr.wisestone.owl.service.UserService;
import kr.wisestone.owl.service.WorkspaceService;
import kr.wisestone.owl.util.MapUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Map;
@Service
public class IssueSearchServiceImpl extends AbstractServiceImpl<IssueSearch, Long, JpaRepository<IssueSearch, Long>> implements IssueSearchService {
    private static final Logger log = LoggerFactory.getLogger(IssueSearchServiceImpl.class);
    @Autowired
    private IssueSearchRepository issueSearchRepository;
    @Autowired
    private WorkspaceService workspaceService;
    @Autowired
    private UserService userService;
    @Override
    protected JpaRepository<IssueSearch, Long> getRepository() {
        return this.issueSearchRepository;
    }
    //  이슈 검색 조건을 저장한다. - 이슈 번호만 저장한다.
    @Override
    @Transactional
    public IssueSearch addIssueSearch(Map<String, Object> params) {
        String conditions = MapUtil.getString(params, "conditions");
        //  해당 업무 공간에서 사용자의 이슈 검색 조건을 조회한다.
        IssueSearch saveIssueSearch = this.findByUserIdAndWorkspaceId();
        if (saveIssueSearch == null) {
            IssueSearch issueSearch = new IssueSearch();
            Workspace workspace = this.workspaceService.getWorkspace(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
            User user = this.userService.getUser(this.webAppUtil.getLoginId());
            issueSearch.setWorkspace(workspace);
            issueSearch.setUser(user);
            issueSearch.setConditions(conditions);
            return this.issueSearchRepository.saveAndFlush(issueSearch);
        }
        else {
            saveIssueSearch.setConditions(conditions);
            return this.issueSearchRepository.saveAndFlush(saveIssueSearch);
        }
    }
    //  해당 업무 공간에서 사용자의 이슈 검색 조건을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public IssueSearch findByUserIdAndWorkspaceId() {
        return this.issueSearchRepository.findByUserIdAndWorkspaceId(this.webAppUtil.getLoginId(),
                this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
    }
    //  저장된 이슈 검색 조건을 조회 후 즉시 삭제한다 - 이슈 번호만 검색 조건 저장해서 사용
    @Override
    @Transactional
    public void detailIssueSearch(Map<String, Object> resJsonData) {
        //  해당 업무 공간에서 사용자의 이슈 검색 조건을 조회한다.
        IssueSearch issueSearch = this.findByUserIdAndWorkspaceId();
        if (issueSearch != null) {
            resJsonData.put(Constants.RES_KEY_CONTENTS, issueSearch.getConditions());
            issueSearch.setConditions(null);
            this.issueSearchRepository.saveAndFlush(issueSearch);
        }
        else {
            resJsonData.put(Constants.RES_KEY_CONTENTS, "");
        }
    }
}
src/main/java/kr/wisestone/owl/service/impl/IssueServiceImpl.java
New file
@@ -0,0 +1,2336 @@
package kr.wisestone.owl.service.impl;
import com.google.common.collect.Lists;
import kr.wisestone.owl.common.ExcelConditionCheck;
import kr.wisestone.owl.config.CommonConfiguration;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.ElasticSearchConstants;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.*;
import kr.wisestone.owl.domain.enumType.CustomFieldType;
import kr.wisestone.owl.domain.enumType.EmailType;
import kr.wisestone.owl.domain.enumType.IssueHistoryType;
import kr.wisestone.owl.domain.enumType.IssueStatusType;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.mapper.IssueMapper;
import kr.wisestone.owl.mapper.ProjectMapper;
import kr.wisestone.owl.repository.IssueRepository;
import kr.wisestone.owl.service.*;
import kr.wisestone.owl.util.*;
import kr.wisestone.owl.util.DateUtil;
import kr.wisestone.owl.vo.*;
import kr.wisestone.owl.web.condition.IssueCondition;
import kr.wisestone.owl.web.condition.IssueTypeCustomFieldCondition;
import kr.wisestone.owl.web.condition.ProjectCondition;
import kr.wisestone.owl.web.form.IssueCommentForm;
import kr.wisestone.owl.web.form.IssueForm;
import kr.wisestone.owl.web.view.ExcelView;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.poi.ss.usermodel.*;
import org.jsoup.Jsoup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.locks.Condition;
@Service
public class IssueServiceImpl extends AbstractServiceImpl<Issue, Long, JpaRepository<Issue, Long>> implements IssueService {
    private static final Logger log = LoggerFactory.getLogger(IssueServiceImpl.class);
    @Autowired
    private IssueRepository issueRepository;
    @Autowired
    private ProjectService projectService;
    @Autowired
    private IssueStatusService issueStatusService;
    @Autowired
    private IssueTypeService issueTypeService;
    @Autowired
    private PriorityService priorityService;
    @Autowired
    private SeverityService severityService;
    @Autowired
    private CommonConfiguration configuration;
    @Autowired
    private IssueNumberGeneratorService issueNumberGeneratorService;
    @Autowired
    private AttachedFileService attachedFileService;
    @Autowired
    private IssueCustomFieldValueService issueCustomFieldValueService;
    @Autowired
    private IssueUserService issueUserService;
    @Autowired
    private CustomFieldService customFieldService;
    @Autowired
    private IssueTypeCustomFieldService issueTypeCustomFieldService;
    @Autowired
    private UserService userService;
    @Autowired
    private IssueCommentService issueCommentService;
    @Autowired
    private IssueHistoryService issueHistoryService;
    @Autowired
    private ProjectRoleUserService projectRoleUserService;
    @Autowired
    private IssueRiskService issueRiskService;
    @Autowired
    private WorkspaceService workspaceService;
    @Autowired
    private SystemEmailService systemEmailService;
    @Autowired
    private IssueVersionService issueVersionService;
    @Autowired
    private IssueReservationService issueReservationService;
    @Autowired
    private UserWorkspaceService userWorkspaceService;
    @Autowired
    private IssueRelationService issueRelationService;
    @Autowired
    private ExcelView excelView;
    @Autowired
    private IssueMapper issueMapper;
    @Autowired
    private ExcelConditionCheck excelConditionCheck;
    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;
    @Override
    protected JpaRepository<Issue, Long> getRepository() {
        return this.issueRepository;
    }
    private static final int EXCEL_DOWNLOAD_MAX_ROWS = 10000;   //  excel download 제한
    private static final int EXCEL_IMPORT_MAX_ROWS = 10000; //  excel import 제한
    @Override
    @Transactional
    public void addIssueVersion(Long id) {
        Issue issue = this.getIssue(id);
        //  이슈 버전 생성
        this.issueVersionService.addIssueVersion(issue);
    }
    //  이슈를 생성한다.
    @Override
    @Transactional
    public Issue addIssue(IssueForm issueForm, List<MultipartFile> multipartFiles) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        //  프로젝트 유효성 체크
        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());
        //  이슈 상태 유형이 '대기' 인 이슈 상태 가져오기
        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);
        issue.setIssueNumber(this.issueNumberGeneratorService.generateIssueNumber(project));    //  각 프로젝트의 고유 이슈 번호 생성
        this.issueRepository.saveAndFlush(issue);
        issue.setReverseIndex(issue.getId() * -1);  //  쿼리 속도 개선을 위해 리버스 인덱스 생성
        //  담당자 지정
        this.issueUserService.modifyIssueUser(issue, project.getWorkspace(), issueForm.getUserIds());
        //  multipartFile 을 file Map List 객체로 변경한다.
        List<Map<String, Object>> convertFileMaps = this.convertMultipartFileToFile(multipartFiles);
        //  첨부 파일 저장
        this.attachedFileService.addAttachedFile(convertFileMaps, issue, this.webAppUtil.getLoginUser().getAccount());
        //  텍스트 에디터에 첨부한 파일을 이슈와 연결
        this.checkNotHaveIssueIdAttachedFile(issue, issueForm);
        //  사용자 정의 필드 저장
        this.issueCustomFieldValueService.modifyIssueCustomFieldValue(issue, issueForm.getIssueCustomFields());
        //  이슈 이력 생성
        this.issueHistoryService.addIssueHistory(issue, IssueHistoryType.ADD, null);
        //  이슈 위험 관리 생성
        this.issueRiskService.addIssueRisk(issue, project.getWorkspace());
        //  영속성 컨텍스트 비우기
        this.clear();
        //  이슈 생성, 삭제시 예약 이메일에 등록해놓는다.
        this.reservationIssueEmail(issue.getId(), EmailType.ISSUE_ADD);
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_ADD));
        return issue;
    }
    //  이슈 생성, 삭제시 예약 이메일에 등록해놓는다.
    private void reservationIssueEmail(Long id, EmailType emailType) {
        Issue issue = this.getIssue(id);
        Map<String, Object> issueMap = new HashMap<>();
        //  이슈 정보를 이메일 전송에 사용하기 위해 Map 형태로 변환한다.
        this.makeIssueMapToIssue(issue, issueMap);
        Map<String, Object> projectRoleUserMap = new HashMap<>();
        projectRoleUserMap.put("id", issue.getProject().getId());
        projectRoleUserMap.put("statuses", Lists.newArrayList("02"));   //  관리자 조회
        //  관리자 정보 셋팅
        List<Map<String, Object>> projectRoleUsers = this.projectRoleUserService.findProjectRoleUser(projectRoleUserMap);
        if (projectRoleUsers != null && !projectRoleUsers.isEmpty()) {
            for (Map<String, Object> projectRoleUser : projectRoleUsers) {
                UserVo userVo = ConvertUtil.convertMapToClass(projectRoleUser, UserVo.class);
                //  이슈 생성 알림 메일 전송
                this.systemEmailService.reservationEmail(new String[]{userVo.getAccount()}, emailType, issueMap);
            }
        }
    }
    //  이슈 정보를 이메일 전송에 사용하기 위해 Map 형태로 변환한다.
    private void makeIssueMapToIssue(Issue issue, Map<String, Object> issueMap) {
        issueMap.put("title", issue.getTitle());
        issueMap.put("issueNumber", issue.getIssueNumber());
        issueMap.put("issueTypeName", issue.getIssueType().getName());
        issueMap.put("issueStatusName", issue.getIssueStatus().getName());
        //  담당자
        StringBuilder assigneeBuilder = new StringBuilder();
        for (IssueUser issueUser : issue.getIssueUsers()) {
            assigneeBuilder.append(issueUser.getUser().getName());
            assigneeBuilder.append("(");
            assigneeBuilder.append(CommonUtil.decryptAES128(issueUser.getUser().getAccount()));
            assigneeBuilder.append(")");
            assigneeBuilder.append("\n");
        }
        issueMap.put("assignees", assigneeBuilder.toString());
        //  기간
        if (!StringUtils.isEmpty(issue.getStartDate())) {
            issueMap.put("period", issue.getStartDate() + " ~ " + issue.getCompleteDate());
        }
        issueMap.put("severityName", issue.getSeverity().getName());
        issueMap.put("priorityName", issue.getPriority().getName());
        issueMap.put("projectName", issue.getProject().getName());
        issueMap.put("projectKey", issue.getProject().getProjectKey());
        User user = this.userService.getUser(issue.getRegisterId());
        StringBuilder registerBuilder = new StringBuilder();
        registerBuilder.append(user.getName());
        registerBuilder.append("(");
        registerBuilder.append(CommonUtil.decryptAES128(user.getAccount()));
        registerBuilder.append(")");
        issueMap.put("register", registerBuilder.toString());
        Map<String, Object> customField = new HashMap<>();
        List<IssueCustomFieldValueVo> issueCustomFieldValueVos = this.issueCustomFieldValueService.findByIssueId(issue.getId());
        for (IssueCustomFieldValueVo issueCustomFieldValueVo : issueCustomFieldValueVos) {
            //  이미 데이터가 존재
            if (customField.get(issueCustomFieldValueVo.getCustomFieldVo().getName()) != null) {
                List<String> useValues = (List<String>) customField.get(issueCustomFieldValueVo.getCustomFieldVo().getName());
                useValues.add(issueCustomFieldValueVo.getUseValue());
                customField.put(issueCustomFieldValueVo.getCustomFieldVo().getName(), useValues);
            } else {
                if (issueCustomFieldValueVo.getCustomFieldVo().getCustomFieldType().equals(CustomFieldType.INPUT.toString())) {
                    customField.put(issueCustomFieldValueVo.getCustomFieldVo().getName(), issueCustomFieldValueVo.getUseValue());
                } else {
                    customField.put(issueCustomFieldValueVo.getCustomFieldVo().getName(), Lists.newArrayList(issueCustomFieldValueVo.getUseValue()));
                }
            }
        }
        List<Map<String, Object>> customFields = Lists.newArrayList();
        Iterator<String> iterator = customField.keySet().iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            Map<String, Object> result = new HashMap<>();
            result.put("name", key);
            result.put("useValue", customField.get(key));
            customFields.add(result);
        }
        issueMap.put("customFields", customFields);
        issueMap.put("description", issue.getDescription());
        StringBuilder attachedFileBuilder = new StringBuilder();
        List<AttachedFile> attachedFiles = this.attachedFileService.findByIssueId(issue.getId());
        for (AttachedFile attachedFile : attachedFiles) {
            attachedFileBuilder.append("<a href='");
            attachedFileBuilder.append(attachedFile.getPath());
            attachedFileBuilder.append("'>");
            attachedFileBuilder.append(attachedFile.getName());
            attachedFileBuilder.append("</a>");
            attachedFileBuilder.append("\n");
        }
        issueMap.put("attachedFiles", attachedFileBuilder.toString());
    }
    //  제목 유효성 체크
    private void verifyTitle(String title) {
        if (StringUtils.isEmpty(title)) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_NO_TITLE));
        }
        if (title.length() > 300) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_TITLE_MAX_LENGTH_OUT));
        }
    }
    //  날짜 유효성 체크
    private void checkStartCompleteDate(String startDate, String completeDate) {
        if (!StringUtils.isEmpty(startDate) && !StringUtils.isEmpty(completeDate)) {
            Date start = DateUtil.convertStrToDate(startDate, "yy-MM-dd");
            Date end = DateUtil.convertStrToDate(completeDate, "yy-MM-dd");
            if (start.getTime() > end.getTime()) {
                throw new OwlRuntimeException(
                        this.messageAccessor.getMessage(MsgConstants.DATE_PICKER_NOT_AVAILABLE));
            }
        }
    }
    //  텍스트 에디터에 첨부한 파일을 이슈와 연결
    private void checkNotHaveIssueIdAttachedFile(Issue issue, IssueForm issueForm) {
        if (!issueForm.getAttachedFileIds().isEmpty()) {
            this.attachedFileService.connectIssueIdAttachedFile(issue, issueForm);
        }
    }
    //  이슈 목록을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public List<IssueVo> findIssue(Map<String, Object> resJsonData,
                                   IssueCondition issueCondition, Pageable pageable) {
        //  검색 조건을 만든다
        if (!this.makeIssueSearchCondition(issueCondition, Lists.newArrayList("01", "02", "03"), pageable)) {
            //  이슈 목록을 찾지 못할 경우 기본 정보로 리턴한다.
            this.notFoundIssueList(resJsonData, pageable);
            return Lists.newArrayList();
        }
        Set<String> issueIds = new HashSet<>(); //  사용자 정의 필드 검색시 나오는 이슈 아이디 저장 컬렉션
        //  사용자 정의 필드를 사용한 이슈를 찾는다. 만약 이슈가 없다면 여기서 이슈 조회가 끝난다.
        if (!this.searchUseCustomFields(issueCondition, issueIds, resJsonData, pageable)) {
            //  이슈 목록을 찾지 못할 경우 기본 정보로 리턴한다.
            this.notFoundIssueList(resJsonData, pageable);
            return Lists.newArrayList();
        }
        //  튜닝 전 - 1.3 / 1.2 / 1.1
        //  튜닝 후 (단일/다중 검색 조건 3개 기준) - 0.49 / 0.41 / 0.47 / 0.41
        List<IssueVo> issueVos = Lists.newArrayList();  //  이슈 목록 데이터 저장 컬렉션
        //  사용자 정의 필드로 검색한 이슈 아이디 값
        List<String> issueKeys = Lists.newArrayList(issueIds);
        issueCondition.setIssueIds(issueKeys);
        List<Map<String, Object>> results = this.issueMapper.find(issueCondition);
        //  튜닝 전 - 0.8, 0.9, 0.9, 0.9, 0.9
        StopWatch serviceStart = new StopWatch();
        serviceStart.start();
        Long totalCount = this.issueMapper.count(issueCondition);
        //  튜닝 전 - 1.1, 1.1, 1.3, 1.2
        serviceStart.stop();
        log.debug("serviceENd1 : " + serviceStart.getTime());
        int totalPage = (int) Math.ceil((totalCount - 1) / pageable.getPageSize()) + 1;
        //  이슈 아이디 초기화
        issueCondition.setIssueIds(Lists.newArrayList());
        //  Map 에 있는 데이터를 IssueVo 데이터로 변환한다.
        this.setMapToIssueVo(results, issueVos, issueCondition);
        resJsonData.put(Constants.RES_KEY_CONTENTS, issueVos);
        resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(),
                totalPage, totalCount));
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_FIND));
        return issueVos;
    }
    //  이슈 목록을 조회한다(차트용 - 연관일감포함)
    @Override
    @Transactional(readOnly = true)
    public List<IssueVo> findChartIssue(Map<String, Object> resJsonData,
                                   IssueCondition issueCondition, Pageable pageable) {
        //  검색 조건을 만든다
        if (!this.makeIssueSearchCondition(issueCondition,Lists.newArrayList("01", "02", "03"), pageable)) {
            //  이슈 목록을 찾지 못할 경우 기본 정보로 리턴한다.
            this.notFoundIssueList(resJsonData, pageable);
            return Lists.newArrayList();
        }
        Set<String> issueIds = new HashSet<>(); //  사용자 정의 필드 검색시 나오는 이슈 아이디 저장 컬렉션
        //  사용자 정의 필드를 사용한 이슈를 찾는다. 만약 이슈가 없다면 여기서 이슈 조회가 끝난다.
        if (!this.searchUseCustomFields(issueCondition, issueIds, resJsonData, pageable)) {
            //  이슈 목록을 찾지 못할 경우 기본 정보로 리턴한다.
            this.notFoundIssueList(resJsonData, pageable);
            return Lists.newArrayList();
        }
        //  튜닝 전 - 1.3 / 1.2 / 1.1
        //  튜닝 후 (단일/다중 검색 조건 3개 기준) - 0.49 / 0.41 / 0.47 / 0.41
        List<IssueVo> issueVos = Lists.newArrayList();  //  이슈 목록 데이터 저장 컬렉션
        //  사용자 정의 필드로 검색한 이슈 아이디 값
        List<String> issueKeys = Lists.newArrayList(issueIds);
        issueCondition.setIssueIds(issueKeys);
        List<Map<String, Object>> results = this.issueMapper.find(issueCondition);
        int totalCount = results.size();
        int totalPage = (int) Math.ceil((totalCount - 1) / pageable.getPageSize()) + 1;
        //  이슈 아이디 초기화
        issueCondition.setIssueIds(Lists.newArrayList());
        //  Map 에 있는 데이터를 IssueVo 데이터로 변환한다.
        this.setMapToIssueVoForChart(results, issueVos, issueCondition);
        resJsonData.put(Constants.RES_KEY_CONTENTS, issueVos);
        resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(),
                totalPage, totalCount));
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_FIND));
        return issueVos;
    }
    //  이슈 목록을 조회한다(차트용 - 연관일감)
    @Override
    @Transactional(readOnly = true)
    public List<IssueVo> findChartIssue(Map<String, Object> resJsonData,
                                        ProjectCondition projectCondition, Pageable pageable) {
        IssueCondition issueCondition = new IssueCondition();
        //  검색 조건을 만든다
        if (!this.makeIssueSearchCondition(issueCondition, projectCondition, pageable)) {
            //  이슈 목록을 찾지 못할 경우 기본 정보로 리턴한다.
            this.notFoundIssueList(resJsonData, pageable);
            return Lists.newArrayList();
        }
        Set<String> issueIds = new HashSet<>(); //  사용자 정의 필드 검색시 나오는 이슈 아이디 저장 컬렉션
        List<IssueVo> issueVos = Lists.newArrayList();  //  이슈 목록 데이터 저장 컬렉션
        //  사용자 정의 필드로 검색한 이슈 아이디 값
        List<String> issueKeys = Lists.newArrayList(issueIds);
        issueCondition.setIssueIds(issueKeys);
        List<Map<String, Object>> results = this.issueMapper.find(issueCondition);
        int totalCount = results.size();
        int totalPage = (int) Math.ceil((totalCount - 1) / pageable.getPageSize()) + 1;
        //  이슈 아이디 초기화
        issueCondition.setIssueIds(Lists.newArrayList());
        //  Map 에 있는 데이터를 IssueVo 데이터로 변환한다.
        this.setMapToIssueVoForChart(results, issueVos, issueCondition);
        resJsonData.put(Constants.RES_KEY_CONTENTS, issueVos);
        resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(),
                totalPage, totalCount));
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_FIND));
        return issueVos;
    }
    // Map 에 있는 데이터를 IssueVo 데이터로 변환한다. 차트용
    private void setMapToIssueVoForChart(List<Map<String, Object>> results, List<IssueVo> issueVos, IssueCondition issueCondition) {
        for (Map<String, Object> result : results) {
            IssueVo issueVo = ConvertUtil.convertMapToClass(result, IssueVo.class);
            issueVos.add(issueVo);
            issueCondition.addIssueIds(String.valueOf(issueVo.getId()));
        }
        for (IssueVo issueVo : issueVos) {
            this.setRelationIssue(issueVo, issueVo.getId());
        }
    }
    //  Map 에 있는 데이터를 IssueVo 데이터로 변환한다.
    private void setMapToIssueVo(List<Map<String, Object>> results, List<IssueVo> issueVos, IssueCondition issueCondition) {
        for (Map<String, Object> result : results) {
            IssueVo issueVo = ConvertUtil.convertMapToClass(result, IssueVo.class);
            issueVos.add(issueVo);
            issueCondition.addIssueIds(String.valueOf(issueVo.getId()));
        }
        //  이슈 사용자 정보 추가
        this.setIssueUserList(issueVos, issueCondition);
        //  등록자 정보 추가
        this.setRegister(issueVos);  //  담당자 정보 셋팅
        //  사용자 정의 필드 정보 추가
        this.setIssueCustomFieldValue(issueVos, issueCondition);
    }
        //  검색 조건을 만든다
    private boolean makeIssueSearchCondition(IssueCondition condition, List<String> projectStatues, Pageable pageable) {
        if (pageable != null) {
            condition.setPage(pageable.getPageNumber() * pageable.getPageSize());
            condition.setPageSize(pageable.getPageSize());
        }
        condition.setLoginUserId(this.webAppUtil.getLoginId());
        condition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        //  프로젝트 키가 존재할 경우 프로젝트 키에 해당하는 프로젝트를 조회하고 검색 조건에 셋팅한다.
        if (!this.getProjectByProjectKey(condition.getProjectKey(), condition)) {
            return false;
        }
        //  프로젝트를 선택하지 않았으면 해당 업무 공간에서 참여하고 있는 프로젝트를 찾는다.
        if (condition.getProjectIds().size() < 1) {
            List<Map<String, Object>> projects = this.projectService.findByWorkspaceIdAndIncludeProjectAll(projectStatues, condition.getProjectType());
            List<Long> projectIds = Lists.newArrayList();
            for (Map<String, Object> result : projects) {
                Long projectId = MapUtil.getLong(result, "id");
                if (projectId != null) {
                    projectIds.add(projectId);
                }
            }
            condition.setProjectIds(projectIds);
            if (projectIds.size() < 1) {
                return false;
            }
        }
        return true;
    }
    //  검색 조건을 만든다
    private boolean makeIssueSearchCondition(IssueCondition condition, ProjectCondition projectCondition, Pageable pageable) {
        if (pageable != null) {
            condition.setPage(pageable.getPageNumber() * pageable.getPageSize());
            condition.setPageSize(pageable.getPageSize());
        }
        condition.setLoginUserId(this.webAppUtil.getLoginId());
        condition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        projectCondition.setWorkspaceId(condition.getWorkspaceId());
        //  프로젝트 키가 존재할 경우 프로젝트 키에 해당하는 프로젝트를 조회하고 검색 조건에 셋팅한다.
        if (!this.getProjectByProjectKey(condition.getProjectKey(), condition)) {
            return false;
        }
        //  프로젝트를 선택하지 않았으면 해당 업무 공간에서 참여하고 있는 프로젝트를 찾는다.
        if (condition.getProjectIds().size() < 1) {
            List<Map<String, Object>> projects = null;
            if (this.userWorkspaceService.checkWorkspaceManager()) {
                projects = this.projectMapper.findByWorkspaceManagerAll(projectCondition);
            } else  {
                projects = this.projectService.findByWorkspaceIdAndIncludeProjectAll(projectCondition);
            }
            List<Long> projectIds = Lists.newArrayList();
            for (Map<String, Object> result : projects) {
                Long projectId = MapUtil.getLong(result, "id");
                if (projectId != null) {
                    projectIds.add(projectId);
                }
            }
            condition.setProjectIds(projectIds);
            if (projectIds.size() < 1) {
                return false;
            }
        }
        return true;
    }
    //  프로젝트 키가 존재할 경우 프로젝트 키에 해당하는 프로젝트를 조회하고 검색 조건에 셋팅한다.
    private boolean getProjectByProjectKey(String projectKey, IssueCondition condition) {
        if (!StringUtils.isEmpty(projectKey)) {
            Project project = this.projectService.findByProjectKey(projectKey);
            if (project != null) {
                //  이미 프로젝트를 선택했을 경우에 프로젝트키로 검색한 프로젝트가 포함되어 있지 않으면 false
                if (condition.getProjectIds().size() > 0) {
                    if (condition.getProjectIds().contains(project.getId())) {
                        condition.setProjectIds(Lists.newArrayList());
                    } else {
                        return false;
                    }
                }
                condition.addProjectIds(project.getId());
            } else {
                return false;
            }
        }
        return true;
    }
    //  이슈 목록을 찾지 못할 경우 기본 정보로 리턴한다.
    private void notFoundIssueList(Map<String, Object> resJsonData, Pageable pageable) {
        resJsonData.put(Constants.RES_KEY_CONTENTS, Lists.newArrayList());
        resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(),
                0, 0));
    }
    //  사용자 정의 필드를 사용한 이슈를 찾는다. 만약 이슈가 없다면 여기서 이슈 조회가 끝난다.
    private boolean searchUseCustomFields(IssueCondition condition, Set<String> issueIds, Map<String, Object> resJsonData, Pageable pageable) {
        //  사용자 정의 필드 값이 검색 옵션으로 있었을 때 조회된 이슈가 없으면 조회를 더 이상 할 필요가 없다.
        //  사용자 정의 필드를 사용한 이슈를 찾는다.
        boolean customFieldSearch = this.issueCustomFieldValueService.find(condition, issueIds);
        //  사용자 정의 필드 값이 존재하여 검색을 했을 때 검색된 이슈가 없으면 여기서 종료한다.
        if (customFieldSearch && issueIds.size() < 1) {
            resJsonData.put(Constants.RES_KEY_CONTENTS, Lists.newArrayList());
            if (pageable != null) {
                resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(),
                        0, 0));
            }
            return false;
        }
        return true;
    }
    //  이슈 담당자 정보를 셋팅한다.
    private void setIssueUserList(List<IssueVo> issueVos, IssueCondition issueCondition) {
        if (issueVos.size() < 1) {
            return;
        }
        List<Map<String, Object>> issueUsers = this.issueMapper.findIssueUser(issueCondition);
        Map<String, Object> issueConverterUsers = new HashMap<>();
        //  이슈에 해당하는 이슈 담당자 정보 셋팅
        for (Map<String, Object> issueUser : issueUsers) {
            String issueId = MapUtil.getString(issueUser, "issueId");
            if (MapUtil.getObject(issueConverterUsers, issueId) != null) {
                List<UserVo> users = (List) MapUtil.getObject(issueConverterUsers, issueId);
                users.add(new UserVo(MapUtil.getLong(issueUser, "id"), MapUtil.getString(issueUser, "name"), CommonUtil.decryptAES128(MapUtil.getString(issueUser, "account")), MapUtil.getString(issueUser, "profile")));
            } else {
                List<UserVo> users = Lists.newArrayList(new UserVo(MapUtil.getLong(issueUser, "id"), MapUtil.getString(issueUser, "name"), CommonUtil.decryptAES128(MapUtil.getString(issueUser, "account")),
                        MapUtil.getString(issueUser, "profile")));
                issueConverterUsers.put(issueId, users);
            }
        }
        //  이슈Vo에 담당자 정보를 셋팅
        for (IssueVo issueVo : issueVos) {
            if (MapUtil.getObject(issueConverterUsers, String.valueOf(issueVo.getId())) != null) {
                List<UserVo> userVos = (List) MapUtil.getObject(issueConverterUsers, String.valueOf(issueVo.getId()));
                issueVo.setUserVos(userVos);
            }
            //  이슈 수정 권한을 갖고 있는지 확인
            if (this.checkHasPermission(issueVo, issueVo.getUserVos())) {
                issueVo.setModifyPermissionCheck(Boolean.TRUE);
            }
        }
    }
    //  이슈 상세 정보를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public void detailIssue(Map<String, Object> resJsonData, IssueCondition issueCondition) {
        IssueVo issueVo = new IssueVo();
        if (issueCondition.getId() != null) {
            Issue issue = this.getIssue(issueCondition.getId());
            issueVo = ConvertUtil.copyProperties(issue, IssueVo.class);
            switch (issueCondition.getDeep()) {
                case "01": //  프로젝트, 이슈 유형, 이슈 상태,  우선순위, 중요도, 담당자, 첨부파일, 사용자 정의 필드 정보를 셋팅한다.
                    issueVo.setProjectVo(ConvertUtil.copyProperties(issue.getProject(), ProjectVo.class));
                    issueVo.setIssueTypeVo(ConvertUtil.copyProperties(issue.getIssueType(), IssueTypeVo.class));
                    issueVo.setIssueStatusVo(ConvertUtil.copyProperties(issue.getIssueStatus(), IssueStatusVo.class));
                    issueVo.setPriorityVo(ConvertUtil.copyProperties(issue.getPriority(), PriorityVo.class));
                    issueVo.setSeverityVo(ConvertUtil.copyProperties(issue.getSeverity(), SeverityVo.class));
                    this.setRegister(issue, issueVo);   //  등록자 정보 셋팅
                    this.setIssueUser(issue, issueVo);  //  담당자 정보 셋팅
                    this.setAttachedFiles(issue, issueVo);  //  첨부 파일 정보 셋팅
                    this.setIssueCustomFields(issue, issueVo);  //  사용자 정의 필드 값 정보 셋팅
                    this.setRelationIssue(issue, issueVo);        //연관 일감 셋팅
                    break;
                case "02": //  프로젝트, 이슈 유형, 이슈 상태,  우선순위, 중요도, 담당자, 첨부파일, 사용자 정의 필드 정보, 댓글, 기록을 셋팅한다.
                    this.setIssueDetail(issueVo, issue);    //  이슈 상세 정보를 셋팅한다.
                    break;
            }
        }
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_DETAIL));
        resJsonData.put(Constants.RES_KEY_CONTENTS, issueVo);
    }
    //  이슈 상세 정보를 셋팅한다.
    @Override
    @Transactional(readOnly = true)
    public void setIssueDetail(IssueVo issueVo, Issue issue) {
        issueVo.setProjectVo(ConvertUtil.copyProperties(issue.getProject(), ProjectVo.class));
        issueVo.setIssueTypeVo(ConvertUtil.copyProperties(issue.getIssueType(), IssueTypeVo.class));
        IssueStatusVo issueStatusVo = ConvertUtil.copyProperties(issue.getIssueStatus(), IssueStatusVo.class, "issueStatusType");
        issueStatusVo.setIssueStatusType(issue.getIssueStatus().getIssueStatusType().toString());
        issueVo.setIssueStatusVo(issueStatusVo);
        issueVo.setPriorityVo(ConvertUtil.copyProperties(issue.getPriority(), PriorityVo.class));
        issueVo.setSeverityVo(ConvertUtil.copyProperties(issue.getSeverity(), SeverityVo.class));
        this.setRegister(issue, issueVo);   //  등록자 정보 셋팅
        this.setIssueUser(issue, issueVo);  //  담당자 정보 셋팅
        this.setAttachedFiles(issue, issueVo);  //  첨부 파일 정보 셋팅
        this.setIssueCustomFields(issue, issueVo);  //  사용자 정의 필드 값 정보 셋팅
        this.setIssueComments(issue, issueVo);  //  댓글 정보 셋팅
        this.setIssueHistory(issue, issueVo);   //  이슈 기록 정보 셋팅
        this.setRelationIssue(issue, issueVo);        //연관 일감 셋팅
    }
    //  등록자 정보 추가
    private void setRegister(List<IssueVo> issueVos) {
        for (IssueVo issueVo : issueVos) {
            //  사용자 패스워드를 삭제하고 사용자 계정을 복호화한다.
            issueVo.setRegisterVo(this.userService.removeSensitiveUser(issueVo.getRegisterId()));
        }
    }
    //  이슈 등록자 정보를 셋팅한다.
    private void setRegister(Issue issue, IssueVo issueVo) {
        UserVo userVo = this.userService.removeSensitiveUser(issue.getRegisterId());
        issueVo.setRegisterVo(userVo);
        //  등록자는 항상 수정 가능.
        if (userVo.getId().equals(this.webAppUtil.getLoginId())) {
            issueVo.setModifyPermissionCheck(Boolean.TRUE);
        }
    }
    // 연관 이슈 정보를 셋팅한다
    private void setRelationIssue(Issue issue, IssueVo issueVo) {
        Set<IssueRelation> issueRelations = issue.getIssueRelations();
        if (issue != null && issueVo != null && issueRelations.size() > 0) {
            for (IssueRelation issueRelation : issueRelations) {
                IssueRelationVo issueRelationVo = ConvertUtil.copyProperties(issueRelation, IssueRelationVo.class);
                Issue relationIssue = issueRelation.getRelationIssue();
                IssueVo relIssueVo = ConvertUtil.copyProperties(relationIssue, IssueVo.class);
                Project project = this.projectService.getProject(relationIssue.getProject().getId());
                relIssueVo.setProjectId(project.getId());
                relIssueVo.setProjectKey(project.getProjectKey());
                relIssueVo.setIssueNumber(relationIssue.getIssueNumber());
                issueRelationVo.setIssueRelation(relIssueVo);
                issueRelationVo.setTitle(relationIssue.getTitle());
                issueVo.addIssueRelationVo(issueRelationVo);
            }
        } else {
            issue.clearIssueRelations();
        }
    }
    //  이슈 담당자 정보를 셋팅한다.
    private void setIssueUser(Issue issue, IssueVo issueVo) {
        List<UserVo> userVos = Lists.newArrayList();
        for (IssueUser issueUser : issue.getIssueUsers()) {
            UserVo userVo = ConvertUtil.copyProperties(issueUser.getUser(), UserVo.class, "password");
            userVo.setByName(userVo.getName() + "(" + CommonUtil.decryptAES128(userVo.getAccount()) + ")");
            userVo.setAccount(CommonUtil.decryptAES128(userVo.getAccount()));
            userVos.add(userVo);
            //  담당자가 있을 경우 담당자만 수정 가능.
            if (userVo.getId().equals(this.webAppUtil.getLoginId())) {
                issueVo.setModifyPermissionCheck(Boolean.TRUE);
            }
        }
        //  스케쥴러에서 실행될 경우 오류 발생하므로 권한 체크하지 않는다.
        if (this.webAppUtil.getLoginId() != null) {
            //  업무 공간 관리자일 경우 수정 권한을 갖는다.
            if (this.userWorkspaceService.checkWorkspaceManager()) {
                issueVo.setModifyPermissionCheck(Boolean.TRUE);
            }
            //  프로젝트 관리자일 경우 해당 프로젝트에 등록된 이슈는 수정 권한을 갖는다.
            if (this.projectRoleUserService.checkProjectManager(issue.getProject())) {
                issueVo.setModifyPermissionCheck(Boolean.TRUE);
            }
        }
        //  담당자가 없으면 모든 사용자가 수정 가능.
        if (issue.getIssueUsers().size() < 1) {
            issueVo.setModifyPermissionCheck(Boolean.TRUE);
        }
        issueVo.setUserVos(userVos);
    }
    //  이슈 첨부파일 정보를 셋팅한다.
    private void setAttachedFiles(Issue issue, IssueVo issueVo) {
        List<AttachedFileVo> attachedFileVos = Lists.newArrayList();
        for (AttachedFile attachedFile : issue.getAttachedFiles()) {
            AttachedFileVo attachedFileVo = ConvertUtil.copyProperties(attachedFile, AttachedFileVo.class, "fileType");
            attachedFileVo.setFileType(attachedFile.getFileType().toString());
            attachedFileVos.add(attachedFileVo);
        }
        issueVo.setAttachedFileVos(attachedFileVos);
    }
    //  이슈(이슈 유형)에 연결된 사용자 정의 필드 정보를 셋팅한다.
    private void setIssueCustomFields(Issue issue, IssueVo issueVo) {
        //  해당 프로젝트의 이슈 유형에 연결된 사용자 정의 필드 정보를 가져온다.
        IssueTypeCustomFieldCondition issueTypeCustomFieldCondition = new IssueTypeCustomFieldCondition();
        issueTypeCustomFieldCondition.setProjectId(issue.getProject().getId());
        issueTypeCustomFieldCondition.setIssueTypeId(issue.getIssueType().getId());
        List<IssueTypeCustomFieldVo> issueTypeCustomFieldVos = this.issueTypeCustomFieldService.findIssueTypeCustomField(new HashMap<>(), issueTypeCustomFieldCondition);
        issueVo.setIssueTypeCustomFieldVos(issueTypeCustomFieldVos);
        //  이슈에서 사용된 사용자 정의 필드 값을 가져온다.
        List<IssueCustomFieldValueVo> issueCustomFieldValueVos = this.issueCustomFieldValueService.findByIssueId(issue.getId());
        issueVo.setIssueCustomFieldValueVos(issueCustomFieldValueVos);
    }
    //  이슈에 등록된 댓글 정보를 셋팅한다.
    private void setIssueComments(Issue issue, IssueVo issueVo) {
        issueVo.setIssueCommentVos(this.issueCommentService.findIssueComment(issue.getId()));
    }
    //  이슈 기록 정보를 셋팅한다.
    private void setIssueHistory(Issue issue, IssueVo issueVo) {
        issueVo.setIssueHistoryVos(this.issueHistoryService.findIssueHistory(issue.getId()));
    }
    //  이슈를 수정한다.
    @Override
    @Transactional
    public Issue modifyIssue(IssueForm issueForm, List<MultipartFile> multipartFiles) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        //  이슈 수정 권한 체크
        this.verifyIssueModifyPermission(issueForm.getId());
        //  프로젝트 유효성 체크
        Project project = this.projectService.getProject(issueForm.getProjectId());
        //  이슈 상태 유효성 체크
        IssueStatus issueStatus = this.issueStatusService.getIssueStatus(issueForm.getIssueStatusId());
        //  이슈 유형 유효성 체크
        IssueType issueType = this.issueTypeService.getIssueType(issueForm.getIssueTypeId());
        //  우선순위 유효성 체크
        Priority priority = this.priorityService.getPriority(issueForm.getPriorityId());
        //  중요도 유효성 체크
        Severity severity = this.severityService.getSeverity(issueForm.getSeverityId());
        //  제목 유효성 체크
        this.verifyTitle(issueForm.getTitle());
        //  날짜 유효성 체크
        this.checkStartCompleteDate(issueForm.getStartDate(), issueForm.getCompleteDate());
        //  담당자 유효성 체크
        this.verifyIssueAssignee(project, issueForm);
        Issue issue = this.getIssue(issueForm.getId());
        //  변경 이력 정보 추출
        StringBuilder detectIssueChange = this.issueHistoryService.detectIssueChange(issue, issueForm, project, issueStatus, issueType, priority, severity, multipartFiles);
        //  프로젝트가 변경되면 이슈 넘버를 새로 따야 한다.
        this.checkChangeProject(project, issue);
        //  이슈 유형이 변경되었는지 확인하고 변경되었다면 이슈 상태 속성이 '대기' 인 이슈 상태로 교체한다.
        if (this.checkChangeIssueType(issueType, issueStatus, issue)) {
            issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(issueType.getWorkflow());
            //  이슈 상태 변경 이력 남기기 - 이력을 남기기 위해 issueForm 에 issueStatus Id 값을 저장.
            issueForm.setIssueStatusId(issueStatus.getId());
            this.issueHistoryService.detectIssueStatus(issue, issueForm, detectIssueChange, issueStatus);
        }
        ConvertUtil.copyProperties(issueForm, issue, "id");
        issue.setProject(project);
        issue.setIssueStatus(issueStatus);
        issue.setIssueType(issueType);
        issue.setPriority(priority);
        issue.setSeverity(severity);
        issue.setStartDate(issueForm.getStartDate());
        issue.setCompleteDate(issueForm.getCompleteDate());
        this.issueRepository.saveAndFlush(issue);
        //  담당자 지정
        this.issueUserService.modifyIssueUser(issue, project.getWorkspace(), issueForm.getUserIds());
        //  multipartFile 을 file Map List 객체로 변경한다.
        List<Map<String, Object>> convertFileMaps = this.convertMultipartFileToFile(multipartFiles);
        //  첨부 파일 저장 - 비동기로 작동
        this.attachedFileService.addAttachedFile(convertFileMaps, issue, this.webAppUtil.getLoginUser().getAccount());
        //  삭제된 첨부파일 처리
        this.attachedFileService.removeAttachedFiles(issueForm.getRemoveFiles());
        //  텍스트 에디터에 첨부한 파일을 이슈와 연결
        this.checkNotHaveIssueIdAttachedFile(issue, issueForm);
        //  사용자 정의 필드 저장
        this.issueCustomFieldValueService.modifyIssueCustomFieldValue(issue, issueForm.getIssueCustomFields());
        //  이슈 이력 등록
        if (!StringUtils.isEmpty(detectIssueChange.toString())) {
            this.issueHistoryService.addIssueHistory(issue, IssueHistoryType.MODIFY, detectIssueChange.toString());
        }
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_MODIFY));
        return issue;
    }
    //  multipartFile 을 file Map 객체로 변경한다.
    private List<Map<String, Object>> convertMultipartFileToFile(List<MultipartFile> multipartFiles) {
        List<Map<String, Object>> convertFileMaps = Lists.newArrayList();
        for (MultipartFile multipartFile : multipartFiles) {
            try {
                Map<String, Object> fileMap = CommonUtil.makeFileMap(multipartFile);
                convertFileMaps.add(fileMap);
            } catch (Exception e) {
                log.debug("multipartFile -> file 변환 오류" + e.getMessage());
            }
        }
        return convertFileMaps;
    }
    //  프로젝트가 변경되었는지 확인한다.
    private void checkChangeProject(Project newProject, Issue issue) {
        if (!issue.getProject().getId().equals(newProject.getId())) {
            //  각 프로젝트의 고유 이슈 번호 생성
            issue.setIssueNumber(this.issueNumberGeneratorService.generateIssueNumber(newProject));
        }
    }
    //  이슈 유형이 변경되었는지 확인하고 변경되었으면 이슈 상태를 대기 속성인 이슈 상태로 셋팅한다.
    private Boolean checkChangeIssueType(IssueType newIssueType, IssueStatus issueStatus, Issue issue) {
        if (!issue.getIssueType().getId().equals(newIssueType.getId())) {
            //  이슈 상태를 선택하지 않았을 때
            if (issueStatus == null) {
                return true;
            }
            //  이슈 상태의 속성이 '대기' 가 아닌 경우
            if (!issueStatus.getIssueStatusType().equals(IssueStatusType.READY)) {
                return true;
            } else {
                //  변경하는 이슈 유형의 워크플로우에 존재하는 상태 속성 '대기'와 동일한 상태인지 확인
                IssueStatus newReadyIssueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(newIssueType.getWorkflow());
                if (!newReadyIssueStatus.getId().equals(issueStatus.getId())) {
                    return true;
                }
            }
        }
        return false;
    }
    //  이슈 담당자로 지정될 사용자가 해당 프로젝트에 참여 하고 있는 사용자 인지 확인
    private void verifyIssueAssignee(Project project, IssueForm issueForm) {
        if (issueForm.getUserIds().size() > 0) {
            List<Long> trustUserIds = Lists.newArrayList(); //  참여 확인된 사용자
            for (Long userId : issueForm.getUserIds()) {
                boolean includeProject = false;
                for (ProjectRole projectRole : project.getProjectRoles()) {
                    ProjectRoleUser projectRoleUser = this.projectRoleUserService.findByProjectRoleIdAndUserId(projectRole.getId(), userId);
                    if (projectRoleUser != null) {
                        includeProject = true;
                        trustUserIds.add(userId);
                        break;
                    }
                }
                //  데이터 보정 작업 - 프로젝트에서 제외된 사용자는 담당자에서 제외 될 수 있도록 처리
                /*if (!includeProject) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.PROJECT_NOT_INCLUDE_USER));
                }*/
            }
            //  참여 확인된 사용자로 담당자 변경
            issueForm.setUserIds(trustUserIds);
        }
    }
    //  이슈 수정 권한 체크
    private void verifyIssueModifyPermission(Long issueId) {
        Issue issue = this.getIssue(issueId);
        //  이슈 수정 권한을 갖고 있는지 확인
        if (!this.checkHasPermission(ConvertUtil.copyProperties(issue, IssueVo.class), this.getIssueUserVos(issue))) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_MODIFY_PERMISSION));
        }
    }
    //  이슈에서 담당자 정보를 추출한다.
    private List<UserVo> getIssueUserVos(Issue issue) {
        List<UserVo> userVos = Lists.newArrayList();
        for (IssueUser issueUser : issue.getIssueUsers()) {
            UserVo userVo = ConvertUtil.copyProperties(issueUser.getUser(), UserVo.class, "password");
            userVos.add(userVo);
        }
        return userVos;
    }
    //  이슈 수정 권한을 갖고 있는지 확인
    private boolean checkHasPermission(IssueVo issueVo, List<UserVo> issueUserVos) {
        boolean hasPermission = false;
        //  업무 공간 관리자일 경우 수정 권한을 갖는다.
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.WORKSPACE_MANAGER, issueVo, null);
        //  프로젝트 관리자일 경우 해당 프로젝트에 등록된 이슈는 수정 권한을 갖는다.
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.PROJECT_MANAGER, issueVo, null);
        //   이슈 등록자일 경우 수정 권한을 갖는다.
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.REGISTER, issueVo, null);
        //  이슈 담당자일 경우 수정 권한을 갖는다.
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.ASSIGNEE, issueVo, issueUserVos);
        //  담당자가 없으면 모든 사용자가 수정 권한을 갖는다.
        return hasPermission;
    }
    //  이슈 수정 권한을 확인한다.
    private boolean checkIssueModifyPermission(Boolean hasPermission, String checkType, IssueVo issueVo, List<UserVo> issueUserVos) {
        if (!hasPermission) {
            switch (checkType) {
                case Issue.WORKSPACE_MANAGER:  //  업무 공간 관리자
                    //  업무 공간 관리자일 경우 수정 권한을 갖는다.
                    hasPermission = this.userWorkspaceService.checkWorkspaceManager();
                    break;
                case Issue.PROJECT_MANAGER:    //  프로젝트 관리자
                    Issue issue = this.getIssue(issueVo.getId());
                    //  프로젝트 관리자일 경우 해당 프로젝트에 등록된 이슈는 수정 권한을 갖는다.
                    hasPermission = this.projectRoleUserService.checkProjectManager(issue.getProject());
                    break;
                case Issue.REGISTER:   //  이슈 등록자
                    hasPermission = issueVo.getRegisterId().equals(this.webAppUtil.getLoginId());
                    break;
                case Issue.ASSIGNEE:
                    //  담당자가 없으면 모든 사용자가 수정 권한을 갖는다.
                    if (issueUserVos.size() < 1) {
                        hasPermission = true;
                        break;
                    }
                    //   이슈 담당자 여부 확인
                    for (UserVo issueUserVo : issueUserVos) {
                        if (issueUserVo.getId().equals(this.webAppUtil.getLoginId())) {
                            hasPermission = true;
                            break;
                        }
                    }
                    break;
            }
        }
        return hasPermission;
    }
    //  이슈 상태 변경
    @Override
    @Transactional
    public void modifyIssueStatus(IssueForm issueForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        //  변경 이력 정보 추출
        StringBuilder detectIssueChange = new StringBuilder();
        //  이슈 수정 권한 체크
        this.verifyIssueModifyPermission(issueForm.getId());
        Issue issue = this.getIssue(issueForm.getId());
        IssueStatus issueStatus = this.issueStatusService.getIssueStatus(issueForm.getIssueStatusId());
        //  이슈 상태를 변경할 때 선택한 이슈 상태로 변경할 수 있는지 확인한다.
        this.issueStatusService.checkNextIssueStatus(issue, issueStatus);
        //  변경 이력 정보 추출
        this.issueHistoryService.detectIssueStatus(issue, issueForm, detectIssueChange, issueStatus);
        issue.setIssueStatus(issueStatus);
        this.issueRepository.saveAndFlush(issue);
        //  코멘트 등록
        if (!StringUtils.isEmpty(issueForm.getComment())) {
            IssueCommentForm issueCommentForm = new IssueCommentForm();
            issueCommentForm.setIssueId(issue.getId());
            issueCommentForm.setDescription(issueForm.getComment());
            this.issueCommentService.addIssueComment(issueCommentForm);
        }
        //  이슈 이력 등록
        if (!StringUtils.isEmpty(detectIssueChange.toString())) {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("<ul class=\"activity-list\">");
            stringBuilder.append(detectIssueChange.toString());
            stringBuilder.append("</ul>");
            this.issueHistoryService.addIssueHistory(issue, IssueHistoryType.MODIFY, stringBuilder.toString());
        }
        //  이슈 버전 생성
        this.issueVersionService.addIssueVersion(issue);
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_STATUS_CHANGE));
    }
    //  이슈 담당자 변경
    @Override
    @Transactional
    public void modifyIssueUser(IssueForm issueForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        //  변경 이력 정보 추출
        StringBuilder detectIssueChange = new StringBuilder();
        //  이슈 수정 권한 체크
        this.verifyIssueModifyPermission(issueForm.getId());
        Issue issue = this.getIssue(issueForm.getId());
        issue.setProject(this.projectService.getProject(issueForm.getProjectId()));
        //  변경 이력 정보 추출
        this.issueHistoryService.detectIssueManager(issue, issueForm, detectIssueChange);
        this.issueUserService.modifyIssueUser(issue, issue.getProject().getWorkspace(), issueForm.getUserIds());
        this.issueRepository.saveAndFlush(issue);
        //  이슈 이력 등록
        if (!StringUtils.isEmpty(detectIssueChange.toString())) {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("<ul class=\"activity-list\">");
            stringBuilder.append(detectIssueChange.toString());
            stringBuilder.append("</ul>");
            this.issueHistoryService.addIssueHistory(issue, IssueHistoryType.MODIFY, stringBuilder.toString());
        }
        //  이슈 버전 생성
        this.issueVersionService.addIssueVersion(issue);
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_USER_CHANGE));
    }
    //  이슈를 삭제한다.
    @Override
    @Transactional
    public void removeIssues(IssueForm issueForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        if (issueForm.getRemoveIds().size() < 1) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_REMOVE_NOT_SELECT));
        }
        List<Issue> removeIssues = Lists.newArrayList();
        for (Long issueId : issueForm.getRemoveIds()) {
            Issue issue = this.issueRemoves(issueId);
            removeIssues.add(issue);
        }
        if (removeIssues.size() > 0) {
            //this.issueRepository.deleteAll(removeIssues);
        }
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_REMOVE));
    }
    private Issue issueRemoves(Long issueId) {
        Issue issue = this.getIssue(issueId);
        //  이슈 수정 권한을 갖고 있는지 확인
        this.verifyIssueModifyPermission(issueId);
        //  이슈 첨부 파일을 삭제한다.
        if (issue.getAttachedFiles().size() > 0) {
            List<Long> attachedFileIds = Lists.newArrayList();
            for (AttachedFile attachedFile : issue.getAttachedFiles()) {
                attachedFileIds.add(attachedFile.getId());
            }
            //  첨부파일 삭제
            this.attachedFileService.removeAttachedFiles(attachedFileIds);
        }
        //  이슈 생성, 삭제시 예약 이메일에 등록해놓는다.
        this.reservationIssueEmail(issue.getId(), EmailType.ISSUE_REMOVE);
        //  이슈 삭제
        this.issueRepository.delete(issue);
        return issue;
    }
    @Override
    @Transactional(readOnly = true)
    public Issue getIssue(Long id) {
        if (id == null) {
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_EXIST));
        }
        Issue issue = this.findOne(id);
        if (issue == null) {
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_EXIST));
        }
        return issue;
    }
    //  이슈 유형을 사용하는 이슈 갯수를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public long countByIssueTypeId(Long issueTypeId) {
        return this.issueMapper.countByIssueTypeId(issueTypeId);
    }
    //  이슈 상태를 사용하는 이슈 갯수를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public long countByIssueStatus(Long issueStatusId) {
        return this.issueMapper.countByIssueStatusId(issueStatusId);
    }
    //  이슈 유형에서 워크플로우가 변경되었을 때 해당 워크플로우에 현재 이슈 상태가 존재하지 않으면 대기(생성) 로 변경한다.
    @Override
    @Transactional
    public void changeWorkflows(Workflow workflow, IssueType issueType) {
        List<Map<String, Object>> issueMaps = this.issueMapper.findByIssueTypeId(issueType.getId());
        List<IssueStatusVo> issueStatusVos = this.issueStatusService.findByWorkflowId(workflow.getId());
        IssueStatus readyIssueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(workflow);
        List<Issue> updateIssues = Lists.newArrayList();
        for (Map<String, Object> issueMap : issueMaps) {
            boolean exist = false;
            for (IssueStatusVo issueStatusVo : issueStatusVos) {
                Long issueStatusId = MapUtil.getLong(issueMap, "issueStatusId");
                if (issueStatusId == null) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_NOT_EXIST));
                }
                if (issueStatusId.equals(issueStatusVo.getId())) {
                    exist = true;
                    break;
                }
            }
            //  존재하지 않는 대상은
            if (!exist) {
                StringBuilder stringBuilder = new StringBuilder();
                String issueStatusName = MapUtil.getString(issueMap, "issueStatusName");
                if (issueStatusName == null) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_NOT_EXIST));
                }
                Issue issue = this.getIssue(MapUtil.getLong(issueMap, "issueId"));
                //  워크플로우에서 이슈 상태가 삭제되어 상태가 변경된 정보를 기록한다.
                this.issueHistoryService.recordRemoveWorkflowToIssueStatus(issueStatusName, readyIssueStatus.getName(), stringBuilder);
                this.issueHistoryService.addIssueHistory(issue, IssueHistoryType.MODIFY, stringBuilder.toString());
                issue.setIssueStatus(readyIssueStatus);
                updateIssues.add(issue);
            }
        }
        if (updateIssues.size() > 0) {
            this.issueRepository.saveAll(updateIssues);
            for (Issue issue : updateIssues) {
                //  이슈 버전 생성
                this.issueVersionService.addIssueVersion(issue);
            }
        }
    }
    //  이슈 목록을 엑셀로 다운로드 한다.
    @Override
    @Transactional
    public ModelAndView downloadExcel(HttpServletRequest request, Model model) {
        //  사용 공간에서 로그인한 사용자가 비활성인지 확인하고 비활성일 경우 엑셀 다운로드를 금지한다.
        ModelAndView modelAndView = this.workspaceService.checkUseExcelDownload(model);
        if (modelAndView != null) {
            return modelAndView;
        }
        Map<String, Object> conditions = new HashMap<>();
        //  엑셀 다운로드에 필요한 검색 조건 정보를 추출하고 검색 조건 추출에 오류가 발생하면 경고를 표시해준다.
        modelAndView = this.excelConditionCheck.checkCondition(conditions, request, model);
        if (modelAndView != null) {
            return modelAndView;
        }
        IssueCondition issueCondition = IssueCondition.make(conditions);
        //  검색 조건을 만든다
        this.makeIssueSearchCondition(issueCondition, Lists.newArrayList("01", "02", "03"), null);
        Set<String> issueIds = new HashSet<>(); //  사용자 정의 필드 검색시 나오는 이슈 아이디 저장 컬렉션
        Map<String, Object> resJsonData = new HashMap<>();
        ExportExcelVo excelInfo = new ExportExcelVo();
        excelInfo.setFileName(this.messageAccessor.message("common.issueList")); // 이슈 목록
        excelInfo.addAttrInfos(new ExportExcelAttrVo("issueStatusName", this.messageAccessor.message("common.status"), 6, ExportExcelAttrVo.ALIGN_CENTER)); // 상태
        excelInfo.addAttrInfos(new ExportExcelAttrVo("issueKey", this.messageAccessor.message("common.issueKey"), 6, ExportExcelAttrVo.ALIGN_CENTER)); // 이슈 번호
        excelInfo.addAttrInfos(new ExportExcelAttrVo("title", this.messageAccessor.message("common.issueTitle"), 40, ExportExcelAttrVo.ALIGN_LEFT)); // 이슈 제목
        excelInfo.addAttrInfos(new ExportExcelAttrVo("description", this.messageAccessor.message("common.content"), 60, ExportExcelAttrVo.ALIGN_LEFT)); // 내용
        excelInfo.addAttrInfos(new ExportExcelAttrVo("issueTypeName", this.messageAccessor.message("common.issueType"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 이슈 타입
        excelInfo.addAttrInfos(new ExportExcelAttrVo("assignees", this.messageAccessor.message("common.assignee"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 담당자
        excelInfo.addAttrInfos(new ExportExcelAttrVo("priorityName", this.messageAccessor.message("common.priority"), 6, ExportExcelAttrVo.ALIGN_CENTER)); // 우선순위
        excelInfo.addAttrInfos(new ExportExcelAttrVo("severityName", this.messageAccessor.message("common.importance"), 6, ExportExcelAttrVo.ALIGN_CENTER)); // 중요도
        excelInfo.addAttrInfos(new ExportExcelAttrVo("register", this.messageAccessor.message("common.register"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 등록자
        excelInfo.addAttrInfos(new ExportExcelAttrVo("period", this.messageAccessor.message("common.period"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 기간
        excelInfo.addAttrInfos(new ExportExcelAttrVo("modifyDate", this.messageAccessor.message("common.modifyDate"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 최종 변경일
        //  사용자 정의 필드를 사용한 이슈를 찾는다. 만약 이슈가 없다면 여기서 이슈 조회가 끝난다.
        if (!this.searchUseCustomFields(issueCondition, issueIds, resJsonData, null)) {
            model.addAttribute(Constants.EXCEL, excelInfo);
            return new ModelAndView(this.excelView);
        }
        List<IssueVo> issueVos = Lists.newArrayList();  //  이슈 목록 데이터 저장 컬렉션
        //  사용자 정의 필드로 검색한 이슈 아이디 값
        List<String> issueKeys = Lists.newArrayList(issueIds);
        issueCondition.setIssueIds(issueKeys);
        List<Map<String, Object>> results = this.issueMapper.find(issueCondition);
        //  엑셀 다운로드를 진행할 때 전체 사용자 정의 필드를 표시한다.
        this.makeIssueExcelDownloadCustomFields(excelInfo);
        //  이슈 아이디 초기화
        issueCondition.setIssueIds(Lists.newArrayList());
        //  Map 에 있는 데이터를 IssueVo 데이터로 변환한다.
        this.setMapToIssueVo(results, issueVos, issueCondition);
        //  IssueVos 데이터를 엑셀에서 표시할 수 있는 데이터로 변경한다.
        List<Map<String, String>> convertExcelViewToIssueMaps = this.convertExcelViewToIssueVos(issueVos);
        if (results.size() > EXCEL_DOWNLOAD_MAX_ROWS) {
            //  엑셀 1만건 초과 알림
            this.simpMessagingTemplate.convertAndSendToUser(this.webAppUtil.getLoginUser().getAccount(), "/notification/system-alert", this.messageAccessor.getMessage(MsgConstants.EXCEL_DOWNLOAD_MAX_ROWS_OVER));
            //  1만 건만 출력해준다.
            excelInfo.setDatas(convertExcelViewToIssueMaps.subList(0, EXCEL_DOWNLOAD_MAX_ROWS));
            model.addAttribute(Constants.EXCEL, excelInfo);
            return new ModelAndView(this.excelView);
        }
        //  엑셀에 넣을 데이터 - IssueVos 데이터를 엑셀에서 표시할 수 있는 데이터로 변경한다.
        excelInfo.setDatas(convertExcelViewToIssueMaps);
        model.addAttribute(Constants.EXCEL, excelInfo);
        return new ModelAndView(this.excelView);
    }
    //  엑셀 다운로드를 진행할 때 전체 사용자 정의 필드를 표시한다.
    private void makeIssueExcelDownloadCustomFields(ExportExcelVo excelInfo) {
        List<CustomField> customFields = this.customFieldService.findByWorkspaceId();
        for (CustomField customField : customFields) {
            excelInfo.addAttrInfos(new ExportExcelAttrVo("customField_" + customField.getId(), customField.getName(), 10, ExportExcelAttrVo.ALIGN_CENTER));
        }
    }
    //  사용자 정의 필드 정보 추가
    private void setIssueCustomFieldValue(List<IssueVo> issueVos, IssueCondition issueCondition) {
        //  이슈에서 저장한 사용자 정의 필드 값을 조회한다.
        List<Map<String, Object>> issueCustomFieldValues = this.issueCustomFieldValueService.findInIssueIds(issueCondition);
        for (IssueVo issueVo : issueVos) {
            for (Map<String, Object> issueCustomFieldValue : issueCustomFieldValues) {
                if (issueVo.getId().equals(MapUtil.getLong(issueCustomFieldValue, "issueId"))) {
                    IssueCustomFieldValueVo issueCustomFieldValueVo = new IssueCustomFieldValueVo();
                    issueCustomFieldValueVo.setUseValue(MapUtil.getString(issueCustomFieldValue, "useValue"));
                    CustomFieldVo customFieldVo = new CustomFieldVo();
                    customFieldVo.setId(MapUtil.getLong(issueCustomFieldValue, "customFieldId"));
                    issueCustomFieldValueVo.setCustomFieldVo(customFieldVo);
                    issueVo.addIssueCustomFieldValueVo(issueCustomFieldValueVo);
                }
            }
        }
    }
    //  연관일감 정보 추가
    private void setRelationIssue(IssueVo issueVo, Long issueId) {
        List<IssueVo> relationIssues = this.issueRelationService.findRelationIssue(issueId);
        issueVo.setIssueRelationIssueVos(relationIssues);
    }
    //  IssueVos 데이터를 엑셀에서 표시할 수 있는 데이터로 변경한다.
    private List<Map<String, String>> convertExcelViewToIssueVos(List<IssueVo> issueVos) {
        List<Map<String, String>> results = Lists.newArrayList();
        for (IssueVo issueVo : issueVos) {
            try {
                Map<String, String> result = new HashMap<>();
                result.put("issueStatusName", issueVo.getIssueStatusName());
                result.put("issueKey", issueVo.getProjectKey() + "-" + issueVo.getIssueNumber());
                result.put("title", issueVo.getTitle());
                String description = "";
                if (issueVo.getDescription() != null) {
                    description = Jsoup.parse(issueVo.getDescription()).text();   //  HTML 태그 제거
                    description = description.replaceAll("\\<.*?>", "");    //  공백 제거
                }
                result.put("description", description);
                result.put("issueTypeName", issueVo.getIssueTypeName());
                result.put("assignees", CommonUtil.convertUserVosToString(issueVo.getUserVos()));
                result.put("priorityName", issueVo.getPriorityName());
                result.put("severityName", issueVo.getSeverityName());
                UserVo register = this.userService.removeSensitiveUser(issueVo.getRegisterId());
                //  등록자
                result.put("register", register.getByName());
                //  최종 변경일
                result.put("modifyDate", issueVo.getModifyDate());
                if (StringUtils.isEmpty(issueVo.getStartDate())) {
                    result.put("period", "");
                } else {
                    result.put("period", issueVo.getStartDate() + " ~ " + issueVo.getCompleteDate());
                }
                for (IssueCustomFieldValueVo issueCustomFieldValueVo : issueVo.getIssueCustomFieldValueVos()) {
                    //  사용자 정의 필드 값 저장이 아직 안되어 있는 경우
                    if (result.get("customField_" + issueCustomFieldValueVo.getCustomFieldVo().getId()) == null) {
                        result.put("customField_" + issueCustomFieldValueVo.getCustomFieldVo().getId().toString(), issueCustomFieldValueVo.getUseValue());
                    } else {
                        //  이미 저장되어 있으면 다중 선택 사용자 정의 필드 값이다.
                        String useValue = result.get("customField_" + issueCustomFieldValueVo.getCustomFieldVo().getId());
                        result.put("customField_" + issueCustomFieldValueVo.getCustomFieldVo().getId().toString(), useValue + ", " + issueCustomFieldValueVo.getUseValue());
                    }
                }
                results.add(result);
            } catch (Exception e) {
                log.error("엑셀 다운로드 오류 발생");
            }
        }
        return results;
    }
    //  이슈 다중 상태 변경
    @Override
    @Transactional
    public void modifyMultiIssueStatus(IssueForm issueForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        for (Long issueId : issueForm.getIds()) {
            issueForm.setId(issueId);
            //  이슈 상태 변경
            this.modifyIssueStatus(issueForm);
        }
    }
    //  이슈 Import 용 엑셀 템플릿 다운로드
    @Override
    @Transactional
    public ModelAndView downloadExcelTemplate(HttpServletRequest request, Model model) {
        Map<String, Object> conditions;
        try {
            //  엑셀 다운로드에 필요한 검색 조건 정보를 추출한다.
            conditions = CommonUtil.getSearchConditions(request);
        } catch (IOException e) {
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.EXCEL_CONDITIONS_NOT_EXIST));
        }
        ExportExcelVo excelInfo = new ExportExcelVo();
        excelInfo.setHideCount(true);
        excelInfo.setFileName(this.messageAccessor.message("common.registerExcelIssue")); // 엑셀로 이슈 등록하기
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.title"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 제목
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.content"), 40, ExportExcelAttrVo.ALIGN_CENTER)); // 내용
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.projectKey"), 10, ExportExcelAttrVo.ALIGN_LEFT)); // 프로젝트 키
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.issueType"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 이슈 타입
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.priority"), 5, ExportExcelAttrVo.ALIGN_CENTER)); // 우선순위
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.importance"), 5, ExportExcelAttrVo.ALIGN_CENTER)); // 중요도
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.assignee"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 담당자
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.startDate"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 시작일
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.endDate"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 종료일
        //  프로젝트에 연결된 사용자 정의 필드 정보를 추출하여 엑셀 download 템플릿을 만든다.
        this.makeIssueExcelTemplateCustomFields(excelInfo, conditions);
        //  엑셀에 넣을 데이터 - IssueVos 데이터를 엑셀에서 표시할 수 있는 데이터로 변경한다.
        excelInfo.setDatas(Lists.newArrayList(new IssueVo()));
        model.addAttribute(Constants.EXCEL, excelInfo);
        return new ModelAndView(this.excelView);
    }
    //  프로젝트에 연결된 사용자 정의 필드 정보를 추출하여 엑셀 download 템플릿을 만든다.
    private void makeIssueExcelTemplateCustomFields(ExportExcelVo excelInfo, Map<String, Object> conditions) {
        List<IssueTypeCustomField> issueTypeCustomFields = this.issueTypeCustomFieldService.findByProjectIdAndIssueTypeId(MapUtil.getLong(conditions, "projectId"), MapUtil.getLong(conditions, "issueTypeId"));
        for (IssueTypeCustomField issueTypeCustomField : issueTypeCustomFields) {
            excelInfo.addAttrInfos(new ExportExcelAttrVo("id", issueTypeCustomField.getCustomField().getName(), 10, ExportExcelAttrVo.ALIGN_CENTER));
        }
    }
    //  엑셀 import 로 이슈를 등록한다.
    @Override
    @Transactional
    public void importExcel(MultipartFile multipartFile) throws Exception {
        /*StopWatch serviceStart = new StopWatch();
        serviceStart.start();*/
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        if (multipartFile != null) {
            //  업로드 파일 확장자 체크
            this.verifyMultipartFileExtension(multipartFile);
            Map<String, Project> projectMaps = new HashMap<>(); //  프로젝트 모음
            Map<String, IssueType> issueTypeMaps = new HashMap<>(); //  이슈 타입 모음
            Map<String, Priority> priorityMaps = new HashMap<>();   //  우선 순위 모음
            Map<String, Severity> severityMaps = new HashMap<>();   //  중요도 모음
            Map<String, Object> userMaps = new HashMap<>(); //  사용자 모음
            Map<String, CustomField> customFieldMaps = new HashMap<>();
            Map<String, IssueStatus> issueStatusReadyMaps = new HashMap<>();   //  상태 속성 '대기'인 이슈 상태
            Map<Long, Long> issueNumberMaps = new HashMap<>();  //  이슈 번호 모음
            Map<String, Long> issueTypeCustomFieldMaps = new HashMap<>(); //  이슈 타입 + 사용자 정의 필드 연결 정보
            Workspace workspace = this.workspaceService.getWorkspace(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());  //  이슈를 넣으려는 업무 공간
            //  이슈의 주요 속성을 map 에 저장하여 엑셀 import 에서 지정한 대상(이슈 속성)을 빠르게 찾을 수 있게 한다.
            this.IssueAttributeMapToList(projectMaps, issueTypeMaps, priorityMaps, severityMaps, userMaps, customFieldMaps, issueNumberMaps, issueTypeCustomFieldMaps, issueStatusReadyMaps);
            //  0.237 - 0.230
            List<IssueForm> issueForms = Lists.newArrayList();
            List<String> headers = Lists.newArrayList();
            Workbook workbook;
            workbook = WorkbookFactory.create(multipartFile.getInputStream());
            Sheet sheet = workbook.getSheetAt(0);
            int lastRowNum = sheet.getLastRowNum() + 1;
            //  2건 - 제목, 헤더 - 성능을 위해 최대 1만건으로 제한
            if (lastRowNum > (EXCEL_IMPORT_MAX_ROWS + 2)) {
                throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_MAX_ROWS_OVER));
            }
            for (int rowIndex = 0; rowIndex < lastRowNum; rowIndex++) {
                //  0번은 헤더는 무시한다.
                Row row = sheet.getRow(rowIndex);
                //  헤더 정보를 추출한다 - 사용자 정의 필드 정보를 가져오기 위해
                if (rowIndex == 1) {
                    for (int cellIndex = 0; cellIndex < row.getLastCellNum(); cellIndex++) {
                        Cell cell = row.getCell(cellIndex);
                        if (cell == null) {
                            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.EXCEL_EMPTY_CELL));
                        }
                        //  엑셀 import 데이터에서 cell 값을 문자열로 변환한다.
                        String cellValue = CommonUtil.convertExcelStringToCell(cell);
                        if (StringUtils.isEmpty(cellValue)) {
                            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.EXCEL_HEADER_EMPTY_CELL));
                        }
                        headers.add(cellValue);
                    }
                }
                //  1번 헤더부터 데이터 영역
                if (rowIndex > 1) {
                    //  이슈로 등록하기 위해 IssueForm 에 데이터를 셋팅한다.
                    issueForms.add(this.setIssueFormToExcelField(row, (rowIndex + 1), issueStatusReadyMaps, projectMaps, issueTypeMaps, priorityMaps, severityMaps, userMaps, customFieldMaps, issueNumberMaps, headers));
                }
            }
            if (issueForms.size() < 1) {
                return;
            }
            //  1.176
            //  이슈 등록
            this.issueMapper.insertBatch(issueForms);
            //  0.416 - 0.439
            //  1.373 ~ 1.394
            //  TODO - 이슈 이력 벌크 등록을 할 경우 프로필 이력 조회에서 부하가 심해서 넣을지 고민해야함.
            //  this.bulkInsertIssueHistory(issueForms);
            //  이슈 담당자 벌크 등록
            this.bulkInsertIssueAssignee(issueForms, workspace);
            //  0.361 - 0.705
            //  1.816
            /*StopWatch serviceStart = new StopWatch();
            serviceStart.start();*/
            //  이슈 사용자 정의 값 필드 벌크 등록
            this.bulkInsertIssueCustomFieldValue(issueForms, issueTypeCustomFieldMaps);
            //  3.628 - 3.445
            /*serviceStart.stop();
            log.debug("2차 저장 시간 : " + serviceStart.getTime());*/
            //  이슈 리스크 벌크 등록
            this.bulkInsertIssueRisk(issueForms, workspace);
            //  reverse index 업데이트
            this.issueMapper.updateBatch(issueForms);
            //  증가된 이슈 번호를 업데이트 한다.
            this.issueNumberGeneratorService.updateIssueNumber(issueNumberMaps);
        }
    }
    //  업로드 파일 확장자 체크
    private void verifyMultipartFileExtension(MultipartFile multipartFile) {
        multipartFile.getOriginalFilename();
        int pos = multipartFile.getOriginalFilename().lastIndexOf(".");
        String ext = multipartFile.getOriginalFilename().substring(pos + 1);
        if (!ext.equals("xlsx")) {
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.EXCEL_NOT_EXTENSION));
        }
    }
    //  이슈 이력 벌크 등록
    private void bulkInsertIssueHistory(List<IssueForm> issueForms) {
        List<Map<String, Object>> issueHistoryMaps = Lists.newArrayList();
        for (IssueForm issueForm : issueForms) {
            Map<String, Object> issueHistoryMap = new HashMap<>();
            issueHistoryMap.put("issueId", issueForm.getId());
            issueHistoryMap.put("projectId", issueForm.getProjectId());
            issueHistoryMap.put("registerId", this.webAppUtil.getLoginId());
            issueHistoryMap.put("issueHistoryType", IssueHistoryType.ADD.toString());
            StringBuilder description = new StringBuilder();
            //  이력 테스트 추출
            this.issueHistoryService.makeDescription(description, IssueHistoryType.ADD, null);
            issueHistoryMap.put("description", description.toString());
            issueHistoryMaps.add(issueHistoryMap);
        }
        //  이슈 이력 벌크 등록
        this.issueMapper.insertHistoryBatch(issueHistoryMaps);
    }
    //  이슈 담당자 벌크 등록
    private void bulkInsertIssueAssignee(List<IssueForm> issueForms, Workspace workspace) {
        List<Map<String, Long>> issueAssigneeMaps = Lists.newArrayList();
        for (IssueForm issueForm : issueForms) {
            for (Long userId : issueForm.getUserIds()) {
                Map<String, Long> issueAssigneeMap = new HashMap<>();
                issueAssigneeMap.put("issueId", issueForm.getId());
                issueAssigneeMap.put("userId", userId);
                issueAssigneeMap.put("workspaceId", workspace.getId());
                issueAssigneeMap.put("registerId", this.webAppUtil.getLoginId());
                issueAssigneeMaps.add(issueAssigneeMap);
            }
        }
        if (issueAssigneeMaps.size() > 0) {
            //  이슈 담당자 벌크 등록
            this.issueUserService.insertIssueUser(issueAssigneeMaps);
        }
    }
    //  이슈 리스크 벌크 등록
    private void bulkInsertIssueRisk(List<IssueForm> issueForms, Workspace workspace) {
        List<Map<String, Long>> issueRiskMaps = Lists.newArrayList();
        for (IssueForm issueForm : issueForms) {
            Map<String, Long> issueRiskMap = new HashMap<>();
            issueRiskMap.put("issueId", issueForm.getId());
            issueRiskMap.put("changeAssigneeCount", 0L);
            issueRiskMap.put("changeIssueStatusCount", 0L);
            issueRiskMap.put("workspaceId", workspace.getId());
            issueRiskMap.put("issueStatusIds", issueForm.getIssueStatusId());
            issueRiskMap.put("registerId", this.webAppUtil.getLoginId());
            issueRiskMaps.add(issueRiskMap);
        }
        if (issueRiskMaps.size() > 0) {
            //  이슈 리스크 벌크 등록
            this.issueMapper.insertIssueRiskBatch(issueRiskMaps);
        }
    }
    //  이슈 사용자 정의 필드 선택 값 벌크 등록
    private void bulkInsertIssueCustomFieldValue(List<IssueForm> issueForms, Map<String, Long> issueTypeCustomFieldMaps) {
        List<Map<String, Object>> issueCustomFieldValueMaps = Lists.newArrayList();
        for (IssueForm issueForm : issueForms) {
            for (Map<String, Object> issueCustomField : issueForm.getIssueCustomFields()) {
                String findKey = issueForm.getIssueTypeId().toString() + MapUtil.getString(issueCustomField, "customFieldId");
                //  이슈 타입 + 사용자 정의 필드 연결정보가 없다면 저장하지 않는다.
                if (issueTypeCustomFieldMaps.get(findKey) == null) {
                    continue;
                }
                issueCustomField.put("issueTypeCustomFieldId", issueTypeCustomFieldMaps.get(findKey));
                issueCustomField.put("issueId", issueForm.getId());
                issueCustomField.put("registerId", this.webAppUtil.getLoginId());
                issueCustomFieldValueMaps.add(issueCustomField);
            }
        }
        if (issueCustomFieldValueMaps.size() > 0) {
            this.issueMapper.insertIssueCustomFieldValueBatch(issueCustomFieldValueMaps);
        }
    }
    //  이슈의 주요 속성을 map 에 저장하여 엑셀 import 에서 지정한 대상(이슈 속성)을 빠르게 찾을 수 있게 한다.
    private void IssueAttributeMapToList(Map<String, Project> projectMaps, Map<String, IssueType> issueTypeMaps, Map<String, Priority> priorityMaps, Map<String, Severity> severityMaps,
                                         Map<String, Object> userMaps, Map<String, CustomField> customFieldMaps, Map<Long, Long> issueNumberMaps, Map<String, Long> issueTypeCustomFieldMaps, Map<String, IssueStatus> issueStatusReadyMaps) {
        //  프로젝트 키로 바로 찾을 수 있게 준비
        List<Project> projects = this.projectService.findByWorkspaceId();
        List<Long> projectIds = Lists.newArrayList();
        for (Project project : projects) {
            projectIds.add(project.getId());
            //  해당 프로젝트에서 생성되는 다음 이슈 번호를 생성해온다.
            issueNumberMaps.put(project.getId(), this.issueNumberGeneratorService.generateIssueNumber(project));
            projectMaps.put(project.getProjectKey(), project);
            for (IssueTypeCustomField issueTypeCustomField : project.getIssueTypeCustomFields()) {
                //  빠르게 찾기 위해 이슈 타입 아이디 + 사용자 정의 필드 아이디를 키로 한다.
                String makeKey = issueTypeCustomField.getIssueType().getId().toString() + issueTypeCustomField.getCustomField().getId().toString();
                issueTypeCustomFieldMaps.put(makeKey, issueTypeCustomField.getId());
            }
            //  프로젝트에 참여하는 사용자 정보
            List<Map<String, Object>> users = this.userService.findProjectMember(project);
            Map<String, Object> userMap = new HashMap<>();
            //  사용자 정보를 Map 에 저장
            for (Map<String, Object> user : users) {
                userMap.put(CommonUtil.decryptAES128(MapUtil.getString(user, "account")), MapUtil.getLong(user, "userId"));
            }
            userMaps.put(project.getProjectKey(), userMap);
        }
        //  이슈 유형을 바로 찾을 수 있게 준비
        List<IssueType> issueTypes = this.issueTypeService.findByWorkspaceId();
        for (IssueType issueType : issueTypes) {
            issueTypeMaps.put(issueType.getName(), issueType);
            IssueStatus issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(issueType.getWorkflow());
            issueStatusReadyMaps.put(issueType.getId().toString(), issueStatus);
        }
        //  우선순위를 바로 찾을 수 있게 준비
        List<Priority> priorities = this.priorityService.findByWorkspaceId();
        for (Priority priority : priorities) {
            priorityMaps.put(priority.getName(), priority);
        }
        //  중요도를 바로 찾을 수 있게 준비
        List<Severity> severities = this.severityService.findByWorkspaceId();
        for (Severity severity : severities) {
            severityMaps.put(severity.getName(), severity);
        }
        //  사용자 정의 필드를 바로 찾을 수 있게 준비
        List<CustomField> customFields = this.customFieldService.findByWorkspaceId();
        for (CustomField customField : customFields) {
            customFieldMaps.put(customField.getName(), customField);
        }
    }
    //  엑셀 필드에 있는 정보를 이슈 form 으로 옮긴다.
    private IssueForm setIssueFormToExcelField(Row row, int rowIndex, Map<String, IssueStatus> issueStatusReadyMaps, Map<String, Project> projectMaps, Map<String, IssueType> issueTypeMaps, Map<String,
            Priority> priorityMaps, Map<String, Severity> severityMaps, Map<String, Object> userMaps, Map<String, CustomField> customFieldMaps, Map<Long, Long> issueNumberMaps, List<String> headers) {
        IssueForm issueForm = new IssueForm();
        issueForm.setRegisterId(this.webAppUtil.getLoginId());
        Project project = null;
        //  제목, 내용, 프로젝트 키, 이슈 타입, 우선순위, 중요도, 담당자, 시작일, 종료일, 사용자 정의 필드
        for (int cellIndex = 0; cellIndex < headers.size(); cellIndex++) {
            Cell cell = row.getCell(cellIndex);
            switch (cellIndex) {
                case 0:
                    //  이슈 제목을 IssueForm 에 저장한다.
                    this.setIssueFormTitle(cell, issueForm, rowIndex);
                    break;
                case 1:    //  내용
                    if (cell != null) {
                        issueForm.setDescription(CommonUtil.convertExcelStringToCell(cell));
                    } else {
                        //  null 입력 방지
                        issueForm.setDescription("");
                    }
                    break;
                case 2:    //  프로젝트 키와 이슈 번호
                    project = this.setIssueFormProjectKeyAndIssueNumber(cell, issueForm, projectMaps, issueNumberMaps, rowIndex);
                    break;
                case 3:
                    //  이슈 타입을 IssueForm 에 저장한다.
                    this.setIssueFormIssueType(cell, issueTypeMaps, issueForm, rowIndex);
                    //  이슈 타입에 연결된 워크플로우의 상태 속성 '대기' 인 상태를 issueForm 에 저장한다.
                    this.setIssueFormIssueStatus(issueStatusReadyMaps, issueForm, rowIndex);
                    break;
                case 4:
                    //  우선순위를 IssueForm 에 저장한다.
                    this.setIssueFormPriority(cell, priorityMaps, issueForm, rowIndex);
                    break;
                case 5:
                    //  중요도를 IssueForm 에 저장한다.
                    this.setIssueFormSeverity(cell, severityMaps, issueForm, rowIndex);
                    break;
                case 6:
                    //  담당자를 IssueForm 에 저장한다.
                    this.setIssueFormAssignee(cell, userMaps, issueForm, project);
                    break;
                case 7:
                    //  시작일을 IssueForm 에 저장한다.
                    this.setIssueFormPeriod(cell, issueForm, true, rowIndex);
                    break;
                case 8:
                    //  종료일을 IssueForm 에 저장한다.
                    this.setIssueFormPeriod(cell, issueForm, false, rowIndex);
                    break;
                default:
                    //  8번 이상부터는 사용자 정의 필드. 사용자 정의 필드 정보를 IssueForm 에 저장한다.
                    this.setIssueFormCustomFieldValue(cell, customFieldMaps, issueForm, headers.get(cellIndex), rowIndex);
            }
        }
        return issueForm;
    }
    //  이슈 제목을 IssueForm 에 저장한다.
    private void setIssueFormTitle(Cell cell, IssueForm issueForm, int rowIndex) {
        if (cell == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_ISSUE_TITLE_IS_NULL, rowIndex));
        }
        String title = CommonUtil.convertExcelStringToCell(cell);
        //  제목 유효성 체크
        this.verifyTitle(title);
        issueForm.setTitle(title);
    }
    //  프로젝트 키, 이슈 고유 번호, 담당자를 IssueForm 에 저장한다.
    private Project setIssueFormProjectKeyAndIssueNumber(Cell cell, IssueForm issueForm, Map<String, Project> projectMaps, Map<Long, Long> issueNumberMaps, int rowIndex) {
        //  프로젝트 아이디를 IssueForm 에 저장한다.
        Project project = this.setIssueFormProject(cell, projectMaps, issueForm, rowIndex);
        //  이슈 고유 번호를 IssueForm 에 저장한다.
        this.setIssueFormIssueNumber(issueForm, issueNumberMaps, project, rowIndex);
        return project;
    }
    //  프로젝트 아이디를 IssueForm 에 저장한다.
    private Project setIssueFormProject(Cell cell, Map<String, Project> projectMaps, IssueForm issueForm, int rowIndex) {
        if (cell == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_PROJECT_KEY_IS_NULL, rowIndex));
        }
        Project project = projectMaps.get(CommonUtil.convertExcelStringToCell(cell).toUpperCase());
        if (project == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_PROJECT_NOT_EXIST, rowIndex));
        }
        issueForm.setProjectId(project.getId());
        return project;
    }
    //  이슈 고유 번호를 IssueForm 에 저장한다.
    private void setIssueFormIssueNumber(IssueForm issueForm, Map<Long, Long> issueNumberMaps, Project project, int rowIndex) {
        //  이슈 고유 번호
        Long issueNumber = issueNumberMaps.get(project.getId());
        if (issueNumber == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_NUMBER_NOT_EXIST, rowIndex));
        }
        issueForm.setIssueNumber(issueNumber);
        issueNumberMaps.put(project.getId(), ++issueNumber);  //  이슈 번호를 1씩 증가 시킨다.
    }
    //  이슈 타입을 IssueForm 에 저장한다.
    private void setIssueFormIssueType(Cell cell, Map<String, IssueType> issueTypeMaps, IssueForm issueForm, int rowIndex) {
        if (cell == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_ISSUE_TYPE_IS_NULL, rowIndex));
        }
        IssueType issueType = issueTypeMaps.get(CommonUtil.convertExcelStringToCell(cell));
        if (issueType == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_ISSUE_TYPE_NOT_EXIST, rowIndex));
        }
        issueForm.setIssueTypeId(issueType.getId());
    }
    //  이슈 타입에 연결된 워크플로우의 상태 속성 '대기' 인 상태를 issueForm 에 저장한다.
    private void setIssueFormIssueStatus(Map<String, IssueStatus> issueStatusReadyMaps, IssueForm issueForm, int rowIndex) {
        IssueStatus issueStatus = issueStatusReadyMaps.get(issueForm.getIssueTypeId().toString());
        if (issueStatus == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_ISSUE_STATUS_READY_NOT_EXIST, rowIndex));
        }
        issueForm.setIssueStatusId(issueStatus.getId());
    }
    //  우선순위를 IssueForm 에 저장한다.
    private void setIssueFormPriority(Cell cell, Map<String, Priority> priorityMaps, IssueForm issueForm, int rowIndex) {
        if (cell == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_PRIORITY_IS_NULL, rowIndex));
        }
        Priority priority = priorityMaps.get(CommonUtil.convertExcelStringToCell(cell));
        if (priority == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_PRIORITY_NOT_EXIST, rowIndex));
        }
        issueForm.setPriorityId(priority.getId());
    }
    //  중요도를 IssueForm 에 저장한다.
    private void setIssueFormSeverity(Cell cell, Map<String, Severity> severityMaps, IssueForm issueForm, int rowIndex) {
        if (cell == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_SEVERITY_IS_NULL, rowIndex));
        }
        Severity severity = severityMaps.get(CommonUtil.convertExcelStringToCell(cell));
        if (severity == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_SEVERITY_NOT_EXIST, rowIndex));
        }
        issueForm.setSeverityId(severity.getId());
    }
    //  담당자를 IssueForm 에 저장한다.
    private void setIssueFormAssignee(Cell cell, Map<String, Object> userMaps, IssueForm issueForm, Project project) {
        if (cell != null) {
            String[] splitAssignee = CommonUtil.convertExcelStringToCell(cell).split("#");
            Map<String, Object> userMap = (Map<String, Object>) MapUtil.getObject(userMaps, project.getProjectKey());
            List<Long> userIds = Lists.newArrayList();
            for (String account : splitAssignee) {
                if (MapUtil.getLong(userMap, account) != null) {
                    userIds.add(MapUtil.getLong(userMap, account));
                }
            }
            issueForm.setUserIds(userIds);
        }
    }
    //  시작일, 종료일을 IssueForm 에 저장한다.
    private void setIssueFormPeriod(Cell cell, IssueForm issueForm, Boolean checkStartDate, int rowIndex) {
        if (cell != null && !cell.toString().equals("")) {
            Date startDate;
            try {
                startDate = cell.getDateCellValue();
            } catch (Exception e) {
                throw new OwlRuntimeException(
                        this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_PERIOD_NOT_VALIDITY_EMPTY, rowIndex));
            }
            if (checkStartDate) {
                issueForm.setStartDate(DateUtil.convertDateToStr(startDate, "yyyy-MM-dd"));
            } else {
                issueForm.setCompleteDate(DateUtil.convertDateToStr(startDate, "yyyy-MM-dd"));
                try {
                    //  날짜 유효성 체크
                    this.checkStartCompleteDate(issueForm.getStartDate(), issueForm.getCompleteDate());
                } catch (OwlRuntimeException e) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_PERIOD_NOT_VALIDITY, rowIndex));
                }
            }
        }
    }
    //  사용자 정의 필드 정보를 IssueForm 에 저장한다.
    private void setIssueFormCustomFieldValue(Cell cell, Map<String, CustomField> customFieldMaps, IssueForm issueForm, String customFieldName, int rowIndex) {
        if (cell != null) {
            String cellValue = CommonUtil.convertExcelStringToCell(cell);
            Map<String, Object> issueCustomFieldMap = new HashMap<>();
            CustomField customField = customFieldMaps.get(customFieldName);
            if (customField == null) {
                throw new OwlRuntimeException(
                        this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_HEADER_CUSTOM_FIELD_NOT_EXIST, rowIndex));
            }
            //  사용자 정의 필드 값이 공백이면 중지
            if (StringUtils.isEmpty(cellValue)) {
                return;
            }
            boolean validity = false;
            switch (customField.getCustomFieldType()) {
                case INPUT:
                    if (cellValue.length() > 100) {
                        throw new OwlRuntimeException(
                                this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_TEXT_TYPE_MAX_LENGTH_OUT));
                    }
                    issueCustomFieldMap.put("customFieldId", customField.getId());
                    issueCustomFieldMap.put("useValue", cellValue);
                    issueForm.addIssueCustomFields(issueCustomFieldMap);
                    break;
                case SINGLE_SELECT:
                    //  값 유효성 체크
                    for (CustomFieldValue customFieldValue : customField.getCustomFieldValues()) {
                        if (customFieldValue.getValue().equals(cellValue)) {
                            validity = true;
                            break;
                        }
                    }
                    if (!validity) {
                        throw new OwlRuntimeException(
                                this.messageAccessor.getMessage(MsgConstants.EXCEL_CUSTOM_FIELD_VALUE_NOT_VALIDITY, rowIndex));
                    }
                    issueCustomFieldMap.put("customFieldId", customField.getId());
                    issueCustomFieldMap.put("useValue", cellValue);
                    issueForm.addIssueCustomFields(issueCustomFieldMap);
                    break;
                case MULTI_SELECT:
                    //  값 유효성 체크
                    String[] useValues = cellValue.split("#");
                    //  해, 달
                    for (String useValue : useValues) {
                        for (CustomFieldValue customFieldValue : customField.getCustomFieldValues()) {
                            if (customFieldValue.getValue().equals(useValue)) {
                                validity = true;
                                Map<String, Object> multiValueMap = new HashMap<>();
                                multiValueMap.put("customFieldId", customField.getId());
                                multiValueMap.put("useValue", useValue);
                                issueForm.addIssueCustomFields(multiValueMap);
                            }
                        }
                    }
                    if (!validity) {
                        throw new OwlRuntimeException(
                                this.messageAccessor.getMessage(MsgConstants.EXCEL_CUSTOM_FIELD_VALUE_NOT_VALIDITY, rowIndex));
                    }
                    break;
            }
        }
    }
    //  프로젝트에 있는 모든 이슈 정보를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public List<Long> findByProjectId(Long projectId) {
        List<Map<String, Object>> issueMaps = this.issueMapper.findByProjectId(projectId);
        List<Long> issueIds = Lists.newArrayList();
        for (Map<String, Object> issueMap : issueMaps) {
            Long issueId = MapUtil.getLong(issueMap, "id");
            if (issueId != null) {
                issueIds.add(issueId);
            }
        }
        return issueIds;
    }
    //  이슈를 대상자들에게 메일로 발송한다.
    @Override
    @Transactional(readOnly = true)
    public void sendIssueEmail(IssueForm issueForm) {
        if (issueForm.getSendEmails().size() < 1) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_SEND_USER));
        }
        Issue issue = this.getIssue(issueForm.getId());
        Map<String, Object> issueMap = new HashMap<>();
        //  이슈 정보를 이메일 전송에 사용하기 위해 Map 형태로 변환한다.
        this.makeIssueMapToIssue(issue, issueMap);
        //  발신자 표시
        UserVo toUser = this.webAppUtil.getLoginUser();
        issueMap.put("toUser", toUser.getName() + "(" + CommonUtil.decryptAES128(toUser.getAccount()) + ")");
        // 이슈 링크
        String projectKey = issue.getProject().getProjectKey();
        Long IssueNumber = issue.getIssueNumber();
        String link = this.configuration.getEmailSendUrl() + "/#/issues/issueList?projectKey=" + projectKey + "&issueNumber=" + IssueNumber.toString();
        issueMap.put("issueLink", link);
        issueMap.put("projectLink", link);
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_ANOTHER_USER_SEND_EMAIL));
        this.systemEmailService.directEmail(issueForm.getSendEmails().toArray(new String[issueForm.getSendEmails().size()]), EmailType.ISSUE_SEND, issueMap, null);
    }
    //  예약 발생 이슈를 실행한다
    @Override
    @Transactional
    public void reservationIssue() {
        List<IssueReservation> issueReservations = this.issueReservationService.findByIssueReservationTypeNotNull();
        Calendar calendar = Calendar.getInstance();
        int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
        int month = calendar.get(Calendar.MONTH) + 1;
        int date = calendar.get(Calendar.DATE);
        for (IssueReservation issueReservation : issueReservations) {
            switch (issueReservation.getIssueReservationType()) {
                case DAY:
                    //  이슈를 다시 생성 상태로 변경시킨다.
                    this.occurReservationIssue(issueReservation.getIssue());
                    break;
                case WEEK:
                    if (dayOfWeek == Integer.parseInt(issueReservation.getReservation())) {
                        //  이슈를 다시 생성 상태로 변경시킨다.
                        this.occurReservationIssue(issueReservation.getIssue());
                    }
                    break;
                case MONTH:
                    if (date == Integer.parseInt(issueReservation.getReservation())) {
                        //  이슈를 다시 생성 상태로 변경시킨다.
                        this.occurReservationIssue(issueReservation.getIssue());
                    }
                    break;
                case YEAR:
                    String[] reservations = issueReservation.getReservation().split("-");
                    if (reservations.length > 1) {
                        if ((Integer.parseInt(reservations[0]) == month) && Integer.parseInt(reservations[1]) == date) {
                            //  이슈를 다시 생성 상태로 변경시킨다.
                            this.occurReservationIssue(issueReservation.getIssue());
                        }
                    }
                    break;
            }
        }
    }
    //  이슈를 다시 생성 상태로 변경시킨다.
    private void occurReservationIssue(Issue issue) {
        if (!issue.getIssueStatus().getIssueStatusType().equals(IssueStatusType.READY)) {
            //  이슈를 생성 상태로 변경시킨다.
            IssueStatus issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(issue.getIssueType().getWorkflow());
            if (issueStatus != null) {
                StringBuilder detectIssueChange = new StringBuilder();
                //  예약 발생으로 이슈 상태 변경된 정보를 기록한다.
                this.issueHistoryService.detectReservationIssueStatus(issue, detectIssueChange, issueStatus);
                issue.setIssueStatus(issueStatus);
                this.issueRepository.saveAndFlush(issue);
                //  이슈 상태 변경 정보를 이력으로 남긴다.
                this.issueHistoryService.addIssueHistory(issue, IssueHistoryType.MODIFY, detectIssueChange.toString());
                //  이슈 버전 생성
                this.issueVersionService.addIssueVersion(issue);
            }
        }
    }
    @Autowired
    private ProjectMapper projectMapper;
    @Override
    @Transactional(readOnly = true)
    public Map<String, Object> findTask(IssueCondition taskCondition) {
        StopWatch serviceStart = new StopWatch();
        serviceStart.start();
        ProjectCondition projectCondition = new ProjectCondition(this.webAppUtil.getLoginUser().getLastProjectId(), this.webAppUtil.getLoginId());
        List<Map<String, Object>> checkProjectRole = this.projectMapper.checkIncludeProject(projectCondition);
        if (checkProjectRole.size() < 1) {
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.PROJECT_NOT_INCLUDE_USER));
        }
        //  이중으로 역할 체크를 해서 참여하지 않은 사용자가 해당 프로젝트에 접근 못하도록 한다.
        taskCondition.setLoginUserId(this.webAppUtil.getLoginId());
//        List<Map<String, Object>> results = this.taskMapper.find(taskCondition);
        List<Map<String, Object>> results = this.issueMapper.find(taskCondition);
        serviceStart.stop();
        long executeTime = serviceStart.getTime();
        Map<String, Object> tasks = new HashMap<>();
        //  워크플로우 상태 id 를 키로 만든다.
        for (Long workflowStatusId : taskCondition.getStatusIds()) {
            tasks.put(workflowStatusId.toString(), Lists.newArrayList());
        }
        Map<String, Object> taskUserSave = new HashMap<>();
        //  전체 task_id를 모은다.
        for (Map<String, Object> result : results) {
            Long taskId = MapUtil.getLong(result, "id");
            taskCondition.addIssueIds(taskId.toString());
            //  각 task_id 별로 사용자 정보를 저장할 공간을 미리 확보한다.
            taskUserSave.put(taskId.toString(), Lists.newArrayList());
        }
        List<Map<String, Object>> taskUsers = Lists.newArrayList();
        //  task 가 하나도 없을 경우에는 조회를 하지 않는다.
        if (!taskCondition.getIssueIds().isEmpty()) {
            taskUsers = this.issueMapper.getAllTaskUser(taskCondition);
        }
        //  task_id 에 매칭되는 담당자 정보를 준비한다.
        for (Map<String, Object> taskUser : taskUsers) {
            Long taskId = MapUtil.getLong(taskUser, "taskId");
            List<UserVo> userVos = (List<UserVo>)taskUserSave.get(taskId.toString());
            userVos.add(ConvertUtil.convertMapToClass(taskUser, UserVo.class));
        }
        for (Map<String, Object> result : results) {
            IssueVo taskVo = ConvertUtil.convertMapToClass(result, IssueVo.class);
            //  중요도 셋팅
            if (MapUtil.getLong(result, "priorityId") != null) {
                PriorityVo priorityVo = new PriorityVo();
                priorityVo.setId(MapUtil.getLong(result, "priorityId"));
                priorityVo.setName(MapUtil.getString(result, "priorityName"));
                priorityVo.setColor(MapUtil.getString(result, "priorityColor"));
                taskVo.setPriorityVo(priorityVo);
            }
            //  워크플로우 상태 셋팅
            if (MapUtil.getLong(result, "workflowStatusId") != null) {
                WorkflowStatusVo workflowStatusVo = new WorkflowStatusVo();
                workflowStatusVo.setId(MapUtil.getLong(result, "workflowStatusId"));
                workflowStatusVo.setName(MapUtil.getString(result, "workflowStatusName"));
                workflowStatusVo.setColor(MapUtil.getString(result, "workflowStatusColor"));
                taskVo.setWorkflowStatusVo(workflowStatusVo);
            }
            //  담당자 셋팅
            List<UserVo> userVos =  (List<UserVo>)taskUserSave.get(taskVo.getId().toString());
            taskVo.setUserVos(userVos);
            List<IssueVo> taskVos = (List<IssueVo>)tasks.get(MapUtil.getString(result, "workflowStatusId"));
            taskVos.add(taskVo);
            tasks.put(MapUtil.getString(result, "workflowStatusId"), taskVos);
        }
        /*serviceStart.stop();
        long executeTime = serviceStart.getTime();*/
        return tasks;
    }
}
src/main/java/kr/wisestone/owl/service/impl/IssueStatusServiceImpl.java
New file
@@ -0,0 +1,570 @@
package kr.wisestone.owl.service.impl;
import com.google.common.collect.Lists;
import kr.wisestone.owl.common.ExcelConditionCheck;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.*;
import kr.wisestone.owl.domain.enumType.IssueStatusType;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.mapper.IssueStatusMapper;
import kr.wisestone.owl.repository.IssueStatusRepository;
import kr.wisestone.owl.service.*;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.util.MapUtil;
import kr.wisestone.owl.vo.*;
import kr.wisestone.owl.web.condition.IssueStatusCondition;
import kr.wisestone.owl.web.form.IssueStatusForm;
import kr.wisestone.owl.web.view.ExcelView;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
@Service
public class IssueStatusServiceImpl extends AbstractServiceImpl<IssueStatus, Long, JpaRepository<IssueStatus, Long>> implements IssueStatusService {
    private static final Logger log = LoggerFactory.getLogger(IssueStatusServiceImpl.class);
    @Autowired
    private IssueStatusRepository issueStatusRepository;
    @Autowired
    private WorkspaceService workspaceService;
    @Autowired
    private WorkflowTransitionService workflowTransitionService;
    @Autowired
    private IssueTypeService issueTypeService;
    @Autowired
    private IssueStatusMapper issueStatusMapper;
    @Autowired
    private IssueService issueService;
    @Autowired
    private UserService userService;
    @Autowired
    private ExcelView excelView;
    @Autowired
    private ExcelConditionCheck excelConditionCheck;
    @Override
    protected JpaRepository<IssueStatus, Long> getRepository() {
        return this.issueStatusRepository;
    }
    //  워크스페이스 생성시 생성되는 이슈 상태 - 프로젝트 유형에 따라 생성되는 이슈 상태가 달라진다.
    @Override
    @Transactional
    public List<IssueStatus> addDefaultIssueStatus(Workspace workspace) {
        List<IssueStatus> issueStatuses = Lists.newArrayList();
        issueStatuses.add(new IssueStatus(workspace, this.messageAccessor.message("common.create"), Boolean.TRUE, "#665fff", IssueStatusType.READY, 1L)); // 생성
        issueStatuses.add(new IssueStatus(workspace, this.messageAccessor.message("common.progress"), Boolean.TRUE, "#98c220", IssueStatusType.OPEN, 2L)); // 진행
        issueStatuses.add(new IssueStatus(workspace, this.messageAccessor.message("common.reProgress"), Boolean.TRUE, "#c940ea", IssueStatusType.OPEN, 3L)); // 재진행
        issueStatuses.add(new IssueStatus(workspace, this.messageAccessor.message("common.check"), Boolean.TRUE, "#febd35", IssueStatusType.OPEN, 4L)); // 확인
        issueStatuses.add(new IssueStatus(workspace, this.messageAccessor.message("common.end"), Boolean.TRUE, "#888888", IssueStatusType.CLOSE, 5L)); // 종료
        issueStatuses.add(new IssueStatus(workspace, this.messageAccessor.message("common.noApproval"), Boolean.TRUE, "#ff5f99", IssueStatusType.CLOSE, 6L)); // 승인 불가
        issueStatuses.add(new IssueStatus(workspace, this.messageAccessor.message("common.approval"), Boolean.TRUE, "#3598fe", IssueStatusType.CLOSE, 7L)); // 승인
        return this.issueStatusRepository.saveAll(issueStatuses);
    }
    //  워크스페이스에 존재하는 이슈 상태 조회
    @Override
    @Transactional(readOnly = true)
    public List<IssueStatus> findByWorkspaceId(Long workspaceId) {
        return this.issueStatusRepository.findByWorkspaceId(workspaceId);
    }
    //  이슈 상태를 생성한다.
    @Override
    @Transactional
    public IssueStatus addIssueStatus(IssueStatusForm issueStatusForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        //  이름 유효성 체크
        this.verifyName(issueStatusForm.getName(), null);
        //  색상 체크
        this.verifyColor(issueStatusForm.getColor());
        IssueStatus issueStatus = ConvertUtil.copyProperties(issueStatusForm, IssueStatus.class, "issueStatusType");
        issueStatus.setIssueStatusType(IssueStatusType.valueOf(issueStatusForm.getIssueStatusType()));
        issueStatus.setWorkspace(this.workspaceService.getWorkspace(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId()));
        issueStatus.setPosition(0L);
        return this.issueStatusRepository.saveAndFlush(issueStatus);
    }
    //  이름 유효성 체크
    private void verifyName(String name, Long id) {
        if (StringUtils.isEmpty(name)) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_NOT_NAME));
        }
        if (name.length() > 20) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_NAME_MAX_LENGTH_OUT));
        }
        IssueStatus issueStatus;
        Long workspaceId = this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId();
        if (id == null) {
            issueStatus = this.issueStatusRepository.findByNameAndWorkspaceId(name, workspaceId);
        }
        else {
            issueStatus = this.issueStatusRepository.findByNameAndWorkspaceIdAndIdNot(name, workspaceId, id);
        }
        if (issueStatus != null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_USED_NAME));
        }
    }
    //  색상 체크
    private void verifyColor(String color) {
        if (StringUtils.isEmpty(color)) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_NOT_COLOR));
        }
    }
    //  이슈 상태 목록을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public List<IssueStatusVo> findIssueStatus(Map<String, Object> resJsonData,
                                               IssueStatusCondition condition, Pageable pageable) {
        condition.setPage(pageable.getPageNumber() * pageable.getPageSize());
        condition.setPageSize(pageable.getPageSize());
        condition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        List<Map<String, Object>> results = this.issueStatusMapper.find(condition);
        Long totalCount = this.issueStatusMapper.count(condition);
        int totalPage = (int) Math.ceil((totalCount - 1) / pageable.getPageSize()) + 1;
        List<IssueStatusVo> issueStatusVos = ConvertUtil.convertListToListClass(results, IssueStatusVo.class);
        //  이슈 상태가 워크플로우에 사용되고 있는지 여부를 체크하고 이슈 상태 안에 사용되는 워크플로우 정보를 셋팅한다.
        if (condition.getDeep() != null) {
            this.setUseIssueStatusByWorkflow(issueStatusVos);
        }
        resJsonData.put(Constants.RES_KEY_CONTENTS, issueStatusVos);
        resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(),
                totalPage, totalCount));
        return issueStatusVos;
    }
    //  이슈 상태 목록을 조회한다 - 페이징 사용x 워크플로우 생성, 수정에서 사용
    @Override
    @Transactional(readOnly = true)
    public List<IssueStatusVo> findIssueStatus(Map<String, Object> resJsonData, IssueStatusCondition condition) {
        condition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        List<Map<String, Object>> results = this.issueStatusMapper.find(condition);
        List<IssueStatusVo> issueStatusVos = ConvertUtil.convertListToListClass(results, IssueStatusVo.class);
        //  issueStatusType 별로 정렬
        Collections.sort(issueStatusVos);
        //  ready 상태가 앞으로 오도록 다시 정렬
        Collections.reverse(issueStatusVos);
        resJsonData.put(Constants.RES_KEY_CONTENTS, issueStatusVos);
        return issueStatusVos;
    }
    //  이슈 상태를 사용하고 있는 워크플로우 정보를 셋팅한다.
    private void setUseIssueStatusByWorkflow(List<IssueStatusVo> issueStatusVos) {
        for (IssueStatusVo issueStatusVo : issueStatusVos) {
            Map<String, Object> workflows = new HashMap<>();
            IssueStatus issueStatus = this.getIssueStatus(issueStatusVo.getId());
            this.findUseIssueStatusByWorkflow(issueStatus.getSourceWorkflowTransitions().iterator(), workflows);
            this.findUseIssueStatusByWorkflow(issueStatus.getTargetWorkflowTransitions().iterator(), workflows);
            for (String key : workflows.keySet()) {
                issueStatusVo.addWorkflowVos((WorkflowVo) workflows.get(key));
            }
        }
    }
    //  이슈 상태를 사용하고 있는 워크플로우를 찾는다.
    private void findUseIssueStatusByWorkflow(Iterator<WorkflowTransition> iterator, Map<String, Object> workflows) {
        while (iterator.hasNext()) {
            WorkflowTransition workflowTransition = iterator.next();
            workflows.put(workflowTransition.getWorkflow().getId().toString(), ConvertUtil.copyProperties(workflowTransition.getWorkflow(), WorkflowVo.class));
        }
    }
    //  이슈의 현재 상태에서 이동 가능한 다음 상태 정보 목록을 찾는다.
    @Override
    @Transactional(readOnly = true)
    public void findNextIssueStatus(Map<String, Object> resJsonData, IssueStatusCondition condition) {
        IssueType issueType = this.issueTypeService.getIssueType(condition.getIssueTypeId());
        IssueStatus issueStatus = this.getIssueStatus(condition.getId());
        Workflow workflow = issueType.getWorkflow();
        List<WorkflowTransitionVo> workflowTransitionVos = this.workflowTransitionService.findBySourceIssueStatusIdAndWorkflowId(issueStatus.getId(), workflow.getId());
        List<IssueStatusVo> issueStatusVos = Lists.newArrayList();
        for (WorkflowTransitionVo workflowTransitionVo : workflowTransitionVos) {
            IssueStatusVo issueStatusVo = new IssueStatusVo();
            issueStatusVo.setId(workflowTransitionVo.getTargetStatusId());
            issueStatusVo.setName(workflowTransitionVo.getTargetStatusName());
            issueStatusVos.add(issueStatusVo);
        }
        resJsonData.put(Constants.RES_KEY_CONTENTS, issueStatusVos);
    }
    //  이슈 상태 상세 정보를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public void detailIssueStatus(Map<String, Object> resJsonData, IssueStatusCondition issueStatusCondition) {
        IssueStatusVo issueStatusVo = new IssueStatusVo();
        if (issueStatusCondition.getId() != null) {
            IssueStatus issueStatus = this.getIssueStatus(issueStatusCondition.getId());
            issueStatusVo = ConvertUtil.copyProperties(issueStatus, IssueStatusVo.class);
            issueStatusVo.setIssueStatusType(issueStatus.getIssueStatusType().toString());
            if (issueStatusCondition.getDeep().equals("01")) {
                int useCount = issueStatus.getSourceWorkflowTransitions().size() + issueStatus.getTargetWorkflowTransitions().size();
                if (useCount > 0) {
                    issueStatusVo.setUseYn(true);
                }
            }
        }
        resJsonData.put(Constants.RES_KEY_CONTENTS, issueStatusVo);
    }
    //  이슈 상태 정보를 수정한다.
    @Override
    @Transactional
    public IssueStatus modifyIssueStatus(IssueStatusForm issueStatusForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        //  이름 유효성 체크
        this.verifyName(issueStatusForm.getName(), issueStatusForm.getId());
        //  색상 체크
        this.verifyColor(issueStatusForm.getColor());
        IssueStatus issueStatus = this.getIssueStatus(issueStatusForm.getId());
        ConvertUtil.copyProperties(issueStatusForm, issueStatus, "id", "issueStatusType");
        issueStatus.setIssueStatusType(IssueStatusType.valueOf(issueStatusForm.getIssueStatusType()));
        return this.issueStatusRepository.saveAndFlush(issueStatus);
    }
    //  이슈 상태 아이디로 이슈 상태를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public IssueStatus getIssueStatus(Long id) {
        if (id == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_NOT_EXIST));
        }
        IssueStatus issueStatus = this.findOne(id);
        if (issueStatus == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_NOT_EXIST));
        }
        return issueStatus;
    }
    //  이슈 상태를 삭제한다.
    @Override
    @Transactional
    public void removeIssueStatus(IssueStatusForm issueStatusForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        if (issueStatusForm.getRemoveIds().size() < 1) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_REMOVE_NOT_SELECT));
        }
        for (Long issueStatusId : issueStatusForm.getRemoveIds()) {
            this.removeIssueStatuses(issueStatusId);
        }
        this.issueStatusRepository.flush();
    }
    private void removeIssueStatuses(Long issueStatusId) {
        IssueStatus issueStatus = this.getIssueStatus(issueStatusId);
        //  삭제할 이슈 상태가 이슈 에서 사용되고 있는지 확인한다.
        this.checkUseIssueStatus(issueStatus);
        //  삭제할 이슈 상태가 워크플로우 에서 사용되고 있는지 확인한다.
        this.checkUseWorkflow(issueStatus);
        //  기본으로 제공되는 이슈 상태는 삭제 금지
        if (issueStatus.getDefaultYn()) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.DEFAULT_ISSUE_STATUS_NOT_REMOVE));
        }
        this.issueStatusRepository.delete(issueStatus);
    }
    //  삭제할 이슈 상태가 이슈 에서 사용되고 있는지 확인한다.
    private void checkUseIssueStatus(IssueStatus issueStatus) {
        //  이슈 유형을 사용하는 이슈 갯수를 조회한다.
        long issueCount = this.issueService.countByIssueStatus(issueStatus.getId());
        if (issueCount > 0) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_USE_ISSUES));
        }
    }
    //  삭제할 이슈 상태가 워크플로우 에서 사용되고 있는지 확인한다.
    private void checkUseWorkflow(IssueStatus issueStatus) {
        List<IssueStatusVo> issueStatusVos = Lists.newArrayList(ConvertUtil.copyProperties(issueStatus, IssueStatusVo.class));
        //  이슈 상태를 사용하고 있는 워크플로우 정보를 셋팅한다.
        this.setUseIssueStatusByWorkflow(issueStatusVos);
        for (IssueStatusVo issueStatusVo : issueStatusVos) {
            if (issueStatusVo.getWorkflowVos().size() > 0) {
                throw new OwlRuntimeException(
                        this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_USE_WORKFLOW));
            }
        }
    }
    //  워크플로우에 있는 이슈 상태를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public List<IssueStatusVo> findByWorkflowId(Long workflowId) {
        List<WorkflowTransition> workflowTransitions = this.workflowTransitionService.findByWorkflowId(workflowId);
        Map<String, Object> issueStatuses = new HashMap<>();
        List<IssueStatusVo> issueStatusVos = Lists.newArrayList();
        for (WorkflowTransition workflowTransition : workflowTransitions) {
            IssueStatus source = workflowTransition.getSourceIssueStatus();
            IssueStatus target = workflowTransition.getTargetIssueStatus();
            //  대상 이슈 상태가 이미 저장되어 있는지 확인한다. - source 항목
            if (source != null) {
                this.setIssueStatusLocation(issueStatuses, source, workflowTransition.getSourceX(), workflowTransition.getSourceY());
            }
            //  대상 이슈 상태가 이미 저장되어 있는지 확인한다. - target 항목
            if (target != null) {
                this.setIssueStatusLocation(issueStatuses, target, workflowTransition.getTargetX(), workflowTransition.getTargetY());
            }
        }
        Iterator<String> iterator = issueStatuses.keySet().iterator();
        while (iterator.hasNext()) {
            IssueStatusVo issueStatusVo = (IssueStatusVo) issueStatuses.get(iterator.next());
            issueStatusVo.setWorkflowTransitionVos(this.workflowTransitionService.findBySourceIssueStatusIdAndWorkflowId(issueStatusVo.getId(), workflowId));
            issueStatusVos.add(issueStatusVo);
        }
        return issueStatusVos;
    }
    private void setIssueStatusLocation(Map<String, Object> issueStatuses, IssueStatus issueStatus, Long xLocation, Long yLocation) {
        Object targetIssueStatus = MapUtil.getObject(issueStatuses, issueStatus.getId().toString());
        if (targetIssueStatus == null) {
            IssueStatusVo issueStatusVo = ConvertUtil.copyProperties(issueStatus, IssueStatusVo.class);
            issueStatusVo.setxLocation(xLocation);
            issueStatusVo.setyLocation(yLocation);
            issueStatusVo.setIssueStatusType(issueStatus.getIssueStatusType().toString());
            issueStatuses.put(issueStatus.getId().toString(), issueStatusVo);
        }
    }
    //  이슈 상태 속성이 대기인 이슈 상태를 가져온다.
    @Override
    @Transactional(readOnly = true)
    public IssueStatus findByIssueStatusTypeIsReady(Workflow workflow) {
        List<IssueStatusVo> issueStatusVos = this.findByWorkflowId(workflow.getId());
        IssueStatus issueStatus = null;
        for (IssueStatusVo issueStatusVo : issueStatusVos) {
            if (IssueStatusType.READY.equals(IssueStatusType.valueOf(issueStatusVo.getIssueStatusType()))) {
                issueStatus = this.getIssueStatus(issueStatusVo.getId());
                break;
            }
        }
        if (issueStatus == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.READY_ISSUE_STATUS_NOT_EXIST));
        }
        return issueStatus;
    }
    //  이슈 상태를 변경할 때 선택한 이슈 상태로 변경할 수 있는지 확인한다.
    @Override
    @Transactional(readOnly = true)
    public void checkNextIssueStatus(Issue issue, IssueStatus nextIssueStatus) {
        Workflow workflow = issue.getIssueType().getWorkflow();
        boolean passNextIssueStatus = false;
        List<WorkflowTransitionVo> workflowTransitionVos = this.workflowTransitionService.findBySourceIssueStatusIdAndWorkflowId(issue.getIssueStatus().getId(), workflow.getId());
        for (WorkflowTransitionVo workflowTransitionVo : workflowTransitionVos) {
            if (workflowTransitionVo.getTargetStatusId().equals(nextIssueStatus.getId())) {
                passNextIssueStatus = true;
                break;
            }
        }
        if (!passNextIssueStatus) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_STATUS_CHANGE_NOT_TARGET));
        }
    }
    //  이슈 상태 목록을 엑셀로 다운로드 한다.
    @Override
    @Transactional
    public ModelAndView downloadExcel(HttpServletRequest request, Model model) {
        //  사용 공간에서 로그인한 사용자가 비활성인지 확인하고 비활성일 경우 엑셀 다운로드를 금지한다.
        ModelAndView modelAndView = this.workspaceService.checkUseExcelDownload(model);
        if (modelAndView != null) {
            return modelAndView;
        }
        Map<String, Object> conditions = new HashMap<>();
        //  엑셀 다운로드에 필요한 검색 조건 정보를 추출하고 검색 조건 추출에 오류가 발생하면 경고를 표시해준다.
        modelAndView = this.excelConditionCheck.checkCondition(conditions, request, model);
        if (modelAndView != null) {
            return modelAndView;
        }
        IssueStatusCondition issueStatusCondition = IssueStatusCondition.make(conditions);
        issueStatusCondition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        List<Map<String, Object>> results = this.issueStatusMapper.find(issueStatusCondition);
        List<IssueStatusVo> issueStatusVos = ConvertUtil.convertListToListClass(results, IssueStatusVo.class);
        //  이슈 상태가 워크플로우에 사용되고 있는지 여부를 체크하고 이슈 상태 안에 사용되는 워크플로우 정보를 셋팅한다.
        if (issueStatusCondition.getDeep() != null) {
            this.setUseIssueStatusByWorkflow(issueStatusVos);
        }
        ExportExcelVo excelInfo = new ExportExcelVo();
        excelInfo.setFileName(this.messageAccessor.message("common.issueStatusList")); // 이슈 상태 목록
        excelInfo.addAttrInfos(new ExportExcelAttrVo("name", this.messageAccessor.message("common.issueStatus"), 10, ExportExcelAttrVo.ALIGN_LEFT)); // 이슈 상태
        excelInfo.addAttrInfos(new ExportExcelAttrVo("issueStatusType", this.messageAccessor.message("common.statusProperties"), 6, ExportExcelAttrVo.ALIGN_CENTER)); // 상태 속성
        excelInfo.addAttrInfos(new ExportExcelAttrVo("color", this.messageAccessor.message("common.color"), 6, ExportExcelAttrVo.ALIGN_CENTER)); // 색상
        excelInfo.addAttrInfos(new ExportExcelAttrVo("workflowNames", this.messageAccessor.message("common.workflow"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 워크플로우
        //  엑셀에 넣을 데이터 - issueStatusVos 데이터를 엑셀에서 표시할 수 있는 데이터로 변경한다.
        excelInfo.setDatas(this.convertExcelViewToIssueStatusVos(issueStatusVos));
        model.addAttribute(Constants.EXCEL, excelInfo);
        return new ModelAndView(this.excelView);
    }
    //  IssueStatusVo 데이터를 엑셀에서 표시할 수 있는 데이터로 변경한다.
    private List<Map<String, String>> convertExcelViewToIssueStatusVos(List<IssueStatusVo> issueStatusVos) {
        List<Map<String, String>> results = Lists.newArrayList();
        for (IssueStatusVo issueStatusVo : issueStatusVos) {
            Map<String, String> result = new HashMap<>();
            result.put("name", issueStatusVo.getName());
            String issueStatusType = "";
            switch (issueStatusVo.getIssueStatusType()) {
                case "READY":
                    issueStatusType = this.messageAccessor.message("common.wait"); // 대기
                    break;
                case "OPEN":
                    issueStatusType = this.messageAccessor.message("common.progress"); // 진행
                    break;
                case "CLOSE":
                    issueStatusType = this.messageAccessor.message("common.end"); // 종료
                    break;
            }
            result.put("issueStatusType", issueStatusType);
            result.put("color", issueStatusVo.getColor());
            StringBuilder stringBuilder = new StringBuilder();
            for (WorkflowVo workflowVo : issueStatusVo.getWorkflowVos()) {
                stringBuilder.append(workflowVo.getName());
                stringBuilder.append("\n");
            }
            result.put("workflowNames", stringBuilder.toString());
            results.add(result);
        }
        return results;
    }
    //  여러건의 이슈의 현재 상태에서 이동 가능한 다음 상태 정보 목록을 찾는다.
    @Override
    @Transactional(readOnly = true)
    public void findNextMultiIssueStatus(Map<String, Object> resJsonData, IssueStatusCondition issueStatusCondition) {
        List<IssueStatusVo> issueStatusVos = Lists.newArrayList();
        int count = 0;
        //  워크플로우에서 이동 가능한 상태 추출
        for (Long issueId : issueStatusCondition.getIssueIds()) {
            Issue issue = this.issueService.getIssue(issueId);
            Workflow workflow = issue.getIssueType().getWorkflow();
            List<WorkflowTransitionVo> workflowTransitionVos = this.workflowTransitionService.findBySourceIssueStatusIdAndWorkflowId(issue.getIssueStatus().getId(), workflow.getId());
            List<IssueStatusVo> tempIssueStatusVos = Lists.newArrayList();
            for (WorkflowTransitionVo workflowTransitionVo : workflowTransitionVos) {
                //  첫번째 이슈에서 이동 가능한 상태 정보를 저장한다.
                if (count < 1) {
                    IssueStatusVo issueStatusVo = new IssueStatusVo(workflowTransitionVo.getTargetStatusId(), workflowTransitionVo.getTargetStatusName());
                    issueStatusVos.add(issueStatusVo);
                }
                else {
                    //  두번째 이슈부터 첫번째 이슈에서 이동 가능했던 상태 중 없는 대상을 찾는다.
                    for (IssueStatusVo issueStatusVo : issueStatusVos) {
                        if (issueStatusVo.getId().equals(workflowTransitionVo.getTargetStatusId())) {
                            tempIssueStatusVos.add(issueStatusVo);
                        }
                    }
                }
            }
            if (count > 0) {
                //  비교한 후 존재하는 이슈 상태만 저장
                issueStatusVos = tempIssueStatusVos;
            }
            count++;
        }
        resJsonData.put(Constants.RES_KEY_CONTENTS, issueStatusVos);
    }
}
src/main/java/kr/wisestone/owl/service/impl/IssueTableConfigServiceImpl.java
New file
@@ -0,0 +1,89 @@
package kr.wisestone.owl.service.impl;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.domain.IssueTableConfig;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.repository.IssueTableConfigRepository;
import kr.wisestone.owl.service.IssueTableConfigService;
import kr.wisestone.owl.service.UserService;
import kr.wisestone.owl.service.WorkspaceService;
import kr.wisestone.owl.util.MapUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Map;
@Service
public class IssueTableConfigServiceImpl extends AbstractServiceImpl<IssueTableConfig, Long, JpaRepository<IssueTableConfig, Long>> implements IssueTableConfigService {
    private static final Logger log = LoggerFactory.getLogger(IssueTableConfigServiceImpl.class);
    @Autowired
    private IssueTableConfigRepository issueTableConfigRepository;
    @Autowired
    private WorkspaceService workspaceService;
    @Autowired
    private UserService userService;
    @Override
    protected JpaRepository<IssueTableConfig, Long> getRepository() {
        return this.issueTableConfigRepository;
    }
    //  이슈 테이블 컬럼 설정 정보를 저장한다.
    @Override
    @Transactional
    public IssueTableConfig addIssueTableConfig(Map<String, Object> params) {
        String issueTableConfigs = MapUtil.getString(params, "issueTableConfigs");
        //  해당 업무 공간에서 사용자의 이슈 목록 테이블 컬럼 설정을 조회한다.
        IssueTableConfig saveIssueTableConfig = this.findByUserIdAndWorkspaceId();
        //  아직 테이블 컬럼 설정을 하지 않았을 경우
        if (saveIssueTableConfig == null) {
            IssueTableConfig issueTableConfig = new IssueTableConfig();
            Workspace workspace = this.workspaceService.getWorkspace(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
            User user = this.userService.getUser(this.webAppUtil.getLoginId());
            issueTableConfig.setWorkspace(workspace);
            issueTableConfig.setUser(user);
            issueTableConfig.setIssueTableConfigs(issueTableConfigs);
            return this.issueTableConfigRepository.saveAndFlush(issueTableConfig);
        }
        else {
            saveIssueTableConfig.setIssueTableConfigs(issueTableConfigs);
            return this.issueTableConfigRepository.saveAndFlush(saveIssueTableConfig);
        }
    }
    //  해당 업무 공간에서 사용자의 이슈 테이블 설정을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public IssueTableConfig findByUserIdAndWorkspaceId() {
        return this.issueTableConfigRepository.findByUserIdAndWorkspaceId(this.webAppUtil.getLoginId(),
                this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
    }
    //  저장된 이슈 테이블 설정을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public void detailIssueTableConfig(Map<String, Object> resJsonData) {
        //  해당 업무 공간에서 사용자의 이슈 검색 조건을 조회한다.
        IssueTableConfig issueTableConfig = this.findByUserIdAndWorkspaceId();
        if (issueTableConfig != null) {
            resJsonData.put(Constants.RES_KEY_CONTENTS, issueTableConfig.getIssueTableConfigs());
        }
        else {
            resJsonData.put(Constants.RES_KEY_CONTENTS, "");
        }
    }
}
src/main/java/kr/wisestone/owl/service/impl/IssueTypeCustomFieldServiceImpl.java
New file
@@ -0,0 +1,191 @@
package kr.wisestone.owl.service.impl;
import com.google.common.collect.Lists;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.domain.*;
import kr.wisestone.owl.repository.IssueTypeCustomFieldRepository;
import kr.wisestone.owl.service.*;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.util.MapUtil;
import kr.wisestone.owl.vo.CustomFieldValueVo;
import kr.wisestone.owl.vo.CustomFieldVo;
import kr.wisestone.owl.vo.IssueTypeCustomFieldVo;
import kr.wisestone.owl.web.condition.IssueTypeCustomFieldCondition;
import kr.wisestone.owl.web.form.IssueTypeCustomFieldForm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
@Service
public class IssueTypeCustomFieldServiceImpl extends AbstractServiceImpl<IssueTypeCustomField, Long, JpaRepository<IssueTypeCustomField, Long>> implements IssueTypeCustomFieldService {
    private static final Logger log = LoggerFactory.getLogger(IssueTypeCustomFieldServiceImpl.class);
    @Autowired
    private IssueTypeCustomFieldRepository issueTypeCustomFieldRepository;
    @Autowired
    private ProjectService projectService;
    @Autowired
    private IssueTypeService issueTypeService;
    @Autowired
    private CustomFieldService customFieldService;
    @Autowired
    private IssueCustomFieldValueService issueCustomFieldValueService;
    @Override
    protected JpaRepository<IssueTypeCustomField, Long> getRepository() {
        return this.issueTypeCustomFieldRepository;
    }
    //  프로젝트에서 사용하는 필드 정보를 업데이트한다.
    @Override
    @Transactional
    public void modifyIssueTypeCustomFields(IssueTypeCustomFieldForm issueTypeCustomFieldForm) {
        List<IssueTypeCustomField> issueTypeCustomFields = Lists.newArrayList();
        Project project = this.projectService.getProject(issueTypeCustomFieldForm.getProjectId());
        IssueType issueType = this.issueTypeService.getIssueType(issueTypeCustomFieldForm.getIssueTypeId());
        List<IssueTypeCustomField> saveIssueTypeCustomFields = this.issueTypeCustomFieldRepository.findByProjectIdAndIssueTypeId(project.getId(), issueType.getId());
        List<Long> addIssueTypeCustomFields = Lists.newArrayList();
        List<IssueTypeCustomField> removeIssueTypeCustomFields = Lists.newArrayList();
        //  저장해야할 대상 추출
        for (Map<String, Object> map : issueTypeCustomFieldForm.getRelationCustomFields()) {
            Long customFieldId = MapUtil.getLong(map, "id");
            boolean exist = false;
            for (IssueTypeCustomField issueTypeCustomField : saveIssueTypeCustomFields) {
                if (issueTypeCustomField.getCustomField().getId().equals(customFieldId)) {
                    exist = true;
                    break;
                }
            }
            if (!exist) {
                addIssueTypeCustomFields.add(customFieldId);
            }
        }
        //  삭제 대상 추출
        for (IssueTypeCustomField issueTypeCustomField : saveIssueTypeCustomFields) {
            boolean exist = false;
            for (Map<String, Object> map : issueTypeCustomFieldForm.getRelationCustomFields()) {
                Long customFieldId = MapUtil.getLong(map, "id");
                //  이미 저장되어 있는 대상은 따로 저장
                if (issueTypeCustomField.getCustomField().getId().equals(customFieldId)) {
                    exist = true;
                    break;
                }
            }
            if (!exist) {
                removeIssueTypeCustomFields.add(issueTypeCustomField);
            }
        }
        //  이슈 타입 - 사용자 정의 필드 삭제
        if (removeIssueTypeCustomFields.size() > 0) {
            for (IssueTypeCustomField issueTypeCustomField : removeIssueTypeCustomFields) {
                //  이슈 - 사용자 정의 필드 값 삭제
                this.issueCustomFieldValueService.removeIssueCustomFieldValue(issueTypeCustomField.getId());
            }
            this.issueTypeCustomFieldRepository.deleteAll(removeIssueTypeCustomFields);
        }
        for (Long customFieldId : addIssueTypeCustomFields) {
            CustomField customField = this.customFieldService.getCustomField(customFieldId);
            IssueTypeCustomField issueTypeCustomField = new IssueTypeCustomField(project, issueType, customField, IssueTypeCustomField.FIELD_OPTION_N);
            issueTypeCustomFields.add(issueTypeCustomField);
        }
        //  이슈 타입 - 사용자 정의 필드 정보 연결
        if (issueTypeCustomFields.size() > 0) {
            this.issueTypeCustomFieldRepository.saveAll(issueTypeCustomFields);
            this.issueTypeCustomFieldRepository.flush();
        }
        //  사용자 정의 필드가 이슈에서 표시되는 순서를 설정한다.
        this.setIssueTypeCustomFieldPosition(project.getId(), issueType.getId(), issueTypeCustomFieldForm.getRelationCustomFields());
    }
    //  사용자 정의 필드가 이슈에서 표시되는 순서를 설정한다.
    private void setIssueTypeCustomFieldPosition(Long projectId, Long issueTypeId, List<Map<String, Object>> relationCustomFields) {
        List<IssueTypeCustomField> saveIssueTypeCustomFields = this.issueTypeCustomFieldRepository.findByProjectIdAndIssueTypeId(projectId, issueTypeId);
        int count = 1;
        for (Map<String, Object> relationCustomField : relationCustomFields) {
            Long customFieldId = MapUtil.getLong(relationCustomField, "id");
            if (customFieldId != null) {
                for (IssueTypeCustomField issueTypeCustomField : saveIssueTypeCustomFields) {
                    if (issueTypeCustomField.getCustomField().getId().equals(customFieldId)) {
                        issueTypeCustomField.setPosition(count);
                        break;
                    }
                }
            }
            count++;
        }
        if (saveIssueTypeCustomFields.size() > 0) {
            this.issueTypeCustomFieldRepository.saveAll(saveIssueTypeCustomFields);
            this.issueTypeCustomFieldRepository.flush();
        }
    }
    //  프로젝트에서 사용하는 전체 필드 정보를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public List<IssueTypeCustomFieldVo> findIssueTypeCustomField(Map<String, Object> resJsonData, IssueTypeCustomFieldCondition condition) {
        Project project = this.projectService.getProject(condition.getProjectId());
        IssueType issueType = this.issueTypeService.getIssueType(condition.getIssueTypeId());
        List<IssueTypeCustomField> issueTypeCustomFields = this.issueTypeCustomFieldRepository.findByProjectIdAndIssueTypeIdOrderByPosition(project.getId(), issueType.getId());
        List<IssueTypeCustomFieldVo> issueTypeCustomFieldVos = Lists.newArrayList();
        for (IssueTypeCustomField issueTypeCustomField : issueTypeCustomFields) {
            IssueTypeCustomFieldVo issueTypeCustomFieldVo = new IssueTypeCustomFieldVo();
            CustomFieldVo customFieldVo = ConvertUtil.copyProperties(issueTypeCustomField.getCustomField(), CustomFieldVo.class);
            customFieldVo.setCustomFieldValueVos(ConvertUtil.convertObjectsToClasses(issueTypeCustomField.getCustomField().getCustomFieldValues(), CustomFieldValueVo.class));
            customFieldVo.setCustomFieldType(issueTypeCustomField.getCustomField().getCustomFieldType().toString());
            issueTypeCustomFieldVo.setCustomFieldVo(customFieldVo);
            issueTypeCustomFieldVo.setFieldOption(issueTypeCustomField.getFieldOption());
            issueTypeCustomFieldVo.setChecked(issueTypeCustomField.getFieldOption().equals(IssueTypeCustomField.FIELD_OPTION_Y));   //  화면에서 옵션 체크되도록 checked 에 값 셋팅
            issueTypeCustomFieldVos.add(issueTypeCustomFieldVo);
        }
        resJsonData.put(Constants.RES_KEY_CONTENTS, issueTypeCustomFieldVos);
        return issueTypeCustomFieldVos;
    }
    //  이슈 excel template download 에서 사용
    @Override
    @Transactional(readOnly = true)
    public List<IssueTypeCustomField> findByProjectIdAndIssueTypeId(Long projectId, Long issueTypeId) {
        return this.issueTypeCustomFieldRepository.findByProjectIdAndIssueTypeId(projectId, issueTypeId);
    }
    //  이슈 add / modify 에서 사용
    @Override
    @Transactional(readOnly = true)
    public IssueTypeCustomField findByProjectIdAndIssueTypeIdAndCustomFieldId(Long projectId, Long issueTypeId, Long customFieldId) {
        return this.issueTypeCustomFieldRepository.findByProjectIdAndIssueTypeIdAndCustomFieldId(projectId, issueTypeId, customFieldId);
    }
}
src/main/java/kr/wisestone/owl/service/impl/IssueTypeServiceImpl.java
New file
@@ -0,0 +1,353 @@
package kr.wisestone.owl.service.impl;
import com.google.common.collect.Lists;
import kr.wisestone.owl.common.ExcelConditionCheck;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.*;
import kr.wisestone.owl.domain.enumType.ProjectType;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.mapper.IssueTypeMapper;
import kr.wisestone.owl.repository.IssueTypeRepository;
import kr.wisestone.owl.service.*;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.vo.*;
import kr.wisestone.owl.web.condition.IssueTypeCondition;
import kr.wisestone.owl.web.form.IssueTypeForm;
import kr.wisestone.owl.web.view.ExcelView;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class IssueTypeServiceImpl extends AbstractServiceImpl<IssueType, Long, JpaRepository<IssueType, Long>> implements IssueTypeService {
    private static final Logger log = LoggerFactory.getLogger(IssueTypeServiceImpl.class);
    @Autowired
    private IssueTypeRepository issueTypeRepository;
    @Autowired
    private WorkflowService workflowService;
    @Autowired
    private WorkspaceService workspaceService;
    @Autowired
    private IssueTypeMapper issueTypeMapper;
    @Autowired
    private IssueService issueService;
    @Autowired
    private UserService userService;
    @Autowired
    private ExcelView excelView;
    @Autowired
    private ExcelConditionCheck excelConditionCheck;
    @Override
    protected JpaRepository<IssueType, Long> getRepository() {
        return this.issueTypeRepository;
    }
    @Override
    @Transactional
    public void addDefaultIssueType(Workspace workspace, List<ProjectType> projectTypes) {
        for (ProjectType projectType : projectTypes) {
            List<IssueType> issueTypes = Lists.newArrayList();
            Workflow workflow = this.workflowService.findByWorkspaceIdAndProjectType(workspace.getId(), projectType);
            switch (projectType) {
                case BTS_PROJECT:
                    issueTypes.add(new IssueType(workspace, workflow, this.messageAccessor.message("common.bug"), "", "#ff5f99")); // 버그
                    issueTypes.add(new IssueType(workspace, workflow, this.messageAccessor.message("common.improvement"), "", "#3598fe")); // 개선
                    break;
                case RMS_PROJECT:
                    issueTypes.add(new IssueType(workspace, workflow, this.messageAccessor.message("common.requirement"), "", "#3bcde2")); // 요구 사항
                    break;
                case TCM_PROJECT:
                    issueTypes.add(new IssueType(workspace, workflow, this.messageAccessor.message("common.testcase"), "", "#008ca7")); // 테스트 케이스, 실행 순서, 전제 조건, 기대 결과
                    break;
            }
            this.issueTypeRepository.saveAll(issueTypes);
        }
    }
    //  이슈 유형을 생성한다.
    @Override
    @Transactional
    public IssueType addIssueType(IssueTypeForm issueTypeForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        //  이름 유효성 체크
        this.verifyName(issueTypeForm.getName(), null);
        //  색상 체크
        this.verifyColor(issueTypeForm.getColor());
        IssueType issueType = ConvertUtil.copyProperties(issueTypeForm, IssueType.class);
        Workspace workspace = this.workspaceService.getWorkspace(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        issueType.setWorkspace(workspace);
        Workflow workflow = this.workflowService.getWorkflow(issueTypeForm.getWorkflowId());
        issueType.setWorkflow(workflow);
        return this.issueTypeRepository.saveAndFlush(issueType);
    }
    //  이름 유효성 체크
    private void verifyName(String name, Long id) {
        if (StringUtils.isEmpty(name)) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_TYPE_NOT_NAME));
        }
        if (name.length() > 15) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_TYPE_NAME_MAX_LENGTH_OUT));
        }
        IssueType issueType;
        Long workspaceId = this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId();
        if (id == null) {
            issueType = this.issueTypeRepository.findByNameAndWorkspaceId(name, workspaceId);
        } else {
            issueType = this.issueTypeRepository.findByNameAndWorkspaceIdAndIdNot(name, workspaceId, id);
        }
        if (issueType != null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_TYPE_USED_NAME));
        }
    }
    //  색상 체크
    private void verifyColor(String color) {
        if (StringUtils.isEmpty(color)) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_TYPE_NOT_COLOR));
        }
    }
    //  이슈 유형 목록을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public List<IssueTypeVo> findIssueType(Map<String, Object> resJsonData,
                                           IssueTypeCondition condition, Pageable pageable) {
        condition.setPage(pageable.getPageNumber() * pageable.getPageSize());
        condition.setPageSize(pageable.getPageSize());
        condition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        List<Map<String, Object>> results = this.issueTypeMapper.find(condition);
        Long totalCount = this.issueTypeMapper.count(condition);
        int totalPage = (int) Math.ceil((totalCount - 1) / pageable.getPageSize()) + 1;
        List<IssueTypeVo> issueTypeVos = ConvertUtil.convertListToListClass(results, IssueTypeVo.class);
        //  이슈 유형에 연결된 워크플로우 정보를 셋팅한다.
        if (condition.getDeep() != null) {
            this.setUseIssueTypeByWorkflow(issueTypeVos);
        }
        resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(),
                totalPage, totalCount));
        resJsonData.put(Constants.RES_KEY_CONTENTS, issueTypeVos);
        return issueTypeVos;
    }
    //  이슈 유형에 연결된 워크플로우 정보를 셋팅한다.
    private void setUseIssueTypeByWorkflow(List<IssueTypeVo> issueTypeVos) {
        for (IssueTypeVo issueTypeVo : issueTypeVos) {
            IssueType issueType = this.getIssueType(issueTypeVo.getId());
            issueTypeVo.setWorkflowVo(ConvertUtil.copyProperties(issueType.getWorkflow(), WorkflowVo.class));
        }
    }
    //  이슈 유형 상세 정보를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public void detailIssueType(Map<String, Object> resJsonData, IssueTypeCondition issueTypeCondition) {
        IssueTypeVo issueTypeVo = new IssueTypeVo();
        if (issueTypeCondition.getId() != null) {
            IssueType issueType = this.getIssueType(issueTypeCondition.getId());
            issueTypeVo = ConvertUtil.copyProperties(issueType, IssueTypeVo.class);
            switch (issueTypeCondition.getDeep()) {
                case "01": //  워크플로우 정보를 가져온다.
                    issueTypeVo.setWorkflowVo(ConvertUtil.copyProperties(issueType.getWorkflow(), WorkflowVo.class));
                    break;
            }
        }
        resJsonData.put(Constants.RES_KEY_CONTENTS, issueTypeVo);
    }
    //  이슈 유형을 수정한다.
    @Override
    @Transactional
    public IssueType modifyIssueType(IssueTypeForm issueTypeForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        IssueType issueType = this.getIssueType(issueTypeForm.getId());
        //  이름 유효성 체크
        this.verifyName(issueTypeForm.getName(), issueTypeForm.getId());
        //  색상 체크
        this.verifyColor(issueTypeForm.getColor());
        Workflow workflow = this.workflowService.getWorkflow(issueTypeForm.getWorkflowId());
        //  워크플로우 변경 체크
        this.checkWorkflowChange(issueType.getWorkflow(), workflow, issueType);
        ConvertUtil.copyProperties(issueTypeForm, issueType, "id", "issueTypeType");
        issueType.setWorkflow(workflow);
        this.issueTypeRepository.saveAndFlush(issueType);
        return issueType;
    }
    //  워크플로우가 변경되었는지 확인하고 변경되었을 경우 이슈 상태가 없는 이슈는 '생성' 인 이슈 상태로 이동한다.
    private void checkWorkflowChange(Workflow oldWorkflow, Workflow newWorkflow, IssueType issueType) {
        if (!oldWorkflow.getId().equals(newWorkflow.getId())) {
            //  이슈 유형에서 워크플로우가 변경되었을 때 해당 워크플로우에 현재 이슈 상태가 존재하지 않으면 대기(생성) 로 변경한다.
            this.issueService.changeWorkflows(newWorkflow, issueType);
        }
    }
    @Override
    @Transactional(readOnly = true)
    public IssueType getIssueType(Long id) {
        if (id == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_TYPE_NOT_EXIST));
        }
        IssueType issueType = this.findOne(id);
        if (issueType == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_TYPE_NOT_EXIST));
        }
        return issueType;
    }
    //  이슈 유형을 삭제한다.
    @Override
    @Transactional
    public void removeIssueTypes(IssueTypeForm issueTypeForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        if (issueTypeForm.getRemoveIds().size() < 1) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_TYPE_REMOVE_NOT_SELECT));
        }
        for (Long projectId : issueTypeForm.getRemoveIds()) {
            this.removeIssueTypes(projectId);
        }
        this.issueTypeRepository.flush();
    }
    private void removeIssueTypes(Long issueTypeId) {
        IssueType issueType = this.getIssueType(issueTypeId);
        //  삭제할 이슈 유형이 사용되고 있는지 확인한다.
        this.checkUseIssueType(issueType);
        this.issueTypeRepository.delete(issueType);
    }
    //  삭제할 이슈 유형이 사용되고 있는지 확인한다.
    private void checkUseIssueType(IssueType issueType) {
        //  이슈 유형을 사용하는 이슈 갯수를 조회한다.
        Long issueCount = this.issueService.countByIssueTypeId(issueType.getId());
        if (issueCount > 0) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_TYPE_USE_ISSUES));
        }
    }
    //  워크스페이스에 있는 모든 이슈 유형을 조회한다. 이슈 엑셀 import 에서 사용
    @Override
    @Transactional(readOnly = true)
    public List<IssueType> findByWorkspaceId() {
        return this.issueTypeRepository.findByWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
    }
    //  이슈 타입 필드 목록을 엑셀로 다운로드 한다.
    @Override
    @Transactional
    public ModelAndView downloadExcel(HttpServletRequest request, Model model) {
        //  사용 공간에서 로그인한 사용자가 비활성인지 확인하고 비활성일 경우 엑셀 다운로드를 금지한다.
        ModelAndView modelAndView = this.workspaceService.checkUseExcelDownload(model);
        if (modelAndView != null) {
            return modelAndView;
        }
        Map<String, Object> conditions = new HashMap<>();
        //  엑셀 다운로드에 필요한 검색 조건 정보를 추출하고 검색 조건 추출에 오류가 발생하면 경고를 표시해준다.
        modelAndView = this.excelConditionCheck.checkCondition(conditions, request, model);
        if (modelAndView != null) {
            return modelAndView;
        }
        IssueTypeCondition issueTypeCondition = IssueTypeCondition.make(conditions);
        issueTypeCondition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        List<Map<String, Object>> results = this.issueTypeMapper.find(issueTypeCondition);
        List<IssueTypeVo> issueTypeVos = ConvertUtil.convertListToListClass(results, IssueTypeVo.class);
        //  이슈 유형에 연결된 워크플로우 정보를 셋팅한다.
        if (issueTypeCondition.getDeep() != null) {
            this.setUseIssueTypeByWorkflow(issueTypeVos);
        }
        ExportExcelVo excelInfo = new ExportExcelVo();
        excelInfo.setFileName(this.messageAccessor.message("common.issueTypeList")); // 이슈 타입 목록
        excelInfo.addAttrInfos(new ExportExcelAttrVo("name", this.messageAccessor.message("common.issueType"), 15, ExportExcelAttrVo.ALIGN_LEFT)); // 이슈 타입
        excelInfo.addAttrInfos(new ExportExcelAttrVo("color", this.messageAccessor.message("common.color"), 6, ExportExcelAttrVo.ALIGN_CENTER)); // 색상
        excelInfo.addAttrInfos(new ExportExcelAttrVo("workflowName", this.messageAccessor.message("common.workflow"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 워크플로우
        //  엑셀에 넣을 데이터
        excelInfo.setDatas(this.convertExcelViewToIssueTypeVos(issueTypeVos));
        model.addAttribute(Constants.EXCEL, excelInfo);
        return new ModelAndView(this.excelView);
    }
    //  IssueTypeVo 데이터를 엑셀에서 표시할 수 있는 데이터로 변경한다.
    private List<Map<String, String>> convertExcelViewToIssueTypeVos(List<IssueTypeVo> issueTypeVos) {
        List<Map<String, String>> results = Lists.newArrayList();
        for (IssueTypeVo issueTypeVo : issueTypeVos) {
            Map<String, String> result = new HashMap<>();
            result.put("name", issueTypeVo.getName());
            result.put("color", issueTypeVo.getColor());
            result.put("workflowName", issueTypeVo.getWorkflowVo().getName());
            results.add(result);
        }
        return results;
    }
}
src/main/java/kr/wisestone/owl/service/impl/IssueUserServiceImpl.java
New file
@@ -0,0 +1,128 @@
package kr.wisestone.owl.service.impl;
import com.google.common.collect.Lists;
import kr.wisestone.owl.domain.*;
import kr.wisestone.owl.mapper.IssueUserMapper;
import kr.wisestone.owl.repository.IssueUserRepository;
import kr.wisestone.owl.service.IssueUserService;
import kr.wisestone.owl.service.ProjectService;
import kr.wisestone.owl.util.CommonUtil;
import kr.wisestone.owl.util.MapUtil;
import kr.wisestone.owl.web.form.IssueForm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class IssueUserServiceImpl extends AbstractServiceImpl<IssueUser, Long, JpaRepository<IssueUser, Long>> implements IssueUserService {
    private static final Logger log = LoggerFactory.getLogger(IssueUserServiceImpl.class);
    @Autowired
    private IssueUserRepository issueUserRepository;
    @Autowired
    private IssueUserMapper issueUserMapper;
    @Override
    protected JpaRepository<IssueUser, Long> getRepository() {
        return this.issueUserRepository;
    }
    //  이슈 담당자를 변경한다.
    @Override
    @Transactional
    public void modifyIssueUser(Issue issue, Workspace workspace, List<Long> userIds) {
        List<Long> oldUserIds = Lists.newArrayList();
        //  이전 담당자
        for (IssueUser issueUser : issue.getIssueUsers()) {
            oldUserIds.add(issueUser.getUser().getId());
        }
        List<Long> newUserIds = CommonUtil.searchChangeList(oldUserIds, userIds); //  추가해야할 사용자를 찾는다.
        List<Long> removeUserIds = CommonUtil.searchChangeList(userIds, oldUserIds); //  삭제해야할 사용자를 찾는다.
        if (removeUserIds.size() > 0) {
            Map<String, Object> removeIssueAssigneeMap = new HashMap<>();
            removeIssueAssigneeMap.put("issueId", issue.getId());
            removeIssueAssigneeMap.put("userIds", removeUserIds);
            //  담당자 삭제
            this.issueUserMapper.deleteIssueUserByIssueIdAndMultiUserId(removeIssueAssigneeMap);
        }
        if (newUserIds.size() > 0) {
            List<Map<String, Long>> addIssueAssigneeMaps = Lists.newArrayList();
            for (Long userId : newUserIds) {
                Map<String, Long> issueAssigneeMap = new HashMap<>();
                issueAssigneeMap.put("userId", userId);
                issueAssigneeMap.put("issueId", issue.getId());
                issueAssigneeMap.put("workspaceId", workspace.getId());
                issueAssigneeMap.put("registerId", this.webAppUtil.getLoginId());
                addIssueAssigneeMaps.add(issueAssigneeMap);
            }
            //  담당자 추가
            this.issueUserMapper.insertIssueUser(addIssueAssigneeMaps);
        }
    }
    //  이슈 담당자 찾기
    @Override
    @Transactional
    public List<IssueUser> find(Issue issue) {
        return this.issueUserRepository.findByIssueId(issue.getId());
    }
    //  이슈 담당자 벌크 등록
    @Override
    @Transactional
    public void insertIssueUser(List<Map<String, Long>> issueAssigneeMaps) {
        //  이슈 담당자 벌크 등록
        this.issueUserMapper.insertIssueUser(issueAssigneeMaps);
    }
    //  이슈 담당자에서 제외한다.
    @Override
    @Transactional
    public void removeIssueUser(Long projectId, List<Long> excludeUserIds) {
        for (Long userId : excludeUserIds) {
            Map<String, Object> issueUserMap = new HashMap<>();
            issueUserMap.put("userId", userId);
            issueUserMap.put("projectId", projectId);
            List<Map<String, Object>> results = this.issueUserMapper.findByUserIdAndProjectId(issueUserMap);
            if (results.size() > 0) {
                List<Long> issueIds = Lists.newArrayList();
                for (Map<String, Object> result : results) {
                    Long id = MapUtil.getLong(result, "id");
                    if (id != null) {
                        issueIds.add(id);
                    }
                }
                if (issueIds.size() > 0) {
                    Map<String, Object> removeIssueAssigneeMap = new HashMap<>();
                    removeIssueAssigneeMap.put("userId", userId);
                    removeIssueAssigneeMap.put("issueIds", issueIds);
                    this.issueUserMapper.deleteIssueUserByUserIdAndMultiIssueId(removeIssueAssigneeMap);
                }
            }
        }
    }
}
src/main/java/kr/wisestone/owl/service/impl/IssueVersionServiceImpl.java
New file
@@ -0,0 +1,146 @@
package kr.wisestone.owl.service.impl;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.domain.Issue;
import kr.wisestone.owl.domain.IssueVersion;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.repository.IssueVersionRepository;
import kr.wisestone.owl.service.IssueService;
import kr.wisestone.owl.service.IssueVersionService;
import kr.wisestone.owl.service.UserService;
import kr.wisestone.owl.util.CommonUtil;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.vo.IssueVersionVo;
import kr.wisestone.owl.vo.IssueVo;
import kr.wisestone.owl.web.condition.IssueVersionCondition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class IssueVersionServiceImpl extends AbstractServiceImpl<IssueVersion, Long, JpaRepository<IssueVersion, Long>> implements IssueVersionService {
    private static final Logger log = LoggerFactory.getLogger(IssueVersionServiceImpl.class);
    @Autowired
    private IssueVersionRepository issueVersionRepository;
    @Autowired
    private IssueService issueService;
    @Autowired
    private UserService userService;
    @Override
    protected JpaRepository<IssueVersion, Long> getRepository() {
        return this.issueVersionRepository;
    }
    //  이슈 버전 생성
    @Override
    @Transactional
    public void addIssueVersion(Issue issue) {
        IssueVo issueVo = ConvertUtil.copyProperties(issue, IssueVo.class);
        this.issueService.setIssueDetail(issueVo, issue);    //  이슈 상세 정보를 셋팅한다.
        List<IssueVersion> issueVersions = this.issueVersionRepository.findByIssueId(issueVo.getId());
        Gson gson = new Gson();
        String content = gson.toJson(issueVo);
        IssueVersion issueVersion = new IssueVersion(issue, issue.getProject(), issue.getProject().getWorkspace(), content);
        if (issueVersions.size() > 0) {
            IssueVersion prevIssueVersion = issueVersions.get(issueVersions.size() - 1);
            issueVersion.setVersion(prevIssueVersion.getVersion() + 1);
        }
        else {
            issueVersion.setVersion(1);
        }
        this.issueVersionRepository.saveAndFlush(issueVersion);
    }
    //  이슈 버전 정보를 만들어서 Vo 로 변환한다.
    private IssueVersionVo makeIssueVersionVo(IssueVersion issueVersion) {
        IssueVersionVo issueVersionVo = ConvertUtil.copyProperties(issueVersion, IssueVersionVo.class); //  이전 버전
        Gson gson = new Gson();
        Map<String, Object> targetIssueVersionContent = (Map<String, Object>) gson.fromJson(issueVersionVo.getContent(), Object.class);
        issueVersionVo.setContent(null);
        issueVersionVo.setVersionHistory(targetIssueVersionContent);
        User beforeModifyUser = this.userService.getUser(issueVersionVo.getModifyId());
        issueVersionVo.setModifyByName(beforeModifyUser.getName() + "(" + CommonUtil.decryptAES128(beforeModifyUser.getAccount()) + ")");
        return issueVersionVo;
    }
    //  선택한 버전과 그 다음 버전 정보를 Vo로 만들어 리턴한다.
    private void makeTargetAndNextIssueVersion(IssueVersion targetIssueVersion, IssueVersion nextIssueVersion, List<IssueVersion> issueVersions, Map<String, Object> results) {
        //  이슈 버전 정보를 만들어서 Vo 로 변환한다.
        IssueVersionVo targetIssueVersionVo = this.makeIssueVersionVo(targetIssueVersion);
        //  이슈 버전 정보를 만들어서 Vo 로 변환한다.
        IssueVersionVo nextIssueVersionVo = this.makeIssueVersionVo(nextIssueVersion);
        results.put("targetIssueVersionVo", targetIssueVersionVo);
        results.put("nextIssueVersionVo", nextIssueVersionVo);
        List<IssueVersionVo> issueVersionVos = ConvertUtil.convertObjectsToClasses(issueVersions, IssueVersionVo.class);
        issueVersionVos.remove(issueVersionVos.size() - 1);
        results.put("issueVersionList", issueVersionVos);
    }
    //  이슈 버전 정보 조회
    @Override
    @Transactional(readOnly = true)
    public void find(Map<String, Object> resJsonData, IssueVersionCondition issueVersionCondition) {
        List<IssueVersion> issueVersions = this.issueVersionRepository.findByIssueId(issueVersionCondition.getIssueId());
        Map<String, Object> results = new HashMap<>();
        results.put("targetIssueVersionVo", null);
        results.put("nextIssueVersionVo", null);
        results.put("issueVersionList", Lists.newArrayList());
        if (issueVersionCondition.getId() == null) {
            if (issueVersions.size() > 1) {
                IssueVersion targetIssueVersion = issueVersions.get(issueVersions.size() - 2);
                IssueVersion nextIssueVersion = issueVersions.get(issueVersions.size() - 1);
                //  선택한 버전과 그 다음 버전 정보를 Vo 로 만들어 리턴한다.
                this.makeTargetAndNextIssueVersion(targetIssueVersion, nextIssueVersion, issueVersions, results);
            }
        }
        else {
            IssueVersion targetIssueVersion = null; //  선택한 버전
            IssueVersion nextIssueVersion = null;   //  선택한 버전의 다음 버전
            for (IssueVersion issueVersion : issueVersions) {
                //  선택한 버전을 찾았으면 그 다음 버전을 찾는다.
                if (targetIssueVersion != null) {
                    nextIssueVersion = issueVersion;
                    break;
                }
                //  선택한 버전을 찾는다.
                if (issueVersion.getId().equals(issueVersionCondition.getId())) {
                    targetIssueVersion = issueVersion;
                }
            }
            //  둘다 존재할 때만 버전 비교가 가능하다.
            if (targetIssueVersion != null && nextIssueVersion != null) {
                //  선택한 버전과 그 다음 버전 정보를 Vo 로 만들어 리턴한다.
                this.makeTargetAndNextIssueVersion(targetIssueVersion, nextIssueVersion, issueVersions, results);
            }
        }
        resJsonData.put(Constants.RES_KEY_CONTENTS, results);
    }
}
src/main/java/kr/wisestone/owl/service/impl/ManageUserServiceImpl.java
New file
@@ -0,0 +1,113 @@
package kr.wisestone.owl.service.impl;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.MngPermission;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.domain.UserWorkspace;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.mapper.UserWorkspaceMapper;
import kr.wisestone.owl.repository.UserRepository;
import kr.wisestone.owl.repository.UserWorkspaceRepository;
import kr.wisestone.owl.service.UserService;
import kr.wisestone.owl.service.ManageUserService;
import kr.wisestone.owl.service.WorkspaceService;
import kr.wisestone.owl.util.CommonUtil;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.util.SecurityUtils;
import kr.wisestone.owl.vo.ResPage;
import kr.wisestone.owl.vo.ManageUserVo;
import kr.wisestone.owl.web.condition.UserWorkspaceCondition;
import kr.wisestone.owl.web.form.ManageUserForm;
import kr.wisestone.owl.web.form.UserWorkspaceForm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Map;
@Service
public class ManageUserServiceImpl extends AbstractServiceImpl<UserWorkspace, Long, JpaRepository<UserWorkspace, Long>>  implements ManageUserService {
    private static final Logger log = LoggerFactory.getLogger(ManageUserServiceImpl.class);
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private UserWorkspaceRepository userWorkspaceRepository;
    @Autowired
    private UserService userService;
    @Autowired
    private UserWorkspaceMapper userWorkspaceMapper;
    @Autowired
    private WorkspaceService workspaceService;
    @Override
    protected JpaRepository<UserWorkspace, Long> getRepository() {
        return this.userWorkspaceRepository;
    }
    //  업무 공간에 참여/참여대기 하는 전체 사용자 목록을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public List<ManageUserVo> findUserPermission(Map<String, Object> resJsonData,
                                                   UserWorkspaceCondition condition, Pageable pageable) {
        //  업무 공간를 가져온다.
        Workspace primaryWorkspace = this.workspaceService.getPrimaryWorkspace();
        condition.setPage(pageable.getPageNumber() * pageable.getPageSize());
        condition.setPageSize(pageable.getPageSize());
        condition.setWorkspaceId(primaryWorkspace.getId());
        condition.setAccount(CommonUtil.encryptAES128(condition.getAccount()));
        List<Map<String, Object>> results = this.userWorkspaceMapper.find(condition);
        Long totalCount = this.userWorkspaceMapper.count(condition);
        int totalPage = (int) Math.ceil((totalCount - 1) / pageable.getPageSize()) + 1;
        List<ManageUserVo> manageUserVos = ConvertUtil.convertListToListClass(results, ManageUserVo.class);
        for (ManageUserVo manageUserVo : manageUserVos) {
            manageUserVo.setAccount(CommonUtil.decryptAES128(manageUserVo.getAccount()));
            manageUserVo.setPermission(manageUserVo.getPermission());
        }
        resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(),
                totalPage, totalCount));
        resJsonData.put(Constants.RES_KEY_CONTENTS, manageUserVos);
        return manageUserVos;
    }
    //  업무 공간에 연결된 사용자의 참여 상태를 변경한다.
    @Override
    @Transactional
    public void modifyUserPermission(ManageUserForm manageUserForm) {
        int newPermission = MngPermission.USER_PERMISSION_MNG_NONE;
        newPermission |= MngPermission.makePermission(manageUserForm.getPermWorkSpace(), MngPermission.USER_PERMISSION_MNG_WORKSPACE);
        newPermission |= MngPermission.makePermission(manageUserForm.getPermProjectSetting(), MngPermission.USER_PERMISSION_MNG_PROJECT);
        newPermission |= MngPermission.makePermission(manageUserForm.getPermIssueSetting(), MngPermission.USER_PERMISSION_MNG_ISSUE_SETTING);
        newPermission |= MngPermission.makePermission(manageUserForm.getPermUser(), MngPermission.USER_PERMISSION_MNG_USER);
        newPermission |= MngPermission.makePermission(manageUserForm.getPermNotice(), MngPermission.USER_PERMISSION_MNG_NOTICE);
        newPermission |= MngPermission.makePermission(manageUserForm.getPermFAQ(), MngPermission.USER_PERMISSION_MNG_FAQ);
        newPermission |= MngPermission.makePermission(manageUserForm.getPermQnA(), MngPermission.USER_PERMISSION_MNG_QNA);
        newPermission |= MngPermission.makePermission(manageUserForm.getPermEvent(), MngPermission.USER_PERMISSION_MNG_EVENT);
        newPermission |= MngPermission.makePermission(manageUserForm.getPermGuide(), MngPermission.USER_PERMISSION_MNG_GUIDE);
        User user = userService.getUser(manageUserForm.getUserId());
        user.setPermission(newPermission);
        this.userRepository.saveAndFlush(user);
    }
}
src/main/java/kr/wisestone/owl/service/impl/NoticeServiceImpl.java
New file
@@ -0,0 +1,156 @@
package kr.wisestone.owl.service.impl;
import kr.wisestone.owl.config.kafka.KafkaSender;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.Notice;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.mapper.NoticeMapper;
import kr.wisestone.owl.repository.NoticeRepository;
import kr.wisestone.owl.service.NoticeService;
import kr.wisestone.owl.service.UserService;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.vo.NoticeVo;
import kr.wisestone.owl.vo.ResPage;
import kr.wisestone.owl.web.condition.NoticeCondition;
import kr.wisestone.owl.web.form.NoticeForm;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class NoticeServiceImpl extends AbstractServiceImpl<Notice, Long, JpaRepository<Notice, Long>> implements NoticeService {
    @Autowired
    private NoticeRepository noticeRepository;
    @Autowired
    private UserService userService;
    @Autowired
    private KafkaSender kafkaSender;
    @Autowired
    private NoticeMapper noticeMapper;
    @Override
    protected JpaRepository<Notice, Long> getRepository() {
        return this.noticeRepository;
    }
    //  공지 사항 등록
    @Override
    @Transactional
    public Notice addNotice(NoticeForm noticeForm) {
        //  공지사항 제목 및 내용 공백 체크
        this.verifyTitleAndDescription(noticeForm.getTitle(), noticeForm.getDescription());
        Notice notice = ConvertUtil.copyProperties(noticeForm, Notice.class);
        return this.noticeRepository.saveAndFlush(notice);
    }
    //  공지사항 제목 및 내용 공백 체크
    private void verifyTitleAndDescription(String title, String description) {
        if (StringUtils.isEmpty(title) || StringUtils.isEmpty(description)) {
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.NOTICE_EMPTY_CONTENT));
        }
    }
    //  공지사항 조회
    @Override
    @Transactional(readOnly = true)
    public List<NoticeVo> findNotice(Map<String, Object> resJsonData,
                                     NoticeCondition noticeCondition, Pageable pageable) {
        noticeCondition.setPage(pageable.getPageNumber() * pageable.getPageSize());
        noticeCondition.setPageSize(pageable.getPageSize());
        noticeCondition.setTitle(noticeCondition.getTitle());
        List<Map<String, Object>> results = this.noticeMapper.find(noticeCondition);
        Long totalCount = this.noticeMapper.count(noticeCondition);
        int totalPage = (int) Math.ceil((totalCount - 1) / pageable.getPageSize()) + 1;
        List<NoticeVo> noticeVos = ConvertUtil.convertListToListClass(results, NoticeVo.class);
        //  로그인 아이디 1 은 관리자 - 관리자만 수정 가능
        for (NoticeVo noticeVo : noticeVos) {
            if (this.webAppUtil.getLoginId().equals(1L)) {
                noticeVo.setModifyPermissionCheck(true);
            }
        }
        resJsonData.put(Constants.RES_KEY_CONTENTS, noticeVos);
        resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(),
                totalPage, totalCount));
        return noticeVos;
    }
    //  공지사항 수정
    @Override
    @Transactional
    public Notice modifyNotice(NoticeForm noticeForm) {
        //  공지사항 제목 및 내용 공백 체크
        this.verifyTitleAndDescription(noticeForm.getTitle(), noticeForm.getDescription());
        Notice notice = this.getNotice(noticeForm.getId());
        ConvertUtil.copyProperties(noticeForm, notice, "id");
        return this.noticeRepository.saveAndFlush(notice);
    }
    //  공지사항을 id 로 조회한다.
    @Override
    @Transactional(readOnly = true)
    public Notice getNotice(Long id) {
        if (id == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.NOTICE_NOT_EXIST));
        }
        Notice notice = this.findOne(id);
        if (notice == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.NOTICE_NOT_EXIST));
        }
        return notice;
    }
    //  공지사항 상세 정보를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public void detailNotice(Map<String, Object> resJsonData, NoticeCondition noticeCondition) {
        NoticeVo noticeVo = new NoticeVo();
        if (noticeCondition.getId() != null) {
            Notice notice = this.getNotice(noticeCondition.getId());
            noticeVo = ConvertUtil.copyProperties(notice, NoticeVo.class);
        }
        resJsonData.put(Constants.RES_KEY_CONTENTS, noticeVo);
    }
    //  메세지 보내기
    @Override
    @Transactional
    public void sendNotice(NoticeForm noticeForm) {
        for (Long userId : noticeForm.getUserIds()) {
            User user = this.userService.getUser(userId);
            Map<String, Object> message = new HashMap<>();
            message.put("url", "/notification/message");
            message.put("message", noticeForm.getDescription());
            message.put("account", user.getAccount());
            this.kafkaSender.send("common-topic", message);
        }
    }
}
src/main/java/kr/wisestone/owl/service/impl/PaymentHistoryServiceImpl.java
New file
@@ -0,0 +1,78 @@
package kr.wisestone.owl.service.impl;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.PaymentHistory;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.repository.PaymentHistoryRepository;
import kr.wisestone.owl.service.PaymentHistoryService;
import kr.wisestone.owl.web.form.PaymentForm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class PaymentHistoryServiceImpl extends AbstractServiceImpl<PaymentHistory, Long, JpaRepository<PaymentHistory, Long>> implements PaymentHistoryService {
    private static final Logger log = LoggerFactory.getLogger(PaymentHistoryServiceImpl.class);
    @Autowired
    private PaymentHistoryRepository paymentHistoryRepository;
    @Override
    protected JpaRepository<PaymentHistory, Long> getRepository() {
        return this.paymentHistoryRepository;
    }
    @Override
    @Transactional
    public PaymentHistory addPaymentHistory(PaymentServiceImpl.RestClientResultObject resultObject, PaymentForm paymentForm, Workspace workspace) {
        PaymentHistory paymentHistory = new PaymentHistory();
        paymentHistory.bindPaymentResult(resultObject);
        paymentHistory.setPrice(paymentForm.getPaymentAmount());
        paymentHistory.setBuyUser(paymentForm.getBuyUser());
        paymentHistory.setType(paymentForm.getType());
        paymentHistory.setCustomerUid(paymentForm.getCustomerUid());
        paymentHistory.setMerchantUid(paymentForm.getMerchantUid());
        paymentHistory.setWorkspace(workspace);
        return this.paymentHistoryRepository.saveAndFlush(paymentHistory);
    }
    //  결제 시 오류가 발생했을 때 취소 정보를 저장한다.
    @Override
    @Transactional
    public PaymentHistory cancelPaymentHistory(PaymentForm paymentForm, Workspace workspace, String reason) {
        PaymentHistory paymentHistory = new PaymentHistory();
        paymentHistory.setPrice(paymentForm.getPaymentAmount());
        paymentHistory.setBuyUser(paymentForm.getBuyUser());
        paymentHistory.setType(paymentForm.getType());
        paymentHistory.setCustomerUid(paymentForm.getCustomerUid());
        paymentHistory.setMerchantUid(paymentForm.getMerchantUid());
        paymentHistory.setWorkspace(workspace);
        paymentHistory.setPaymentResult(PaymentHistory.PAYMENT_RESULT_FAILED);
        paymentHistory.setPaymentResponse(reason);
        return this.paymentHistoryRepository.saveAndFlush(paymentHistory);
    }
    //  해당 업무 공간에서 마지막으로 결제한 정보를 가져온다.
    @Override
    @Transactional(readOnly = true)
    public PaymentHistory findByWorkspaceLastPaymentHistory(Workspace workspace) {
        List<PaymentHistory> paymentHistoryList = this.paymentHistoryRepository.findByWorkspaceId(workspace.getId());
        if (paymentHistoryList.size() < 1) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PAYMENT_NOT_EXIST));
        }
        return paymentHistoryList.get(paymentHistoryList.size() - 1);
    }
}
src/main/java/kr/wisestone/owl/service/impl/PaymentServiceImpl.java
New file
@@ -0,0 +1,789 @@
package kr.wisestone.owl.service.impl;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.*;
import kr.wisestone.owl.domain.enumType.EmailType;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.repository.PaymentRepository;
import kr.wisestone.owl.service.*;
import kr.wisestone.owl.util.CommonUtil;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.util.DateUtil;
import kr.wisestone.owl.util.MapUtil;
import kr.wisestone.owl.vo.PaymentHistoryVo;
import kr.wisestone.owl.vo.PaymentVo;
import kr.wisestone.owl.vo.UserVo;
import kr.wisestone.owl.web.form.PaymentForm;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class PaymentServiceImpl extends AbstractServiceImpl<Payment, Long, JpaRepository<Payment, Long>> implements PaymentService {
    private static final Logger log = LoggerFactory.getLogger(PaymentServiceImpl.class);
    @Autowired
    private PaymentRepository paymentRepository;
    @Autowired
    private WorkspaceService workspaceService;
    @Autowired
    private UserWorkspaceService userWorkspaceService;
    @Autowired
    private ReservationDisableUserService reservationDisableUserService;
    @Autowired
    private UserService userService;
    @Autowired
    private SystemEmailService systemEmailService;
    @Autowired
    private PaymentHistoryService paymentHistoryService;
    @Value("${saas.usdkrw}")
    private Integer usdKrw;
    @Value("${payment.cancel.manager.email}")
    private String paymentCancelManagerEmail;
    private static final String IAMPORT_SERVER_URL = "https://api.iamport.kr";
    private static final String ACCESS_TOKEN_REQUEST_URL = "/users/getToken";
    private static final String IMP_KEY = "4101736461182334";
    private static final String IMP_SECRET = "HjTiwAyYJD8r0NRDY0wcmQG989bsOTGvczOJamIT0Rl0FdEDdaQz1wJ9GUCuNj00TOK3btWclic0vREI";
    private static final String SUBSCRIBE_PAYMENTS_ONETIME = "/subscribe/payments/onetime";  //  최초 결제할때
    private static final String SUBSCRIBE_PAYMENTS_AGAIN = "/subscribe/payments/again";  //  정기 결제할때
    private static final String PAYMENTS_CANCEL_URL = "/payments/cancel";    //  취소할때
    private static final int DEFAULT_BILLING_AMOUNT = 9;
    private static final int BILLING_AMOUNT = 6;
    @Override
    protected JpaRepository<Payment, Long> getRepository() {
        return this.paymentRepository;
    }
    //  즉시 결제
    @Override
    @Transactional
    public void immediateAddUser(PaymentForm paymentForm) {
        Workspace workspace = this.workspaceService.findOne(paymentForm.getWorkspaceId());
        //  서버에서 결재 금액을 다시 계산한다.
        int definiteAmount = this.calculateDailyAmount(paymentForm.getBuyUser(), workspace.getExpireDate());
        paymentForm.setPaymentAmount(definiteAmount);
        //  결재 실행
        RestClientResultObject resultObject = this.executePayment(paymentForm, workspace);
        //  히스토리 생성
        PaymentHistory paymentHistory = this.paymentHistoryService.addPaymentHistory(resultObject, paymentForm, workspace);
        //  결재 결과 처리
        this.paymentResultProcess(paymentHistory, workspace, paymentForm, true);
    }
    private int calculateDailyAmount(int buyUser, Date expireDate) {
        int expireDateTerm = DateUtil.getDateDiff(new Date(), expireDate) + 1;
        //  사용자가 1명이 안될 때는 오류 발생
        if (buyUser < 1) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PAYMENT_BUY_USER_MUST_BE_GREATER_THAN_ZERO));
        }
        if (expireDateTerm > 29) {
            //  기본 사용자(10명)을 초과한 결제를 진행했을 때 결제 금액을 계산한다.
            return this.calculateAmountDefaultUserOver(buyUser);
        }
        else {
            int totalAmount = (BILLING_AMOUNT * this.usdKrw * buyUser);
            int dayAmount = (totalAmount / 30); //  30일 기준으로 가격을 나눈다.
            double sale = buyUser * 0.01;
            double discount = 0;
            //  100명 이상 결제시 할인 적용
            if (buyUser > 99) {
                discount = (totalAmount * (sale / 100));
            }
            return (int) Math.round((dayAmount * expireDateTerm) - discount);
        }
    }
    //  기본 사용자(10명)을 초과한 결제를 진행했을 때 결제 금액을 계산한다.
    private int calculateAmountDefaultUserOver(int buyUser) {
        //   100프로
        int totalAmount = (BILLING_AMOUNT * this.usdKrw * buyUser);
        double sale = buyUser * 0.01;
        double discount = 0;
        //  100명 이상 결제시 할인 적용
        if (buyUser > 99) {
            discount = (totalAmount * (sale / 100));
        }
        return (int) Math.floor((totalAmount - discount));
    }
    //  정기 결제
    @Override
    @Transactional
    public void paymentOneTime(PaymentForm paymentForm) {
        Workspace workspace = this.workspaceService.getWorkspace(paymentForm.getWorkspaceId());
        //  결제하려는 사용자가 업무 공간의 관리자인지 확인한다.
        this.checkWorkspaceManager(workspace);
        //  구매 사용자 수 체크
        this.verifyBuyUser(paymentForm);
        //  결제 유형 체크
        this.verifyPaymentType(paymentForm.getType());
        //  서버에서 결제 금액을 다시 계산한다.
        int definiteAmount = this.calculateAmount(paymentForm.getBuyUser());
        paymentForm.setPaymentAmount(definiteAmount);
        //  결제 실행
        RestClientResultObject resultObject = this.executePayment(paymentForm, workspace);
        //  히스토리 생성
        PaymentHistory paymentHistory = this.paymentHistoryService.addPaymentHistory(resultObject, paymentForm, workspace);
        //  결제 결과 처리
        this.paymentResultProcess(paymentHistory, workspace, paymentForm, false);
        //  결제 정보를 map 으로 만들어준다. - 이메일 발송에 사용
        Map<String, Object> paymentMap = this.makePaymentMap(workspace, this.webAppUtil.getLoginUser().getName(), paymentForm.getBuyUser(), definiteAmount, null);
        //  결제 성공 메일 발송
        this.systemEmailService.directEmail(new String[]{this.webAppUtil.getLoginUser().getAccount()}, EmailType.REGULAR_PAYMENT, paymentMap, this.webAppUtil.getLoginUser().getAccount());
    }
    //  결제 정보를 map 으로 만들어준다. - 이메일 발송에 사용
    private Map<String, Object> makePaymentMap(Workspace workspace, String userName, Integer buyUser, Integer price, String email) {
        Map<String, Object> paymentMap = new HashMap<>();
        paymentMap.put("userName", userName);
        paymentMap.put("workspaceName", workspace.getName());
        paymentMap.put("storageSize", workspace.getStorageSize() / 1024 / 1024 / 1024);
        paymentMap.put("buyUser", buyUser);
        paymentMap.put("price", CommonUtil.getDecimalFormat(price));
        paymentMap.put("registerDate", DateUtil.convertDateToStr(new Date()));
        paymentMap.put("nextPaymentDay", DateUtil.convertDateToYYYYMMDD(DateUtil.addDays(workspace.getExpireDate(), 1)));
        paymentMap.put("startDate", DateUtil.convertDateToYYYYMMDD(workspace.getStartDate()));
        paymentMap.put("expireDate", DateUtil.convertDateToYYYYMMDD(workspace.getExpireDate()));
        paymentMap.put("email", email);
        return paymentMap;
    }
    //  결제하려는 사용자가 업무 공간의 관리자인지 확인한다.
    private void checkWorkspaceManager(Workspace workspace) {
        UserWorkspace userWorkspace = this.userWorkspaceService.findByUserIdAndWorkspaceId(this.webAppUtil.getLoginId(), workspace.getId());
        if (!userWorkspace.getManagerYn()) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PAYMENT_EXECUTE_ONLY_WORKSPACE_MANAGER));
        }
    }
    //  스케쥴러로 한번 결제한 정보를 토대로 자동 결제를 실행한다.
    @Override
    @Transactional
    public void subscribeImmediate(Workspace workspace) {
        Payment payment = workspace.getPayment();
        if (payment == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PAYMENT_NOT_EXIST));
        }
        User workspaceManager = this.userService.findByWorkspaceIdAndManagerYn(workspace);
        if (workspaceManager == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.USER_WORKSPACE_MANAGER_NOT_EXIST));
        }
        PaymentForm paymentForm = ConvertUtil.copyProperties(payment, PaymentForm.class);
        paymentForm.setCustomerUid(workspace.makeCustomerUid(workspaceManager.getAccount()));
        paymentForm.setMerchantUid(workspaceManager.getAccount() + new Date().getTime());
        paymentForm.setPaymentAmount(payment.getPrice());   //  히스토리 생성할 때 사용
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", this.getAccessToken());
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
        MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
        parameters.add("customer_uid", paymentForm.getCustomerUid());
        parameters.add("merchant_uid", paymentForm.getMerchantUid());
        parameters.add("amount", paymentForm.getPaymentAmount().toString());
        RestClientResultObject resultObject = this.postRequest(this.makeIamportRequestUrl(SUBSCRIBE_PAYMENTS_AGAIN), new HttpEntity<>(parameters, headers));
        //  히스토리 생성
        PaymentHistory paymentHistory = this.paymentHistoryService.addPaymentHistory(resultObject, paymentForm, workspace);
        //  결제 결과 처리
        this.paymentResultProcess(paymentHistory, workspace, paymentForm, false);
        //  비활성화 사용자 처리
        this.checkPaymentByUserCount(paymentHistory, payment, paymentForm, workspace);
        List<UserWorkspace> userWorkspaces = this.userWorkspaceService.findByWorkspaceIdAndManagerYn(workspace.getId(), true);
        //  업무 공간 담당자 이름
        String workspaceManagerName = "";
        if (userWorkspaces.size() > 0) {
            workspaceManagerName = userWorkspaces.get(0).getUser().getName();
        }
        //  결제 정보를 map 으로 만들어준다. - 이메일 발송에 사용
        Map<String, Object> paymentMap = this.makePaymentMap(workspace, workspaceManagerName, payment.getBuyUser(), payment.getPrice(), null);
        //  결제 성공 메일 발송
        this.systemEmailService.directEmail(new String[]{workspaceManager.getAccount()}, EmailType.REGULAR_PAYMENT, paymentMap, null);
    }
    //  결제된 사용자 수를 체크한다.
    private void checkPaymentByUserCount(PaymentHistory paymentHistory, Payment payment, PaymentForm paymentForm, Workspace workspace) {
        try {
            //  결제가 성공했을 때 참여 사용자와 결제된 사용자 수를 비교한다.
            if (paymentHistory.getPaymentResult().equals(PaymentHistory.PAYMENT_RESULT_SUCCESS)) {
                Integer activeUserCount = this.userWorkspaceService.countByWorkspaceIdAndUseYn(workspace.getId(), true);
                //  결제한 사용자보다 참여중인 사용자가 많을 경우
                if (workspace.getMaxUser() < activeUserCount) {
                    this.executeWorkspaceStandbyUser(workspace, payment, activeUserCount);
                }
            }
        } catch (Exception e) {
            this.cancelPayment(paymentForm.getMerchantUid(), "");
            this.paymentHistoryService.cancelPaymentHistory(paymentForm, workspace, "결제 완료후 비활성화로 예정된 사용자 처리 과정에서 오류가 발생하였습니다.");
        }
    }
    //  업무 공간에 참여하는 사용자 참여 대기 상태로 변경하기 - 결제 인원 수를 제외한 인원들
    private void executeWorkspaceStandbyUser(Workspace workspace, Payment payment, Integer activeUserCount) {
        int disableUserCount = activeUserCount - workspace.getMaxUser();
        //  참여 대기로 예약된 사용자부터 참여 대기로 상태를 변경
        ReservationDisableUser reservationDisableUser = payment.getReservationDisableUser();
        if (reservationDisableUser != null) {
            String[] userIds = reservationDisableUser.getUserIds().split(",");
            for (String userId : userIds) {
                if (!StringUtils.isEmpty(userId)) {
                    if (disableUserCount < 1) {
                        break;
                    }
                    User user = this.userService.getUser(Long.valueOf(userId));
                    //  해당 업무 공간에서 사용자를 비활성화한다.
                    this.userWorkspaceService.disabledUserWorkspace(user, workspace);
                    //  사용자를 해당 업무 공간에서 참여 대기 상태로 변경했으면 카운터를 내린다.
                    disableUserCount--;
                }
            }
            payment.setReservationDisableUser(null);
            this.paymentRepository.saveAndFlush(payment);
        }
        //  해당 업무 공간에 참여하는 사용자 중 번호가 높은 순서대로 참여 대기 상태로 변경한다.
        List<UserWorkspace> userWorkspaces = this.userWorkspaceService.findByWorkspaceIdAndUseYn(workspace.getId(), true);
        for (UserWorkspace userWorkspace : userWorkspaces) {
            //  관리자는 항상 참여 상태여야 한다.
            if (userWorkspace.getManagerYn()) {
                continue;
            }
            if (disableUserCount < 1) {
                break;
            }
            //  해당 업무 공간에서 사용자를 비활성화한다.
            this.userWorkspaceService.disabledUserWorkspace(userWorkspace.getUser(), workspace);
            //  사용자를 해당 업무 공간에서 참여 대기 상태로 변경했으면 카운터를 내린다.
            disableUserCount--;
        }
    }
    //  서버에서 결제 금액을 다시 계산한다.
    private int calculateAmount(int buyUser) {
        //  사용자가 1명이 안될 때는 오류 발생
        if (buyUser < 1) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PAYMENT_BUY_USER_MUST_BE_GREATER_THAN_ZERO));
        }
        if (buyUser < 11) {
            return (DEFAULT_BILLING_AMOUNT * this.usdKrw);
        }
        else {
            //  기본 사용자(10명)을 초과한 결제를 진행했을 때 결제 금액을 계산한다.
            return this.calculateAmountDefaultUserOver(buyUser);
        }
    }
    //  결제를 실행한다.
    private RestClientResultObject executePayment(PaymentForm paymentForm, Workspace workspace) {
        UserVo workspaceManager = this.webAppUtil.getLoginUser();
        paymentForm.setCustomerUid(workspace.makeCustomerUid(workspaceManager.getAccount()));
        paymentForm.setMerchantUid(workspaceManager.getAccount() + new Date().getTime());
        //  HttpHeader 를 생성한다.
        HttpHeaders headers = this.makeHttpHeader();
        MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
        parameters.add("merchant_uid", paymentForm.getMerchantUid());
        parameters.add("amount", paymentForm.getPaymentAmount().toString());
        parameters.add("card_number", CommonUtil.decryptAES128(paymentForm.getCardNumber1()) + "-" + CommonUtil.decryptAES128(paymentForm.getCardNumber2())
                + "-" + CommonUtil.decryptAES128(paymentForm.getCardNumber3()) + "-" + CommonUtil.decryptAES128(paymentForm.getCardNumber4()));
        parameters.add("expiry", CommonUtil.decryptAES128(paymentForm.getExpireYear()) + "-" + CommonUtil.decryptAES128(paymentForm.getExpireMonth()));
        parameters.add("birth", CommonUtil.decryptAES128(paymentForm.getBirth()));
        parameters.add("pwd_2digit", CommonUtil.decryptAES128(paymentForm.getCardPwd()));
        parameters.add("customer_uid", paymentForm.getCustomerUid());
        parameters.add("buyer_name", workspaceManager.getName());
        parameters.add("buyer_email", workspaceManager.getAccount());
        return this.postRequest(this.makeIamportRequestUrl(SUBSCRIBE_PAYMENTS_ONETIME), new HttpEntity<>(parameters, headers));
    }
    //  결제 결과를 처리한다.
    private PaymentHistoryVo paymentResultProcess(PaymentHistory paymentHistory, Workspace workspace, PaymentForm paymentForm, Boolean immediate) {
        if (paymentHistory.getPaymentResult().equals(PaymentHistory.PAYMENT_RESULT_SUCCESS)) {
            try {
                //  추가 결제
                if (immediate) {
                    Payment payment = workspace.getPayment();
                    //  인원 추가
                    payment.setBuyUser(payment.getBuyUser() + paymentForm.getBuyUser());
                    //  추가 결제로 인한 사용금액 변경
                    payment.setPrice(this.calculateAmountDefaultUserOver(payment.getBuyUser()));
                    this.paymentRepository.saveAndFlush(payment);
                    //  추가 결제 완료 후 최대 사용자 수, 저장 공간을 업데이트한다.
                    this.workspaceService.updateWorkspaceByImmediatePayment(workspace, payment.getBuyUser());
                }
                else {
                    //  정기 결제로 결제를 진행한 경우
                    Payment payment = this.executePaymentOneTime(workspace, paymentForm);
                    if (payment != null) {
                        //  비활성화 사용자 처리
                        this.checkPaymentByUserCount(paymentHistory, payment, paymentForm, workspace);
                    }
                }
            } catch (Exception e) {
                this.cancelPayment(paymentForm.getMerchantUid(), "");
                PaymentHistory cancelPaymentHistory = this.paymentHistoryService.cancelPaymentHistory(paymentForm, workspace, "결제 과정에서 오류가 발생하였습니다. 다시 시도해 주세요.");
                return ConvertUtil.copyProperties(cancelPaymentHistory, PaymentHistoryVo.class);
            }
        }
        else {
            throw new OwlRuntimeException(this.messageAccessor.getMessage(paymentHistory.getPaymentResponse()));
        }
        return ConvertUtil.copyProperties(paymentHistory, PaymentHistoryVo.class);
    }
    //  정기 결제로 결제를 진행한 경우
    private Payment executePaymentOneTime(Workspace workspace, PaymentForm paymentForm) {
        //  다음 결제일 지정 및 업무 공간에 결제한 신규 데이터 적용
        this.workspaceService.updateWorkspace(workspace, paymentForm);
        if (workspace.getPayment() == null) {
            //  결제가 완료되고 결제 정보가 업무 공간에 업데이트가 되면 정기 결제를 위해 결제 정보를 생성한다.
            Payment payment = ConvertUtil.copyProperties(paymentForm, Payment.class);
            payment.setPrice(paymentForm.getPaymentAmount());
            payment.setWorkspace(workspace);
            return this.paymentRepository.saveAndFlush(payment);
        }
        return null;
    }
    //  결제 진행중 발생한 오류로 인한 결제 취소 요청
    private String cancelPayment(String merchantUid, String reason) {
        //  HttpHeader 를 생성한다.
        HttpHeaders headers = this.makeHttpHeader();
        //  RestTemplate 를 생성한다.
        RestTemplate restTemplate = this.makeRestTemplate();
        MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
        parameters.add("merchant_uid", merchantUid);
        parameters.add("reason", reason);
        URI targetUrl = UriComponentsBuilder.fromUriString(IAMPORT_SERVER_URL)
                .path(PAYMENTS_CANCEL_URL)
                .build()
                .toUri();
        String url = null;
        try {
            url = URLDecoder.decode(targetUrl.toString(), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        ResponseEntity<String> result = restTemplate.postForEntity(url, new HttpEntity<>(parameters, headers), String.class);
        Map<String, Object> resultMap = ConvertUtil.convertJsonToMap(result.getBody());
        return StringEscapeUtils.unescapeJava(MapUtil.getString(resultMap, "response"));
    }
    //  HttpHeader 를 생성한다.
    private HttpHeaders makeHttpHeader() {
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", this.getAccessToken());
        return headers;
    }
    //  RestTemplate 를 생성한다.
    private RestTemplate makeRestTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getMessageConverters()
                .add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
        return restTemplate;
    }
    //  정기 결제 취소하기
    @Override
    @Transactional
    public void cancelNextPayment(PaymentForm paymentForm) {
        Workspace workspace = this.workspaceService.getWorkspace(paymentForm.getWorkspaceId());
        //  결제 취소하려는 사용자가 업무 공간의 관리자인지 확인한다.
        this.checkWorkspaceManager(workspace);
        Payment payment = workspace.getPayment();
        if (payment == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PAYMENT_NOT_EXIST));
        }
        //  업무 공간 정기 결제 취소 - 정기 결제 정보 삭제
        this.workspaceService.cancelWorkspacePayment(workspace);
        //  결제 정보를 map 으로 만들어준다. - 이메일 발송에 사용
        Map<String, Object> paymentMap = this.makePaymentMap(workspace, this.webAppUtil.getLoginUser().getName(), 0, 0, CommonUtil.decryptAES128(this.webAppUtil.getLoginUser().getAccount()));
        paymentMap.put("maxUser", workspace.getMaxUser());
        try {
            //  오늘 결제한 업무 공간을 사용자가 취소했는지 확인한다.
            PaymentHistory paymentHistory = this.paymentHistoryService.findByWorkspaceLastPaymentHistory(workspace);
            //  당일 결제 취소일 경우
            if (paymentHistory.getRegisterDate().compareTo(new Date()) == 0) {
                paymentMap.put("refundPrice", CommonUtil.getDecimalFormat(paymentHistory.getPrice())); //  환불 금액
                //  회계 담당자 이메일 주소 암호화
                String encryptAccountingManager = CommonUtil.encryptAES128(this.paymentCancelManagerEmail);
                //  회계 담당자에게 결제 취소 알림 메일
                this.systemEmailService.directEmail(new String[]{encryptAccountingManager}, EmailType.REGULAR_PAYMENT_CANCEL_BY_ACCOUNTING_MANAGER, paymentMap, encryptAccountingManager);
            }
        } catch (Exception e) {
            log.debug("환불 알림 메일 오류 발생" + e.getMessage());
        }
        //  정기 결제 취소 알림 메일
        this.systemEmailService.directEmail(new String[]{this.webAppUtil.getLoginUser().getAccount()}, EmailType.REGULAR_PAYMENT_CANCEL, paymentMap, this.webAppUtil.getLoginUser().getAccount());
    }
    @Override
    public String getAccessToken() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getMessageConverters()
                .add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
        MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
        parameters.add("imp_key", IMP_KEY);
        parameters.add("imp_secret", IMP_SECRET);
        URI targetUrl = UriComponentsBuilder.fromUriString(IAMPORT_SERVER_URL)
                .path(ACCESS_TOKEN_REQUEST_URL)
                .build()
                .toUri();
        String url = "";
        try {
            url = URLDecoder.decode(targetUrl.toString(), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        String result = restTemplate.postForObject(url, parameters, String.class);
        Map<String, Object> resultMap = ConvertUtil.convertJsonToMap(result);
        Map<String, Object> tokenMap = (Map<String, Object>) resultMap.get("response");
        return MapUtil.getString(tokenMap, "access_token");
    }
    //  결제 상세 정보를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public void detailPayment(Map<String, Object> resJsonData, PaymentForm paymentForm) {
        Workspace workspace = this.workspaceService.findOne(paymentForm.getWorkspaceId());
        Payment payment = workspace.getPayment();
        if (payment != null) {
            resJsonData.put(Constants.RES_KEY_CONTENTS, ConvertUtil.copyProperties(payment, PaymentVo.class));
        }
    }
    //  결제 정보를 수정한다.
    @Override
    @Transactional
    public PaymentVo modifyPayment(PaymentForm paymentForm) {
        Workspace workspace = this.workspaceService.findOne(paymentForm.getWorkspaceId());
        //  결제하려는 사용자가 업무 공간의 관리자인지 확인한다.
        this.checkWorkspaceManager(workspace);
        Payment payment = workspace.getPayment();
        Map<String, Object> paymentMap = new HashMap<>();
        paymentMap.put("beforeMaxUser", payment.getBuyUser());    //  이전 최대 사용자 수
        paymentMap.put("beforePrice", CommonUtil.getDecimalFormat(payment.getPrice()));  //  이전 가격
        //  결제 사용자 수 체크
        this.verifyBuyUser(paymentForm);
        //  결제 유형 체크
        this.verifyPaymentType(paymentForm.getType());
        payment.setBuyUser(paymentForm.getBuyUser());
        payment.setType(paymentForm.getType());
        //  서버에서 결제 금액을 다시 계산한다.
        int definiteAmount = this.calculateAmount(paymentForm.getBuyUser());
        payment.setPrice(definiteAmount);
        if (payment.getReservationDisableUser() != null) {
            payment.setReservationDisableUser(null);
        }
        this.paymentRepository.saveAndFlush(payment);
        this.reservationDisableUserService.add(paymentForm, payment);
        paymentMap.put("userName", this.webAppUtil.getLoginUser().getName());
        paymentMap.put("workspaceName", workspace.getName());
        paymentMap.put("maxUser", paymentForm.getBuyUser());
        paymentMap.put("price", CommonUtil.getDecimalFormat(definiteAmount));
        paymentMap.put("nextPaymentDay", DateUtil.convertDateToYYYYMMDD(DateUtil.addDays(workspace.getExpireDate(), 1)));
        //  정기 결제 변경 알림 메일
        this.systemEmailService.directEmail(new String[]{this.webAppUtil.getLoginUser().getAccount()}, EmailType.REGULAR_PAYMENT_MODIFY, paymentMap, this.webAppUtil.getLoginUser().getAccount());
        return ConvertUtil.copyProperties(payment, PaymentVo.class);
    }
    //  결제 사용자 수 체크
    private void verifyBuyUser(PaymentForm paymentForm) {
        if (paymentForm.getBuyUser() == null || paymentForm.getBuyUser() < 1) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PAYMENT_BUY_USER_MUST_BE_GREATER_THAN_ZERO));
        }
        //  1명으로 구입해도 10인까지 사용 가능하도록 변경
        if (paymentForm.getBuyUser() < 10) {
            paymentForm.setBuyUser(10);
        }
    }
    //  결제 유형 체크
    private void verifyPaymentType(String type) {
        if (StringUtils.isEmpty(type)) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PAYMENT_NO_TYPE));
        }
    }
    //  결제 아이디로 결제를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public Payment getPayment(Long id) {
        if (id == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PAYMENT_NOT_EXIST));
        }
        Payment payment = this.findOne(id);
        if (payment == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PAYMENT_NOT_EXIST));
        }
        return payment;
    }
    //  결제 금액에 환율 정보를 업데이트한다.
    @Override
    @Transactional
    public void updateExchangeRatePayment() {
        List<Payment> payments = this.paymentRepository.findAll();
        for (Payment payment : payments) {
            //  서버에서 결제 금액을 다시 계산한다.
            int definiteAmount = this.calculateAmount(payment.getBuyUser());
            payment.setPrice(definiteAmount);
        }
        this.paymentRepository.saveAll(payments);
    }
    private URI makeIamportRequestUrl(String requestPath) {
        return UriComponentsBuilder.fromUriString(IAMPORT_SERVER_URL)
                .path(requestPath)
                .build()
                .toUri();
    }
    private RestClientResultObject postRequest(URI targetUrl, HttpEntity<MultiValueMap<String, String>> request) {
        RestClientResultObject restResult = new RestClientResultObject();
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
        try {
            String url = URLDecoder.decode(targetUrl.toString(), "UTF-8");
            ResponseEntity<String> result = restTemplate.postForEntity(url, request, String.class);
            Map<String, Object> resultMap = ConvertUtil.convertJsonToMap(result.getBody());
            restResult.setHttpStatus(result.getStatusCode());
            restResult.setCode(MapUtil.getString(resultMap, "code"));
            if (resultMap.containsKey("message")) {
                restResult.setMessage(StringEscapeUtils.unescapeJava(MapUtil.getString(resultMap, "message")));
            }
            if (resultMap.containsKey("response")) {
                Map<String, Object> response = (Map<String, Object>) resultMap.get("response");
                restResult.setResponse(response);
                String responseStatus = MapUtil.getString(response, "status");
                String failReason = MapUtil.getString(response, "fail_reason");
                restResult.setIamportStatus(responseStatus);
                Boolean isNotPaidResult = !"paid".equals(responseStatus);
                if (isNotPaidResult) {
                    restResult.setIamportFailReason(failReason);
                }
            }
        } catch (HttpClientErrorException e) {
            restResult.setMessage(e.getMessage());
            restResult.setCode(e.getStatusCode().toString());
            restResult.setHttpStatus(e.getStatusCode());
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return restResult;
    }
    public static class RestClientResultObject {
        private static final String CODE_RESULT_SUCCESS = "0";
        private static final String CODE_RESULT_FAILED = "-1";
        private static final String IAMPORT_STATUS_PAID = "paid";
        private HttpStatus httpStatus;
        private String code;
        private String message;
        private Map<String, Object> response;
        private String iamportStatus;
        private String iamportFailReason;
        public Boolean isValidResult() {
            if (isHttpRequestFailed() || isIamportResultFailed() || isIamportPaymentFailed()) {
                return Boolean.FALSE;
            }
            return Boolean.TRUE;
        }
        public Boolean isHttpRequestFailed() {
            return !HttpStatus.OK.equals(this.getHttpStatus());
        }
        public Boolean isIamportResultFailed() {
            return CODE_RESULT_FAILED.equals(this.getCode());
        }
        public Boolean isIamportPaymentFailed() {
            return !IAMPORT_STATUS_PAID.equals(this.getIamportStatus());
        }
        public HttpStatus getHttpStatus() {
            return httpStatus;
        }
        public void setHttpStatus(HttpStatus httpStatus) {
            this.httpStatus = httpStatus;
        }
        public String getCode() {
            return code;
        }
        public void setCode(String code) {
            this.code = code;
        }
        public String getMessage() {
            return message;
        }
        public void setMessage(String message) {
            this.message = message;
        }
        public Map<String, Object> getResponse() {
            return response;
        }
        public void setResponse(Map<String, Object> response) {
            this.response = response;
        }
        public String getIamportStatus() {
            return iamportStatus;
        }
        public void setIamportStatus(String iamportStatus) {
            this.iamportStatus = iamportStatus;
        }
        public String getIamportFailReason() {
            return iamportFailReason;
        }
        public void setIamportFailReason(String iamportFailReason) {
            this.iamportFailReason = iamportFailReason;
        }
    }
}
src/main/java/kr/wisestone/owl/service/impl/PermissionServiceImpl.java
New file
@@ -0,0 +1,75 @@
package kr.wisestone.owl.service.impl;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import kr.wisestone.owl.domain.Permission;
import kr.wisestone.owl.domain.User;
import kr.wisestone.owl.repository.PermissionRepository;
import kr.wisestone.owl.service.PermissionService;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.vo.PermissionVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class PermissionServiceImpl extends AbstractServiceImpl<Permission, Long, JpaRepository<Permission, Long>> implements PermissionService {
    private static final Logger log = LoggerFactory.getLogger(PermissionServiceImpl.class);
    @Autowired
    private PermissionRepository permissionRepository;
    @Override
    protected JpaRepository<Permission, Long> getRepository() {
        return this.permissionRepository;
    }
    @Override
    @Transactional(readOnly = true)
    public List<PermissionVo> findByUserId() {
        User user = this.webAppUtil.getLoginUserObject();
        List<PermissionVo> allPermission = ConvertUtil.convertObjectsToClasses(this.permissionRepository.findAll(), PermissionVo.class);
        List<PermissionVo> userPermissions = Lists.newArrayList();
        if (user != null) {
            userPermissions.addAll(ConvertUtil.convertObjectsToClasses(this.permissionRepository.findByUserId(user.getId()), PermissionVo.class));
        }
        this.setPermissionActiveYn(allPermission, userPermissions);
        return allPermission;
    }
    @Override
    @Transactional(readOnly = true)
    public List<Permission> findByRoleType(String roleType) {
        return this.permissionRepository.findByRoleType(roleType);
    }
    private void setPermissionActiveYn(List<PermissionVo> allPermissionVos, List<PermissionVo> userPermissionVos) {
        for (PermissionVo permissionVo : allPermissionVos) {
            final Long permissionId = permissionVo.getId();
            PermissionVo userPermission = Iterables.find(userPermissionVos, new Predicate<PermissionVo>() {
                @Override
                public boolean apply(PermissionVo input) {
                    return input.getId().equals(permissionId);
                }
            }, null);
            if (userPermission != null) {
                permissionVo.setActiveYn(Boolean.TRUE);
            }
        }
    }
}
src/main/java/kr/wisestone/owl/service/impl/PriorityServiceImpl.java
New file
@@ -0,0 +1,99 @@
package kr.wisestone.owl.service.impl;
import com.google.common.collect.Lists;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.Priority;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.repository.PriorityRepository;
import kr.wisestone.owl.service.PriorityService;
import kr.wisestone.owl.service.UserService;
import kr.wisestone.owl.service.WorkspaceService;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.vo.PriorityVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
@Service
public class PriorityServiceImpl extends AbstractServiceImpl<Priority, Long, JpaRepository<Priority, Long>> implements PriorityService {
    private static final Logger log = LoggerFactory.getLogger(PriorityServiceImpl.class);
    @Autowired
    private PriorityRepository priorityRepository;
    @Autowired
    private WorkspaceService workspaceService;
    @Autowired
    private UserService userService;
    @Override
    protected JpaRepository<Priority, Long> getRepository() {
        return this.priorityRepository;
    }
    @Override
    @Transactional(readOnly = true)
    public List<Priority> findByWorkspaceIdOrderByPosition() {
        Workspace workspace = this.workspaceService.getWorkspace(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        return this.priorityRepository.findByWorkspaceIdOrderByPosition(workspace.getId());
    }
    //  우선순위 아이디로 우선순위를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public Priority getPriority(Long id) {
        if (id == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PRIORITY_NOT_EXIST));
        }
        Priority priority = this.findOne(id);
        if (priority == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PRIORITY_NOT_EXIST));
        }
        return priority;
    }
    //  우선순위 목록을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public List<PriorityVo> findPriority(Map<String, Object> resJsonData) {
        List<PriorityVo> priorityVos = ConvertUtil.convertObjectsToClasses(this.findByWorkspaceIdOrderByPosition(), PriorityVo.class);
        resJsonData.put(Constants.RES_KEY_CONTENTS, priorityVos);
        return priorityVos;
    }
    //  기본적으로 제공되는 우선순위 목록
    @Override
    @Transactional
    public void addDefaultPriority(Workspace workspace) {
        List<Priority> priorities = Lists.newArrayList();
        priorities.add(new Priority(this.messageAccessor.message("common.urgent"), 1, "#ed5565", workspace));
        priorities.add(new Priority(this.messageAccessor.message("common.high"), 2, "#f8ac59", workspace));
        priorities.add(new Priority(this.messageAccessor.message("common.medium"), 3, "#1c84c6", workspace));
        priorities.add(new Priority(this.messageAccessor.message("common.low"), 4, "#23c6c8", workspace));
        this.priorityRepository.saveAll(priorities);
    }
    //  업무공간에 있는 우선 순위 목록을 가져온다.
    @Override
    @Transactional(readOnly = true)
    public List<Priority> findByWorkspaceId() {
        return this.priorityRepository.findByWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
    }
}
src/main/java/kr/wisestone/owl/service/impl/ProjectClosureServiceImpl.java
New file
@@ -0,0 +1,30 @@
package kr.wisestone.owl.service.impl;
import kr.wisestone.owl.domain.Priority;
import kr.wisestone.owl.domain.Project;
import kr.wisestone.owl.domain.ProjectClosure;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.repository.ProjectClosureRepository;
import kr.wisestone.owl.service.ProjectClosureService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Set;
@Service
public class ProjectClosureServiceImpl extends AbstractServiceImpl<ProjectClosure, Long, JpaRepository<ProjectClosure, Long>> implements ProjectClosureService {
    private static final Logger log = LoggerFactory.getLogger(ProjectClosureServiceImpl.class);
    @Autowired
    private ProjectClosureRepository projectClosureRepository;
    @Override
    protected JpaRepository<ProjectClosure, Long> getRepository() {
        return this.projectClosureRepository;
    }
}
src/main/java/kr/wisestone/owl/service/impl/ProjectRolePermissionServiceImpl.java
New file
@@ -0,0 +1,48 @@
package kr.wisestone.owl.service.impl;
import com.google.common.collect.Lists;
import kr.wisestone.owl.domain.Permission;
import kr.wisestone.owl.domain.ProjectRole;
import kr.wisestone.owl.domain.ProjectRolePermission;
import kr.wisestone.owl.repository.ProjectRolePermissionRepository;
import kr.wisestone.owl.service.PermissionService;
import kr.wisestone.owl.service.ProjectRolePermissionService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class ProjectRolePermissionServiceImpl extends AbstractServiceImpl<ProjectRolePermission, Long, JpaRepository<ProjectRolePermission, Long>> implements ProjectRolePermissionService {
    private static final Logger log = LoggerFactory.getLogger(ProjectRolePermissionServiceImpl.class);
    @Autowired
    private ProjectRolePermissionRepository projectRolePermissionRepository;
    @Autowired
    private PermissionService permissionService;
    @Override
    protected JpaRepository<ProjectRolePermission, Long> getRepository() {
        return this.projectRolePermissionRepository;
    }
    //  해당 프로젝트 역할과 권한을 연결시킨다.
    @Override
    @Transactional
    public void addDefaultProjectRoleAssociatedPermissions(ProjectRole projectRole, String roleType) {
        List<Permission> permissions = this.permissionService.findByRoleType(roleType);
        List<ProjectRolePermission> projectRolePermissions = Lists.newArrayList();
        permissions.parallelStream().forEach(permission -> {
            ProjectRolePermission projectRolePermission = new ProjectRolePermission(projectRole, permission);
            projectRolePermissions.add(projectRolePermission);
        });
        this.projectRolePermissionRepository.saveAll(projectRolePermissions);
    }
}
src/main/java/kr/wisestone/owl/service/impl/ProjectRoleServiceImpl.java
New file
@@ -0,0 +1,87 @@
package kr.wisestone.owl.service.impl;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.*;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.repository.ProjectRoleRepository;
import kr.wisestone.owl.service.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class ProjectRoleServiceImpl extends AbstractServiceImpl<ProjectRole, Long, JpaRepository<ProjectRole, Long>> implements ProjectRoleService {
    private static final Logger log = LoggerFactory.getLogger(ProjectRoleServiceImpl.class);
    @Autowired
    private ProjectRoleRepository projectRoleRepository;
    @Autowired
    private ProjectRoleUserService projectRoleUserService;
    @Autowired
    private ProjectRolePermissionService projectRolePermissionService;
    @Override
    protected JpaRepository<ProjectRole, Long> getRepository() {
        return this.projectRoleRepository;
    }
    //  기본, 관리자 프로젝트 역할을 생성한다.
    @Override
    @Transactional
    public void addDefaultProjectRole(Project project, List<User> managers, List<User> users) {
        ProjectRole projectRole = this.addProjectRole(project, "기본 프로젝트 역할", ProjectRole.TYPE_DEFAULT, Permission.ROLE_TYPE_PROJECT_JOIN);
        ProjectRole managerProjectRole = this.addProjectRole(project, "프로젝트 관리자 역할", ProjectRole.TYPE_MANAGER, Permission.ROLE_TYPE_PROJECT_MANAGER);
        //  프로젝트 관리자 저장
        this.projectRoleAssociatedUser(managers, managerProjectRole);
        //  프로젝트 일반 사용자 저장
        this.projectRoleAssociatedUser(users, projectRole);
        this.projectRoleRepository.flush();
    }
    //  프로젝트 역할가 사용자 연결
    private void projectRoleAssociatedUser(List<User> users, ProjectRole projectRole) {
        for (User user : users) {
            this.addDefaultProjectRoleAssociatedUser(projectRole, user);
        }
    }
    //  해당 역할과 사용자를 연결시킨다.
    private void addDefaultProjectRoleAssociatedUser(ProjectRole projectRole, User user) {
        ProjectRoleUser projectRoleUser = this.projectRoleUserService.findByProjectRoleIdAndUserId(projectRole.getId(), user.getId());
        if (projectRoleUser == null) {
            //  프로젝트 관리자 - 기본 프로젝트 역할에 추가.
            user.addProjectRole(projectRole);
        }
    }
    //  프로젝트 역할을 생성한다.
    private ProjectRole addProjectRole(Project project, String projectRoleName, String projectRoleType, String permissionType) {
        ProjectRole projectRole = new ProjectRole(project, projectRoleName, projectRoleType);
        this.projectRoleRepository.saveAndFlush(projectRole);
        this.projectRolePermissionService.addDefaultProjectRoleAssociatedPermissions(projectRole, permissionType);
        return projectRole;
    }
    //  해당 프로젝트 역할을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public ProjectRole findByProjectIdAndRoleType(Long projectId, String roleType) {
        ProjectRole projectRole = this.projectRoleRepository.findByProjectIdAndRoleType(projectId, roleType);
        if (projectRole == null) {
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.PROJECT_ROLE_NOT_EXIST));
        }
        return projectRole;
    }
}
src/main/java/kr/wisestone/owl/service/impl/ProjectRoleUserServiceImpl.java
New file
@@ -0,0 +1,140 @@
package kr.wisestone.owl.service.impl;
import com.google.common.collect.Lists;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.*;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.mapper.ProjectRoleUserMapper;
import kr.wisestone.owl.repository.ProjectRoleUserRepository;
import kr.wisestone.owl.service.ProjectRoleService;
import kr.wisestone.owl.service.ProjectRoleUserService;
import kr.wisestone.owl.service.UserWorkspaceService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class ProjectRoleUserServiceImpl extends AbstractServiceImpl<ProjectRoleUser, Long, JpaRepository<ProjectRoleUser, Long>> implements ProjectRoleUserService {
    private static final Logger log = LoggerFactory.getLogger(ProjectRoleUserServiceImpl.class);
    @Autowired
    private ProjectRoleUserRepository projectRoleUserRepository;
    @Autowired
    private ProjectRoleService projectRoleService;
    @Autowired
    private UserWorkspaceService userWorkspaceService;
    @Autowired
    private ProjectRoleUserMapper projectRoleUserMapper;
    @Override
    protected JpaRepository<ProjectRoleUser, Long> getRepository() {
        return this.projectRoleUserRepository;
    }
    @Override
    @Transactional(readOnly = true)
    public List<ProjectRoleUser> findByProjectRoleId(Long projectRoleId) {
        return this.projectRoleUserRepository.findByProjectRoleId(projectRoleId);
    }
    //  해당 사용자가 특정 역할에 소속되어 있는지 확인한다.
    @Override
    @Transactional(readOnly = true)
    public ProjectRoleUser findByProjectRoleIdAndUserId(Long projectRoleId, Long userId) {
        return this.projectRoleUserRepository.findByProjectRoleIdAndUserId(projectRoleId, userId);
    }
    //  업무 공간을 탈퇴한 사용자가 다른 업무 공간의 프로젝트 관리자, 일반 사용자로 있을 경우 제거한다.
    @Override
    @Transactional
    public void withDrawWorkspaceManagerModifyProjectRole(Workspace workspace, User user) {
        List<UserWorkspace> userWorkspaces = this.userWorkspaceService.findByWorkspaceIdAndManagerYn(workspace.getId(), true);
        if (userWorkspaces.size() < 1) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.USER_WORKSPACE_MANAGER_NOT_EXIST));
        }
        User workspaceManager = userWorkspaces.get(0).getUser();
        for (Project project : workspace.getProjects()) {
            //  일반 참여 역할 삭제
            this.removeProjectRoleUser(project, ProjectRole.TYPE_DEFAULT, user, workspaceManager);
            //  프로젝트 관리자 역할 삭제
            this.removeProjectRoleUser(project, ProjectRole.TYPE_MANAGER, user, workspaceManager);
        }
    }
    //  프로젝트 역할 연결 정보에서 탈퇴하는 사용자를 제거한다.
    private void removeProjectRoleUser(Project project, String roleType, User user, User workspaceManager) {
        ProjectRole projectRole = this.projectRoleService.findByProjectIdAndRoleType(project.getId(), roleType);
        //  프로젝트 관리자 역할일 때
        if (roleType.equals(ProjectRole.TYPE_MANAGER)) {
            //  프로젝트 관리자 연결 정보
            List<ProjectRoleUser> projectRoleUsers = this.findByProjectRoleId(projectRole.getId());
            boolean existManager = false;
            //  탈퇴한 사용자가 프로젝트 관리자 여부 확인
            for (ProjectRoleUser projectRoleUser : projectRoleUsers) {
                if (projectRoleUser.getUser().getId().equals(user.getId())) {
                    existManager = true;
                    break;
                }
            }
            if (existManager) {
                //  업무 공간 관리자가 일반 사용자 역할에 존재할 수 있으므로 제거 로직 수행
                this.removeProjectRoleUser(project, ProjectRole.TYPE_DEFAULT, workspaceManager, null);
                //  업무 공간 관리자에게 프로젝트 관리자 역할 부여
                List<Map<String, Long>> projectRoleUserMaps = Lists.newArrayList();
                Map<String, Long> projectRoleUserMap = new HashMap<>();
                projectRoleUserMap.put("projectRoleId", projectRole.getId());
                projectRoleUserMap.put("userId", workspaceManager.getId());
                projectRoleUserMap.put("registerId", this.webAppUtil.getLoginId());
                projectRoleUserMaps.add(projectRoleUserMap);
                //  업무 공간 관리자를 프로젝트 관리자로 지정
                this.projectRoleUserMapper.insertProjectRoleUser(projectRoleUserMaps);
                /*workspaceManager.addProjectRole(projectRole);*/
            }
        }
        Map<String, Long> deleteProjectRoleUserMap = new HashMap<>();
        deleteProjectRoleUserMap.put("projectRoleId", projectRole.getId());
        deleteProjectRoleUserMap.put("userId", user.getId());
        //  탈퇴한 사용자는 프로젝트 역할 연결 정보 삭제
        this.projectRoleUserMapper.deleteProjectRoleUser(deleteProjectRoleUserMap);
        //  탈퇴한 사용자는 프로젝트 역할 연결 정보 삭제
        //  user.removeProjectRole(projectRole);
    }
    //  프로젝트에 참여하는 사용자, 프로젝트 관리자를 파라미터에 따라 조회한다.
    @Override
    @Transactional(readOnly = true)
    public List<Map<String, Object>> findProjectRoleUser(Map<String, Object> projectRoleUserMap) {
        return this.projectRoleUserMapper.findProjectRoleUser(projectRoleUserMap);
    }
    //  프로젝트 관리자 여부를 확인한다.
    @Override
    @Transactional(readOnly = true)
    public boolean checkProjectManager(Project project) {
        ProjectRole projectManagerRole = this.projectRoleService.findByProjectIdAndRoleType(project.getId(), ProjectRole.TYPE_MANAGER);
        //  해당 사용자가 특정 역할에 소속되어 있는지 확인한다.
        ProjectRoleUser projectRoleUser = this.findByProjectRoleIdAndUserId(projectManagerRole.getId(), this.webAppUtil.getLoginId());
        return projectRoleUser != null;
    }
}
src/main/java/kr/wisestone/owl/service/impl/ProjectServiceImpl.java
New file
@@ -0,0 +1,1040 @@
package kr.wisestone.owl.service.impl;
import com.google.common.collect.Lists;
import kr.wisestone.owl.common.ExcelConditionCheck;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.*;
import kr.wisestone.owl.domain.enumType.EmailType;
import kr.wisestone.owl.domain.enumType.ProjectType;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.mapper.ProjectMapper;
import kr.wisestone.owl.repository.ProjectClosureRepository;
import kr.wisestone.owl.repository.ProjectRepository;
import kr.wisestone.owl.service.*;
import kr.wisestone.owl.util.CommonUtil;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.util.DateUtil;
import kr.wisestone.owl.util.MapUtil;
import kr.wisestone.owl.vo.*;
import kr.wisestone.owl.web.condition.ProjectCondition;
import kr.wisestone.owl.web.form.ProjectForm;
import kr.wisestone.owl.web.view.ExcelView;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
@Service
public class ProjectServiceImpl extends AbstractServiceImpl<Project, Long, JpaRepository<Project, Long>> implements ProjectService {
    private static final Logger log = LoggerFactory.getLogger(ProjectServiceImpl.class);
    @Autowired
    private ProjectRepository projectRepository;
    @Autowired
    private UserService userService;
    @Autowired
    private ProjectRoleService projectRoleService;
    @Autowired
    private WorkflowStatusService workflowStatusService;
    @Autowired
    private WorkspaceService workspaceService;
    @Autowired
    private UserWorkspaceService userWorkspaceService;
    @Autowired
    private SystemEmailService systemEmailService;
    @Autowired
    private ProjectRoleUserService projectRoleUserService;
    @Autowired
    private AttachedFileService attachedFileService;
    @Autowired
    private IssueService issueService;
    @Autowired
    private IssueUserService issueUserService;
    @Autowired
    private IssueNumberGeneratorService issueNumberGeneratorService;
    @Autowired
    private ProjectClosureRepository projectClosureRepository;
    @Autowired
    private ProjectMapper projectMapper;
    @Autowired
    private ExcelView excelView;
    @Autowired
    private ExcelConditionCheck excelConditionCheck;
    @Override
    protected JpaRepository<Project, Long> getRepository() {
        return this.projectRepository;
    }
    //  기본으로 제공되는 프로젝트를 생성한다.
    @Override
    @Transactional
    public Project addDefaultProject(User user, Workspace workspace) {
        Project project = new Project();
        project.setName(this.messageAccessor.message("common.issueManagementProject")); // 이슈 관리 프로젝트
        project.setProjectKey("BTS");
        project.setStatus(Project.PROJECT_OPEN);
        project.setDescription(this.messageAccessor.message("common.intoTheSystemIssueManagementProject")); // 시스템에서 기본으로 제공되는 이슈 관리 프로젝트입니다.
        project.setStartDate(DateUtil.convertDateToYYYYMMDD(new Date()));
        project.setEndDate(DateUtil.convertDateToYYYYMMDD(DateUtil.addDays(new Date(), 3650)));
        //  기본으로 생성되는 프로젝트의 유형은 BTS
        project.setProjectType(ProjectType.BTS_PROJECT);
        project.setWorkspace(workspace);
        project.setDefaultYn(true);
        this.projectRepository.saveAndFlush(project);
        //  프로젝트 기본 역할과 관리자 역할을 생성한다. 관리자는 생성한 사용자
        this.projectRoleService.addDefaultProjectRole(project, Lists.newArrayList(user), Lists.newArrayList());
        //  각 프로젝트의 이슈 번호를 자동으로 생성한다.
        this.issueNumberGeneratorService.generateIssueNumber(project);
        //  기본으로 생성되는 프로젝트의 유형은 개발 프로젝트
        this.workflowStatusService.addDefaultWorkflowStatus(project, ProjectType.RMS_PROJECT);
        return project;
    }
    //  프로젝트를 사용자가 생성한다.
    @Transactional
    @Override
    public Project addProject(ProjectForm projectForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        //  이름 유효성 체크
        this.verifyName(projectForm.getName(), null);
        //  키 유효성 체크
        this.checkDuplicateProjectKey(projectForm.getProjectKey());
        //  상태 체크
        this.verifyProjectStatus(projectForm.getStatus());
        //  날짜 유효성 체크
        this.checkStartEndDate(projectForm.getStartDate(), projectForm.getEndDate());
        //  관리자 유효성 체크
        this.verifyManager(projectForm.getManagerIds());
        Project project = ConvertUtil.copyProperties(projectForm, Project.class, "projectType");
        project.setProjectType(ProjectType.valueOf(projectForm.getProjectType()));
        Workspace workspace = this.workspaceService.getWorkspace(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        project.setWorkspace(workspace);
        this.projectRepository.saveAndFlush(project);
        // 상위프로젝트 넣기
        SetParentProject(projectForm.getParentProjectId(), project);
        //  각 프로젝트의 이슈 번호를 자동으로 생성한다.
        this.issueNumberGeneratorService.generateIssueNumber(project);
        //  일반 사용자 및 관리자를 등록하고 사용자들에게 해당 역할을 배정한다.
        this.registerManagerAndUser(projectForm, project);
        //  프로젝트 유형에 따른 워크플로우 생성
        this.workflowStatusService.addDefaultWorkflowStatus(project, ProjectType.valueOf(projectForm.getProjectType()));
        return project;
    }
    //  일반 사용자 및 관리자를 등록하고 사용자들에게 해당 역할을 배정한다.
    private void registerManagerAndUser(ProjectForm projectForm, Project project) {
        List<User> managers = Lists.newArrayList();
        //  관리자 등록
        for (Long managerId : projectForm.getManagerIds()) {
            User user = this.userService.getUser(managerId);
            managers.add(user);
        }
        List<User> users = Lists.newArrayList();
        List<String> sendEmails = Lists.newArrayList(); //  메일 대상자
        //  일반 사용자 등록
        for (Long userId : projectForm.getUserIds()) {
            User user = this.userService.getUser(userId);
            users.add(user);
            sendEmails.add(user.getAccount());
        }
        //  기본, 관리자 프로젝트 역할을 생성하고 사용자를 해당 역할에 배정한다.
        this.projectRoleService.addDefaultProjectRole(project, managers, users);
        //  프로젝트 참여자들에게 이메일 발송을 예약한다.
        Map<String, Object> projectMap = new HashMap<>();
        projectMap.put("workspaceName", project.getWorkspace().getName());
        projectMap.put("projectName", project.getName());
        projectMap.put("registerDate", DateUtil.convertDateToStr(new Date()));
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(managers.get(0).getName());
        stringBuilder.append("(");
        stringBuilder.append(CommonUtil.decryptAES128(managers.get(0).getAccount()));
        stringBuilder.append(")");
        projectMap.put("projectManagerName", stringBuilder.toString());
        //  프로젝트 일반 참여 메일 발송 예약
        this.systemEmailService.reservationEmail(sendEmails.toArray(new String[sendEmails.size()]), EmailType.PROJECT_DEFAULT_INCLUDE, projectMap);
    }
    //  이름 유효성 체크
    private void verifyName(String name, Long id) {
        if (StringUtils.isEmpty(name)) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PROJECT_NOT_NAME));
        }
        if (name.length() > 50) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PROJECT_NAME_MAX_LENGTH_OUT));
        }
        Project project;
        Long workspaceId = this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId();
        if (id == null) {
            project = this.projectRepository.findByNameAndWorkspaceId(name, workspaceId);
        }
        else {
            project = this.projectRepository.findByNameAndWorkspaceIdAndIdNot(name, workspaceId, id);
        }
        if (project != null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PROJECT_USED_NAME));
        }
    }
    //  키 유효성 체크
    private void checkDuplicateProjectKey(String projectKey) {
        if (StringUtils.isEmpty(projectKey)) {
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.PROJECT_KEY_NOT_EXIST));
        }
        if (projectKey.length() > 10) {
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.PROJECT_OVER_LENGTH_PROJECT_KEY));
        }
        Project project = this.findByProjectKey(projectKey);
        if (project != null) {
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.PROJECT_USED_PROJECT_KEY));
        }
    }
    //  날짜 유효성 체크
    private void checkStartEndDate(String startDate, String endDate) {
        if (StringUtils.isEmpty(startDate) || StringUtils.isEmpty(endDate)) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.DATE_NOT_EXIST));
        }
        Date start = DateUtil.convertStrToDate(startDate, "yyyy-MM-dd");
        Date end = DateUtil.convertStrToDate(endDate, "yyyy-MM-dd");
        if (start.getTime() > end.getTime()) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.DATE_PICKER_NOT_AVAILABLE));
        }
    }
    //  관리자 유효성 체크
    private void verifyManager(List<Long> managerIds) {
        if (managerIds.isEmpty()) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PROJECT_NOT_MANAGER));
        }
    }
    //  상태 체크
    private void verifyProjectStatus(String projectStatus) {
        if (StringUtils.isEmpty(projectStatus)) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PROJECT_NOT_STATUS));
        }
    }
    //  프로젝트 목록을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public List<ProjectVo> findProject(Map<String, Object> resJsonData,
                                       ProjectCondition condition, Pageable pageable) {
        condition.setPage(pageable.getPageNumber() * pageable.getPageSize());
        condition.setPageSize(pageable.getPageSize());
        condition.setLoginUserId(this.webAppUtil.getLoginId());
        condition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        List<Map<String, Object>> results;
        Long totalCount;
        if (condition.getWorkspaceManager()) {
            //  업무공간 관리자일 경우 모든 프로젝트가 표시되어야 한다.
            //  관리자일 때
            if (this.userWorkspaceService.checkWorkspaceManager()) {
                results = this.projectMapper.findByWorkspaceManager(condition);
                totalCount = this.projectMapper.countByWorkspaceManager(condition);
            }
            else {
                results = this.projectMapper.find(condition);
                totalCount = this.projectMapper.count(condition);
            }
        }
        else {
            results = this.projectMapper.find(condition);
            totalCount = this.projectMapper.count(condition);
        }
        int totalPage = (int) Math.ceil((totalCount - 1) / pageable.getPageSize()) + 1;
        //  프로젝트 조회 결과를 ProjectVos 로 변환한다. - 관리자, 일반 사용자 정보 추가
        List<ProjectVo> projectVos = this.makeProjectVos(results);
        this.setChildrenProject(projectVos);
        resJsonData.put(Constants.RES_KEY_CONTENTS, projectVos);
        resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(),
                totalPage, totalCount));
        return projectVos;
    }
    void setChildrenProject(List<ProjectVo> projectVos) {
        int projectCount = projectVos.size();
        for (int i=0; i< projectCount; i++) {
            ProjectVo projectVo = projectVos.get(i);
            List<Map<String, Object>> children = this.projectMapper.findChildrenProject(projectVo.getId());
            if (children != null && children.size() > 0) {
                List<ProjectVo> childrenVo = this.makeProjectVos(children);
                projectVo.setChildProjects(childrenVo);
                setChildrenProject(childrenVo);
            }
        }
    }
    //  프로젝트 조회 결과를 ProjectVos 로 변환한다.
    private List<ProjectVo> makeProjectByVos(List<Map<String, Object>> results) {
        List<ProjectVo> projectVos = Lists.newArrayList();
        for (Map<String, Object> result : results) {
            ProjectVo projectVo = ConvertUtil.convertMapToClass(result, ProjectVo.class);
            projectVos.add(projectVo);
        }
        return projectVos;
    }
    //  프로젝트 조회 결과를 ProjectVos 로 변환한다. - 관리자, 일반 사용자 정보 추가
    private List<ProjectVo> makeProjectVos(List<Map<String, Object>> results) {
        List<ProjectVo> projectVos = Lists.newArrayList();
        for (Map<String, Object> result : results) {
            ProjectVo projectVo = ConvertUtil.convertMapToClass(result, ProjectVo.class);
            //  프로젝트에 참여하는 사용자를 셋팅한다. - 관리자 / 일반 사용자
            this.setProjectUser(projectVo, true);
            this.setProjectUser(projectVo, false);
            //  업무공간 담당자는 모든 프로젝트를 수정/삭제할 수 있어야 한다.
            if (this.userWorkspaceService.checkWorkspaceManager()) {
                projectVo.setModifyPermissionCheck(true);
            }
            projectVos.add(projectVo);
        }
        return projectVos;
    }
    //  관리자, 일반 사용자를 조건에 따라 찾아준다.
    private void setProjectUser(ProjectVo projectVo, Boolean findProjectManager) {
        Map<String, Object> projectRoleUserMap = new HashMap<>();
        projectRoleUserMap.put("id", projectVo.getId());
        if (findProjectManager) {
            projectRoleUserMap.put("statuses", Lists.newArrayList("02"));   //  관리자 셋팅
        }
        else {
            projectRoleUserMap.put("statuses", Lists.newArrayList("01"));   //  일반 사용자 셋팅
        }
        //  사용자 정보 셋팅
        List<Map<String, Object>> projectRoleUsers = this.projectRoleUserService.findProjectRoleUser(projectRoleUserMap);
        if (projectRoleUsers != null && !projectRoleUsers.isEmpty()) {
            List<UserVo> userVos = Lists.newArrayList();
            for (Map<String, Object> projectRoleUser : projectRoleUsers) {
                UserVo userVo = ConvertUtil.convertMapToClass(projectRoleUser, UserVo.class);
                userVo.setByName(userVo.getName() + "(" + CommonUtil.decryptAES128(userVo.getAccount()) + ")");
                userVo.setAccount(CommonUtil.decryptAES128(userVo.getAccount()));
                //  현재 로그인한 사용자가 담당자일 경우 수정 권한을 준다.
                if (userVo.getId().equals(this.webAppUtil.getLoginId()) && findProjectManager) {
                    projectVo.setModifyPermissionCheck(Boolean.TRUE);
                }
                userVos.add(userVo);
            }
            if (findProjectManager) {
                projectVo.setProjectManagerVos(userVos);
            }
            else {
                projectVo.setProjectUserVos(userVos);
            }
        }
    }
    //  프로젝트 상세 정보를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public void detailProject(Map<String, Object> resJsonData, ProjectCondition projectCondition) {
        ProjectVo projectVo = new ProjectVo();
        if (projectCondition.getId() != null) {
            Project project = this.getProject(projectCondition.getId());
            projectVo = ConvertUtil.copyProperties(project, ProjectVo.class);
            projectVo.setProjectType(project.getProjectType().toString());
            // 상위 프로젝트 정보 가져오기
            ProjectClosure closure = project.getParentProjectClosure();
            if (closure != null) {
                ProjectVo parentProjectVo = ConvertUtil.copyProperties(closure.getParentProject(), ProjectVo.class);
                projectVo.setParentProjectVo(parentProjectVo);
            }
            switch (projectCondition.getDeep()) {
                case "01": //  프로젝트에 참여하는 사용자, 관리자 정보를 셋팅한다.
                    this.setProjectUser(projectVo, true);
                    this.setProjectUser(projectVo, false);
                    break;
            }
        }
        resJsonData.put(Constants.RES_KEY_CONTENTS, projectVo);
    }
    //  프로젝트 정보를 수정한다.
    @Transactional
    @Override
    public Project modifyProject(ProjectForm projectForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        //  이름 유효성 체크
        this.verifyName(projectForm.getName(), projectForm.getId());
        //  상태 체크
        this.verifyProjectStatus(projectForm.getStatus());
        //  날짜 유효성 체크
        this.checkStartEndDate(projectForm.getStartDate(), projectForm.getEndDate());
        //  관리자 유효성 체크
        this.verifyManager(projectForm.getManagerIds());
        Project project = this.getProject(projectForm.getId());
        //  프로젝트 참여 사용자
        List<Long> existUserIds = this.getIncludeProjectUser(project);
        //  워크스페이스에서 기본으로 제공되는 프로젝트에 대한 체크
        this.checkDefaultProject(project, projectForm);
        //  수정 권한 체크
        this.checkModifyPermission(project.getId());
        //  관리자 변경
        Map<String, Object> changeProjectManagerNotifications = this.modifyProjectManagers(project, projectForm, ProjectRole.TYPE_MANAGER);
        //  일반 사용자 변경
        Map<String, Object> changeProjectUserNotifications = this.modifyProjectManagers(project, projectForm, ProjectRole.TYPE_DEFAULT);
        ConvertUtil.copyProperties(projectForm, project, "id", "projectType");
        this.projectRepository.saveAndFlush(project);
        // 상위 프로젝트 정보 저장
        SetParentProject(projectForm.getParentProjectId(), project);
        //  프로젝트에서 참여가 제외된 사용자는 이슈 담당자에서 제외한다.
        //  해당 프로젝트에 참여하는 모든 사용자 조회
        //  빠진 사람이 관리하는 이슈 전체 조회 후 데이터 삭제
        List<Long> changeUserIds = this.getIncludeProjectUser(project);
        //  참여에서 제외된 사용자를 찾고 담당하고 있던 이슈에서 제외한다.
        this.checkExcludeUserAndRemoveIssueAssignee(project, existUserIds, changeUserIds);
        //  관리자/일반 사용자 변경 내역을 통지한다.
        this.notificationProjectRoleUser(changeProjectManagerNotifications, changeProjectUserNotifications, project);
        return project;
    }
    void SetParentProject(Long parentProjectId, Project project) {
        ProjectClosure projectClosure = this.projectClosureRepository.findByProjectId(project.getId());
        if (parentProjectId != null && parentProjectId > -1) {
            Project parentProject = this.getProject(parentProjectId);
            if (projectClosure != null) {
                projectClosure.setParentProject(parentProject);
            } else {
                projectClosure = new ProjectClosure(project, parentProject);
            }
            this.projectClosureRepository.saveAndFlush(projectClosure);
        } else {
            if (projectClosure != null) {
                this.projectClosureRepository.delete(projectClosure);
            }
        }
    }
    //  프로젝트 참여 사용자
    private List<Long> getIncludeProjectUser(Project project) {
        Set<Long> includeUserIds = new HashSet<>();
        for (ProjectRole projectRole : project.getProjectRoles()) {
            List<ProjectRoleUser> projectRoleUsers =  this.projectRoleUserService.findByProjectRoleId(projectRole.getId());
            for (ProjectRoleUser projectRoleUser : projectRoleUsers) {
                includeUserIds.add(projectRoleUser.getUser().getId());
            }
        }
        return Lists.newArrayList(includeUserIds);
    }
    //  참여에서 제외된 사용자를 찾고 담당하고 있던 이슈에서 제외한다.
    private void checkExcludeUserAndRemoveIssueAssignee(Project project, List<Long> existUserIds, List<Long> changeUserIds) {
        List<Long> excludeUserIds = CommonUtil.searchChangeList(changeUserIds, existUserIds);
        if (excludeUserIds.size() > 0) {
            this.issueUserService.removeIssueUser(project.getId(), excludeUserIds);
        }
    }
    //  기본 제공되는 프로젝트의 관리자는 워크스페이스 관리자가 포함되어 있어야 한다.
    private void checkDefaultProject(Project project, ProjectForm projectForm) {
        if (project.getDefaultYn()) {
            //  해당 프로젝트의 워크스페이스의 관리자를 찾는다.
            //  기본 프로젝트는 워크스페이스 관리자가 무조건 프로젝트 관리자로 들어가 있어야 한다.
            Workspace workspace = project.getWorkspace();
            List<UserWorkspace> userWorkspaces = this.userWorkspaceService.findByWorkspaceIdAndManagerYn(workspace.getId(), true);
            for (UserWorkspace userWorkspace : userWorkspaces) {
                User workspaceManager = userWorkspace.getUser();
                boolean checkDefaultManager = false;
                for (Long managerId : projectForm.getManagerIds()) {
                    if (managerId.equals(workspaceManager.getId())) {
                        checkDefaultManager = true;
                        break;
                    }
                }
                if (!checkDefaultManager) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.DEFAULT_PROJECT_MANAGER_NOT_CHANGE));
                }
            }
        }
    }
    //  로그인한 사용자가 관리자 역할에 소속되어 있는지 확인한다.
    private void checkModifyPermission(Long projectId) {
        Boolean hasPermission = Boolean.FALSE;
        //  해당 업무 공간의 관리자일 경우 권한 체크를 하지 않는다.
        if (this.userWorkspaceService.checkWorkspaceManager()) {
            return;
        }
        //  관리자 역할 조회
        Map<String, Object> projectRoleUserMap = new HashMap<>();
        projectRoleUserMap.put("id", projectId);
        projectRoleUserMap.put("statuses", Lists.newArrayList("02"));   //  관리자 조회
        List<Map<String, Object>> projectUsers = this.projectRoleUserService.findProjectRoleUser(projectRoleUserMap);
        //  현재 로그인 사용자가 관리자인지 확인
        if (projectUsers != null && !projectUsers.isEmpty()) {
            for (Map<String, Object> projectUser : projectUsers) {
                UserVo userVo = ConvertUtil.convertMapToClass(projectUser, UserVo.class);
                if (userVo.getId().equals(this.webAppUtil.getLoginId())) {
                    hasPermission = true;
                    break;
                }
            }
        }
        if (!hasPermission) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PROJECT_NOT_MODIFY_PERMISSION));
        }
    }
    //  관리자를 변경한다.
    private Map<String, Object> modifyProjectManagers(Project project, ProjectForm projectForm, String roleType) {
        ProjectRole projectRole = this.projectRoleService.findByProjectIdAndRoleType(project.getId(), roleType);
        List<User> oldManager = Lists.newArrayList();
        List<User> newManager = Lists.newArrayList();
        Map<String, Object> results = new HashMap<>();
        for (ProjectRoleUser projectRoleUser : projectRole.getProjectRoleUsers()) {
            User user = projectRoleUser.getUser();
            oldManager.add(user);
            user.removeProjectRole(projectRole);
        }
        projectRole.getProjectRoleUsers().clear();
        //  관리자일 경우
        if (roleType.equals(ProjectRole.TYPE_MANAGER)) {
            for (Long managerId : projectForm.getManagerIds()) {
                User user = this.userService.getUser(managerId);
                newManager.add(user);
                user.addProjectRole(projectRole);
            }
        }
        else if (roleType.equals(ProjectRole.TYPE_DEFAULT)) {
            //  일반 사용자일 경우
            for (Long userId : projectForm.getUserIds()) {
                User user = this.userService.getUser(userId);
                newManager.add(user);
                user.addProjectRole(projectRole);
            }
        }
        //  제외 대상자 찾기, oldManager 에는 있는데 newManager 에 없으면 제외 대상
        List<String> excludeUsers = this.systemEmailService.notificationUserChange(oldManager, newManager);
        //  참여 대상자 찾기, newManager 에는 있는데 oldManager 에 없으면 초대받은 대상
        List<String> includeUsers = this.systemEmailService.notificationUserChange(newManager, oldManager);
        results.put("excludeUsers", excludeUsers);
        results.put("includeUsers", includeUsers);
        return results;
    }
    //  프로젝트 참여, 제외 통지 정보를 중복으로 나가지 않도록 체크한다.
    private void notificationProjectRoleUser(Map<String, Object> changeProjectManagerNotifications, Map<String, Object> changeProjectUserNotifications, Project project) {
        List<String> projectManagerExcludeUsers = (List<String>) changeProjectManagerNotifications.get("excludeUsers");  //  관리자 제외 사용자
        List<String> projectManagerIncludeUsers = (List<String>) changeProjectManagerNotifications.get("includeUsers");  //  관리자 참여 사용자
        List<String> projectUserExcludeUsers = (List<String>) changeProjectUserNotifications.get("excludeUsers");    //  제외된 일반 사용자
        List<String> projectUserIncludeUsers = (List<String>) changeProjectUserNotifications.get("includeUsers");    //  참여된 일반 사용자
        Map<String, Object> projectMap = new HashMap<>();
        projectMap.put("workspaceName", project.getWorkspace().getName());
        projectMap.put("projectName", project.getName());
        projectMap.put("registerDate", DateUtil.convertDateToStr(new Date()));
        Map<String, Object> projectRoleUserMap = new HashMap<>();
        projectRoleUserMap.put("id", project.getId());
        projectRoleUserMap.put("statuses", Lists.newArrayList("02"));   //  관리자 조회
        //  관리자 정보 셋팅
        List<Map<String, Object>> projectRoleUsers = this.projectRoleUserService.findProjectRoleUser(projectRoleUserMap);
        if (projectRoleUsers != null && !projectRoleUsers.isEmpty()) {
            for (Map<String, Object> projectRoleUser : projectRoleUsers) {
                UserVo userVo = ConvertUtil.convertMapToClass(projectRoleUser, UserVo.class);
                StringBuilder stringBuilder = new StringBuilder();
                stringBuilder.append(userVo.getName());
                stringBuilder.append("(");
                stringBuilder.append(CommonUtil.decryptAES128(userVo.getAccount()));
                stringBuilder.append(")");
                projectMap.put("projectManagerName", stringBuilder.toString());
            }
        }
        // 프로젝트 관리자에서 제외되었으면서 프로젝트 일반 사용자로 들어간 경우
        List<String> excludeManagerAndIncludeUser = Lists.newArrayList();
        for (String projectManagerExcludeUserEmail : projectManagerExcludeUsers) {
            for (String projectUserIncludeUserEmail : projectUserIncludeUsers) {
                if (projectManagerExcludeUserEmail.equals(projectUserIncludeUserEmail)) {
                    excludeManagerAndIncludeUser.add(projectManagerExcludeUserEmail);
                }
            }
        }
        //  일반 사용자에서 제외되었으면서 프로젝트 관리자로 들어간 경우
        List<String> excludeUserAndIncludeManager = Lists.newArrayList();
        for (String projectUserExcludeUserEmail : projectUserExcludeUsers) {
            for (String projectManagerIncludeUserEmail : projectManagerIncludeUsers) {
                if (projectUserExcludeUserEmail.equals(projectManagerIncludeUserEmail)) {
                    excludeUserAndIncludeManager.add(projectManagerIncludeUserEmail);
                }
            }
        }
        // 프로젝트 관리자에서 제외되었으면서 프로젝트 일반 사용자로 들어간 경우
        this.sendEmailProjectRoleChange(excludeManagerAndIncludeUser, EmailType.PROJECT_MANAGER_EXCLUDE_AND_PROJECT_DEFAULT_INCLUDE, projectMap);
        //  일반 사용자에서 제외되었으면서 프로젝트 관리자로 들어간 경우
        this.sendEmailProjectRoleChange(excludeUserAndIncludeManager, EmailType.PROJECT_DEFAULT_EXCLUDE_AND_PROJECT_MANAGER_INCLUDE, projectMap);
        //  관리자 제외 메일 최종
        this.sendEmailProjectRoleChange(this.checkDuplicationEmails(projectManagerExcludeUsers, excludeManagerAndIncludeUser, excludeUserAndIncludeManager), EmailType.PROJECT_MANAGER_EXCLUDE, projectMap);
        //  관리차 참여 메일 최종
        this.sendEmailProjectRoleChange(this.checkDuplicationEmails(projectManagerIncludeUsers, excludeManagerAndIncludeUser, excludeUserAndIncludeManager), EmailType.PROJECT_MANAGER_INCLUDE, projectMap);
        //  일반 사용자 제외 메일 최종
        this.sendEmailProjectRoleChange(this.checkDuplicationEmails(projectUserExcludeUsers, excludeManagerAndIncludeUser, excludeUserAndIncludeManager), EmailType.PROJECT_DEFAULT_EXCLUDE, projectMap);
        //  일반 사용자 참여 메일 최종
        this.sendEmailProjectRoleChange(this.checkDuplicationEmails(projectUserIncludeUsers, excludeManagerAndIncludeUser, excludeUserAndIncludeManager), EmailType.PROJECT_DEFAULT_INCLUDE, projectMap);
    }
    //  중복으로 나가는 메일이 있는지 체크한다.
    private List<String> checkDuplicationEmails(List<String> checkEmails, List<String> excludeManagerAndIncludeUser, List<String> excludeUserAndIncludeManager) {
        List<String> sendProjectManagerExcludeUserEmails = Lists.newArrayList();
        for (String projectManagerExcludeUserEmail : checkEmails) {
            boolean sendEmail = true;
            //  관리자 제외 메일을 보내려는데 중복되는 메일이 나갈지 판단한다.
            for (String excludeManagerAndIncludeUserEmail : excludeManagerAndIncludeUser) {
                if (excludeManagerAndIncludeUserEmail.equals(projectManagerExcludeUserEmail)) {
                    sendEmail = false;
                    break;
                }
            }
            //  관리자 제외 메일을 보내려는데 중복되는 메일이 나갈지 판단한다.
            for (String excludeUserAndIncludeManagerEmail : excludeUserAndIncludeManager) {
                if (excludeUserAndIncludeManagerEmail.equals(projectManagerExcludeUserEmail)) {
                    sendEmail = false;
                    break;
                }
            }
            if (sendEmail) {
                sendProjectManagerExcludeUserEmails.add(projectManagerExcludeUserEmail);
            }
        }
        return sendProjectManagerExcludeUserEmails;
    }
    //  프로젝트 참여가 변경된 대상자에게 이메일을 보낸다.
    private void sendEmailProjectRoleChange(List<String> sendEmails, EmailType emailType, Map<String, Object> params) {
        String[] sendUsers = sendEmails.toArray(new String[sendEmails.size()]);
        if (sendUsers.length > 0) {
            //  마지막 접근 프로젝트에서 제외되었을 때 화면 새로고침을 한다.
            switch (emailType) {
                case PROJECT_DEFAULT_EXCLUDE:
                case PROJECT_MANAGER_EXCLUDE:
                    this.updateProjectExcludeUserLastWorkspaceId(sendUsers, params);
                    break;
            }
            this.systemEmailService.reservationEmail(sendUsers, emailType, params);
        }
    }
    //  접속중인 사용자중 제외당한 사용자가 해당 프로젝트를 보고있으면 경고창을 표시하고 화면을 새로고침해준다.
    private void updateProjectExcludeUserLastWorkspaceId(String[] sendUsers, Map<String, Object> params) {
       /* List<UserVo> activeLoginUserVos = this.webSocketSessionService.getActiveUserVos();
        Long projectId = MapUtil.getLong(params, "projectId");*/
        /*for (String email : sendUsers) {
            //  접속중이 아니어도 마지막 정보는 업데이트한다.
            User user = this.userService.findByAccount(email);
            if (user != null && projectId != null) {
                //  해당 사용자가 마지막으로 접근한 프로젝트가 제외당한 프로젝트라면 워크스페이스 정보를 초기화한다.
                if (projectId.equals(user.getLastProjectId())) {
                    //  자신의 마지막 접근 workspace & project 정보를 본인이 관리하는 워크스페이스의 기본 프로젝트로 초기화한다.
                    this.userService.initLastWorkspaceIdAndLastProjectId(user);
                    for (UserVo userVo : activeLoginUserVos) {
                        //  접속중인 사용자에게만 웹 소켓 업데이트 시작.
                        if (userVo.getAccount().equals(email)) {
                            this.simpMessagingTemplate.convertAndSendToUser(email, "/notification/project-exclude", this.messageAccessor.getMessage(MsgConstants.PROJECT_EXCLUDE), params);
                        }
                    }
                }
            }
        }*/
    }
    //  프로젝트 키로 프로젝트를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public Project findByProjectKey(String projectKey) {
        return this.projectRepository.findByProjectKeyAndWorkspaceId(projectKey, this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
    }
    //  프로젝트 아이디로 프로젝트를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public Project getProject(Long id) {
        if (id == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PROJECT_NOT_EXIST));
        }
        Project project = this.findOne(id);
        if (project == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PROJECT_NOT_EXIST));
        }
        return project;
    }
    //  프로젝트를 삭제한다.
    @Override
    @Transactional
    public void removeProjects(ProjectForm projectForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        if (projectForm.getRemoveIds().size() < 1) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PROJECT_REMOVE_NOT_SELECT));
        }
        for (Long projectId : projectForm.getRemoveIds()) {
            this.removeProjects(projectId);
        }
        this.projectRepository.flush();
    }
    private void removeProjects(Long projectId) {
        Project project = this.getProject(projectId);
        //  기본 프로젝트는 삭제 금지
        if (project.getDefaultYn()) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.DEFAULT_PROJECT_NOT_REMOVE));
        }
        //  로그인한 사용자가 관리자 역할에 소속되어 있는지 확인한다.
        this.checkModifyPermission(project.getId());
        List<String> sendEmails = Lists.newArrayList();
        Map<String, Object> params = new HashMap<>();
        params.put("projectId", projectId);
        //  해당 프로젝트에 참여하고 있는 사용자는 모두 프로젝트 제외 알림 메일을 해준다.
        for (ProjectRole projectRole : project.getProjectRoles()) {
            for (ProjectRoleUser projectRoleUser : projectRole.getProjectRoleUsers()) {
                sendEmails.add(projectRoleUser.getUser().getAccount());
            }
        }
        String[] sendUsers = sendEmails.toArray(new String[sendEmails.size()]);
        this.updateProjectExcludeUserLastWorkspaceId(sendUsers, params);
        //  프로젝트에 있는 모든 이슈 정보를 조회한다.
        List<Long> issueIds = this.issueService.findByProjectId(projectId);
        List<Long> projectRoleIds = Lists.newArrayList();
        for (ProjectRole projectRole : project.getProjectRoles()) {
            projectRoleIds.add(projectRole.getId());
        }
        params.put("issueIds", issueIds);
        params.put("projectRoleIds", projectRoleIds);
        //  프로젝트에 있는 모든 정보를 삭제한다.
        this.projectMapper.deleteProject(params);
        //  프로젝트 삭제시 이슈에 첨부된 파일을 시스템에서 삭제한다.
        this.attachedFileService.deleteIssueCascadeAttachedFile(issueIds, project.getWorkspace());
        this.projectRepository.flush();
        //  this.projectRepository.delete(project.getId());
    }
    //  워크스페이스에 있는 모든 프로젝트를 조회한다. 이슈 엑셀 import 에서 사용
    @Override
    @Transactional(readOnly = true)
    public List<Project> findByWorkspaceId() {
        return this.projectRepository.findByWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
    }
    //  현재 접근한 업무공간에서 참여하고 있는 프로젝트를 조회한다. - 대시보드, 이슈 목록에서 사용
    @Override
    @Transactional(readOnly = true)
    public List<Map<String, Object>> findByWorkspaceIdAndIncludeProject(List<String> statuses, String projectType) {
        ProjectCondition projectCondition = new ProjectCondition();
        projectCondition.setLoginUserId(this.webAppUtil.getLoginId());
        projectCondition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        projectCondition.setProjectType(projectType);
        projectCondition.setStatuses(statuses);
        return this.projectMapper.findByWorkspaceIdAndIncludeProject(projectCondition);
    }
    // 현재 접근한 업구공간에서 참여하고 있는 프로젝트를 조회한다(하위프로젝트 미포함)
    @Override
    public List<Map<String, Object>> findByWorkspaceIdAndIncludeProject(ProjectCondition projectCondition) {
        projectCondition.setLoginUserId(this.webAppUtil.getLoginId());
        projectCondition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        return this.projectMapper.findByWorkspaceIdAndIncludeProject(projectCondition);
    }
    @Override
    public List<Map<String, Object>> findByWorkspaceIdAndIncludeProjectAll(List<String> statuses, String projectType) {
        ProjectCondition projectCondition = new ProjectCondition();
        projectCondition.setLoginUserId(this.webAppUtil.getLoginId());
        projectCondition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        projectCondition.setProjectType(projectType);
        projectCondition.setStatuses(statuses);
        return this.projectMapper.findByWorkspaceIdAndIncludeProjectAll(projectCondition);
    }
    @Override
    public List<Map<String, Object>> findByWorkspaceManagerAll() {
        ProjectCondition projectCondition = new ProjectCondition();
        projectCondition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        return this.projectMapper.findByWorkspaceManagerAll(projectCondition);
    }
    // 현재 접근한 업구공간에서 참여하고 있는 프로젝트를 조회한다(하위프로젝트 포함)
    @Override
    public List<Map<String, Object>> findByWorkspaceIdAndIncludeProjectAll(ProjectCondition projectCondition) {
        projectCondition.setLoginUserId(this.webAppUtil.getLoginId());
        projectCondition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        return this.projectMapper.findByWorkspaceIdAndIncludeProjectAll(projectCondition);
    }
    //  현재 접근한 업무공간에서 참여하고 있는 프로젝트를 조회한다. - 상단 프로젝트 목록에서 사용
    @Override
    @Transactional(readOnly = true)
    public List<ProjectVo> findByIncludeProject(List<String> statuses, String projectType) {
        ProjectCondition projectCondition = new ProjectCondition();
        projectCondition.setLoginUserId(this.webAppUtil.getLoginId());
        projectCondition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        projectCondition.setProjectType(projectType);
        projectCondition.setStatuses(statuses);
        List<Map<String, Object>> results;
        if (this.userWorkspaceService.checkWorkspaceManager()) {
            results = this.projectMapper.findByWorkspaceManager(projectCondition);
        } else {
            results = this.projectMapper.findByWorkspaceIdAndIncludeProject(projectCondition);
        }
        List<ProjectVo> projectVos = this.makeProjectByVos(results);
        this.setChildrenProject(projectVos);
        return  projectVos;
    }
    //  프로젝트 목록을 엑셀로 다운로드 한다.
    @Override
    @Transactional
    public ModelAndView downloadExcel(HttpServletRequest request, Model model) {
        //  사용 공간에서 로그인한 사용자가 비활성인지 확인하고 비활성일 경우 엑셀 다운로드를 금지한다.
        ModelAndView modelAndView = this.workspaceService.checkUseExcelDownload(model);
        if (modelAndView != null) {
            return modelAndView;
        }
        Map<String, Object> conditions = new HashMap<>();
        //  엑셀 다운로드에 필요한 검색 조건 정보를 추출하고 검색 조건 추출에 오류가 발생하면 경고를 표시해준다.
        modelAndView = this.excelConditionCheck.checkCondition(conditions, request, model);
        if (modelAndView != null) {
            return modelAndView;
        }
        ProjectCondition projectCondition = ProjectCondition.make(conditions);
        projectCondition.setLoginUserId(this.webAppUtil.getLoginId());
        projectCondition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        List<Map<String, Object>> results = this.projectMapper.find(projectCondition);
        //  프로젝트 조회 결과를 ProjectVos 로 변환한다. - 관리자, 일반 사용자 정보 추가
        List<ProjectVo> projectVos = this.makeProjectVos(results);
        ExportExcelVo excelInfo = new ExportExcelVo();
        excelInfo.setFileName(this.messageAccessor.message("common.projectList")); // 프로젝트 목록
        excelInfo.addAttrInfos(new ExportExcelAttrVo("statusName", this.messageAccessor.message("common.status"), 6, ExportExcelAttrVo.ALIGN_CENTER)); // 상태
        excelInfo.addAttrInfos(new ExportExcelAttrVo("name", this.messageAccessor.message("common.project"), 40, ExportExcelAttrVo.ALIGN_LEFT)); // 프로젝트
        excelInfo.addAttrInfos(new ExportExcelAttrVo("manager", this.messageAccessor.message("common.admin"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 관리자
        excelInfo.addAttrInfos(new ExportExcelAttrVo("members", this.messageAccessor.message("common.teamMember"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 팀원
        excelInfo.addAttrInfos(new ExportExcelAttrVo("period", this.messageAccessor.message("common.period"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 기간
        excelInfo.addAttrInfos(new ExportExcelAttrVo("projectKey", this.messageAccessor.message("common.projectKey"), 6, ExportExcelAttrVo.ALIGN_CENTER)); // 프로젝트 키
        //  엑셀에 넣을 데이터 - ProjectVos 데이터를 엑셀에서 표시할 수 있는 데이터로 변경한다.
        excelInfo.setDatas(this.convertExcelViewToProjectVos(projectVos));
        model.addAttribute(Constants.EXCEL, excelInfo);
        return new ModelAndView(this.excelView);
    }
    //  ProjectVo 데이터를 엑셀에서 표시할 수 있는 데이터로 변경한다.
    private List<Map<String, String>> convertExcelViewToProjectVos(List<ProjectVo> projectVos) {
        List<Map<String, String>> results = Lists.newArrayList();
        for (ProjectVo projectVo : projectVos) {
            Map<String, String> result = new HashMap<>();
            String projectStatusName = "";
            switch (projectVo.getStatus()) {
                case Project.PROJECT_READY:
                    projectStatusName = this.messageAccessor.message("common.wait"); // 대기
                    break;
                case Project.PROJECT_OPEN:
                    projectStatusName = this.messageAccessor.message("common.progress"); // 진행
                    break;
                case Project.PROJECT_CLOSE:
                    projectStatusName = this.messageAccessor.message("common.end"); // 종료
                    break;
            }
            result.put("statusName", projectStatusName);
            result.put("name", projectVo.getName());
            StringBuilder stringBuilderManager = new StringBuilder();
            if (projectVo.getProjectManagerVos().size() > 0) {
                stringBuilderManager.append(projectVo.getProjectManagerVos().get(0).getName());
                stringBuilderManager.append("(");
                stringBuilderManager.append(projectVo.getProjectManagerVos().get(0).getAccount());
                stringBuilderManager.append(")");
            }
            result.put("manager", stringBuilderManager.toString());
            result.put("members", CommonUtil.convertUserVosToString(projectVo.getProjectUserVos()));
            result.put("projectKey", projectVo.getProjectKey());
            result.put("period", projectVo.getStartDate() + " - " + projectVo.getEndDate());
            results.add(result);
        }
        return results;
    }
    @Override
    @Transactional
    public void findLastUseProject(Map<String, Object> resJsonData) {
        UserVo loginUser = this.webAppUtil.getLoginUser();
        Project project = null;
        if (loginUser.getLastProjectId() != null) {
            project = this.projectRepository.getOne(loginUser.getLastProjectId());
        }
        if (project == null) {
            project = this.projectRepository.findByWorkspaceIdAndDefaultYn(loginUser.getLastWorkspaceId(), true);
        }
        if (project == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.PROJECT_NOT_EXIST));
        }
        resJsonData.put(Constants.RES_KEY_CONTENTS, ConvertUtil.copyProperties(project, ProjectVo.class));
    }
}
Diff truncated after the above file
src/main/java/kr/wisestone/owl/service/impl/QnaServiceImpl.java src/main/java/kr/wisestone/owl/service/impl/ReservationDisableUserServiceImpl.java src/main/java/kr/wisestone/owl/service/impl/SeverityServiceImpl.java src/main/java/kr/wisestone/owl/service/impl/SystemEmailServiceImpl.java src/main/java/kr/wisestone/owl/service/impl/SystemRoleServiceImpl.java src/main/java/kr/wisestone/owl/service/impl/UserHistoryServiceImpl.java src/main/java/kr/wisestone/owl/service/impl/UserInviteProjectServiceImpl.java src/main/java/kr/wisestone/owl/service/impl/UserInviteServiceImpl.java src/main/java/kr/wisestone/owl/service/impl/UserServiceImpl.java src/main/java/kr/wisestone/owl/service/impl/UserWithDrawServiceImpl.java src/main/java/kr/wisestone/owl/service/impl/UserWorkspaceServiceImpl.java src/main/java/kr/wisestone/owl/service/impl/WidgetServiceImpl.java src/main/java/kr/wisestone/owl/service/impl/WorkflowServiceImpl.java src/main/java/kr/wisestone/owl/service/impl/WorkflowStatusServiceImpl.java src/main/java/kr/wisestone/owl/service/impl/WorkflowTransitionServiceImpl.java src/main/java/kr/wisestone/owl/service/impl/WorkspaceServiceImpl.java src/main/java/kr/wisestone/owl/type/LikeType.java src/main/java/kr/wisestone/owl/util/ApplicationContextUtil.java src/main/java/kr/wisestone/owl/util/CommonUtil.java src/main/java/kr/wisestone/owl/util/ConvertUtil.java src/main/java/kr/wisestone/owl/util/DateUtil.java src/main/java/kr/wisestone/owl/util/ElasticSearchUtil.java src/main/java/kr/wisestone/owl/util/MapUtil.java src/main/java/kr/wisestone/owl/util/PageUtil.java src/main/java/kr/wisestone/owl/util/SecurityUtils.java src/main/java/kr/wisestone/owl/util/StringTemplateUtil.java src/main/java/kr/wisestone/owl/util/ThreadCounter.java src/main/java/kr/wisestone/owl/util/WebAppUtil.java src/main/java/kr/wisestone/owl/vo/AttachedFileVo.java src/main/java/kr/wisestone/owl/vo/BaseVo.java src/main/java/kr/wisestone/owl/vo/CustomFieldValueVo.java src/main/java/kr/wisestone/owl/vo/CustomFieldVo.java src/main/java/kr/wisestone/owl/vo/EventVo.java src/main/java/kr/wisestone/owl/vo/ExportExcelAttrVo.java src/main/java/kr/wisestone/owl/vo/ExportExcelVo.java src/main/java/kr/wisestone/owl/vo/FaqVo.java src/main/java/kr/wisestone/owl/vo/GuideVo.java src/main/java/kr/wisestone/owl/vo/IssueCommentVo.java src/main/java/kr/wisestone/owl/vo/IssueCustomFieldValueVo.java src/main/java/kr/wisestone/owl/vo/IssueHistoryVo.java src/main/java/kr/wisestone/owl/vo/IssueRelationVo.java src/main/java/kr/wisestone/owl/vo/IssueReservationVo.java src/main/java/kr/wisestone/owl/vo/IssueStatusVo.java src/main/java/kr/wisestone/owl/vo/IssueTypeCustomFieldVo.java src/main/java/kr/wisestone/owl/vo/IssueTypeVo.java src/main/java/kr/wisestone/owl/vo/IssueVersionVo.java src/main/java/kr/wisestone/owl/vo/IssueVo.java src/main/java/kr/wisestone/owl/vo/ManageUserVo.java src/main/java/kr/wisestone/owl/vo/MessageVo.java src/main/java/kr/wisestone/owl/vo/NoticeVo.java src/main/java/kr/wisestone/owl/vo/PageVo.java src/main/java/kr/wisestone/owl/vo/PaymentHistoryVo.java src/main/java/kr/wisestone/owl/vo/PaymentVo.java src/main/java/kr/wisestone/owl/vo/PermissionVo.java src/main/java/kr/wisestone/owl/vo/PriorityVo.java src/main/java/kr/wisestone/owl/vo/ProjectVo.java src/main/java/kr/wisestone/owl/vo/QnaVo.java src/main/java/kr/wisestone/owl/vo/ResMessageVo.java src/main/java/kr/wisestone/owl/vo/ResPage.java src/main/java/kr/wisestone/owl/vo/SeverityVo.java src/main/java/kr/wisestone/owl/vo/UserInviteVo.java src/main/java/kr/wisestone/owl/vo/UserVo.java src/main/java/kr/wisestone/owl/vo/UserWorkspaceVo.java src/main/java/kr/wisestone/owl/vo/WorkflowStatusVo.java src/main/java/kr/wisestone/owl/vo/WorkflowTransitionVo.java src/main/java/kr/wisestone/owl/vo/WorkflowVo.java src/main/java/kr/wisestone/owl/vo/WorkspaceVo.java src/main/java/kr/wisestone/owl/web/condition/AttachedFileCondition.java src/main/java/kr/wisestone/owl/web/condition/CustomFieldCondition.java src/main/java/kr/wisestone/owl/web/condition/EventCondition.java src/main/java/kr/wisestone/owl/web/condition/FaqCondition.java src/main/java/kr/wisestone/owl/web/condition/GuideCondition.java src/main/java/kr/wisestone/owl/web/condition/IssueCondition.java src/main/java/kr/wisestone/owl/web/condition/IssueCustomFieldValueCondition.java src/main/java/kr/wisestone/owl/web/condition/IssueHistoryCondition.java src/main/java/kr/wisestone/owl/web/condition/IssueRelationCondition.java src/main/java/kr/wisestone/owl/web/condition/IssueReservationCondition.java src/main/java/kr/wisestone/owl/web/condition/IssueStatusCondition.java src/main/java/kr/wisestone/owl/web/condition/IssueTypeCondition.java src/main/java/kr/wisestone/owl/web/condition/IssueTypeCustomFieldCondition.java src/main/java/kr/wisestone/owl/web/condition/IssueVersionCondition.java src/main/java/kr/wisestone/owl/web/condition/NoticeCondition.java src/main/java/kr/wisestone/owl/web/condition/ProjectCondition.java src/main/java/kr/wisestone/owl/web/condition/QnaCondition.java src/main/java/kr/wisestone/owl/web/condition/UserCondition.java src/main/java/kr/wisestone/owl/web/condition/UserHistoryCondition.java src/main/java/kr/wisestone/owl/web/condition/UserWorkspaceCondition.java src/main/java/kr/wisestone/owl/web/condition/WidgetCondition.java src/main/java/kr/wisestone/owl/web/condition/WorkflowCondition.java src/main/java/kr/wisestone/owl/web/condition/WorkflowStatusCondition.java src/main/java/kr/wisestone/owl/web/controller/AttatchedFileController.java src/main/java/kr/wisestone/owl/web/controller/BaseController.java src/main/java/kr/wisestone/owl/web/controller/CustomFieldController.java src/main/java/kr/wisestone/owl/web/controller/EventController.java src/main/java/kr/wisestone/owl/web/controller/FaqController.java src/main/java/kr/wisestone/owl/web/controller/GanttController.java src/main/java/kr/wisestone/owl/web/controller/GuideController.java src/main/java/kr/wisestone/owl/web/controller/IssueCommentController.java src/main/java/kr/wisestone/owl/web/controller/IssueController.java src/main/java/kr/wisestone/owl/web/controller/IssueHistoryController.java src/main/java/kr/wisestone/owl/web/controller/IssueRelationController.java src/main/java/kr/wisestone/owl/web/controller/IssueReservationController.java src/main/java/kr/wisestone/owl/web/controller/IssueSearchController.java src/main/java/kr/wisestone/owl/web/controller/IssueStatusController.java src/main/java/kr/wisestone/owl/web/controller/IssueTableConfigController.java src/main/java/kr/wisestone/owl/web/controller/IssueTypeController.java src/main/java/kr/wisestone/owl/web/controller/IssueTypeCustomFieldController.java src/main/java/kr/wisestone/owl/web/controller/IssueUserController.java src/main/java/kr/wisestone/owl/web/controller/IssueVersionController.java src/main/java/kr/wisestone/owl/web/controller/LanguageController.java src/main/java/kr/wisestone/owl/web/controller/ManageUserController.java src/main/java/kr/wisestone/owl/web/controller/NoticeController.java src/main/java/kr/wisestone/owl/web/controller/PaymentController.java src/main/java/kr/wisestone/owl/web/controller/PermissionController.java src/main/java/kr/wisestone/owl/web/controller/PriorityController.java src/main/java/kr/wisestone/owl/web/controller/ProjectController.java src/main/java/kr/wisestone/owl/web/controller/QnaController.java src/main/java/kr/wisestone/owl/web/controller/ReservationDisableUserController.java src/main/java/kr/wisestone/owl/web/controller/SeverityController.java src/main/java/kr/wisestone/owl/web/controller/SystemEmailController.java src/main/java/kr/wisestone/owl/web/controller/UserController.java src/main/java/kr/wisestone/owl/web/controller/UserHistoryController.java src/main/java/kr/wisestone/owl/web/controller/UserInviteController.java src/main/java/kr/wisestone/owl/web/controller/UserWorkspaceController.java src/main/java/kr/wisestone/owl/web/controller/WebSocketSessionController.java src/main/java/kr/wisestone/owl/web/controller/WidgetController.java src/main/java/kr/wisestone/owl/web/controller/WorkflowController.java src/main/java/kr/wisestone/owl/web/controller/WorkflowStatusController.java src/main/java/kr/wisestone/owl/web/controller/WorkspaceController.java src/main/java/kr/wisestone/owl/web/converter/FileHttpMessageConverter.java src/main/java/kr/wisestone/owl/web/form/CustomFieldForm.java src/main/java/kr/wisestone/owl/web/form/EventForm.java src/main/java/kr/wisestone/owl/web/form/FaqForm.java src/main/java/kr/wisestone/owl/web/form/GuideForm.java src/main/java/kr/wisestone/owl/web/form/IssueCommentForm.java src/main/java/kr/wisestone/owl/web/form/IssueForm.java src/main/java/kr/wisestone/owl/web/form/IssueReservationForm.java src/main/java/kr/wisestone/owl/web/form/IssueStatusChangeForm.java src/main/java/kr/wisestone/owl/web/form/IssueStatusForm.java src/main/java/kr/wisestone/owl/web/form/IssueTypeCustomFieldForm.java src/main/java/kr/wisestone/owl/web/form/IssueTypeForm.java src/main/java/kr/wisestone/owl/web/form/ManageUserForm.java src/main/java/kr/wisestone/owl/web/form/NoticeForm.java src/main/java/kr/wisestone/owl/web/form/PaymentForm.java src/main/java/kr/wisestone/owl/web/form/ProjectForm.java src/main/java/kr/wisestone/owl/web/form/QnaForm.java src/main/java/kr/wisestone/owl/web/form/UserForm.java src/main/java/kr/wisestone/owl/web/form/UserInviteForm.java src/main/java/kr/wisestone/owl/web/form/UserWorkspaceForm.java src/main/java/kr/wisestone/owl/web/form/WorkflowForm.java src/main/java/kr/wisestone/owl/web/form/WorkflowStatusForm.java src/main/java/kr/wisestone/owl/web/form/WorkspaceForm.java src/main/java/kr/wisestone/owl/web/resolver/OwlResponseEntityExceptionHandler.java src/main/java/kr/wisestone/owl/web/view/AbstractExcelView.java src/main/java/kr/wisestone/owl/web/view/ExcelView.java src/main/java/kr/wisestone/owl/web/view/FileDownloadView.java src/main/resources/META-INF/orm.xml src/main/resources/log4j2.xml src/main/resources/mails/issueAddEmail.html src/main/resources/mails/issueRemoveEmail.html src/main/resources/mails/issueSendEmail.html src/main/resources/mails/projectDefaultExcludeAndManagerIncludeEmail.html src/main/resources/mails/projectDefaultExcludeEmail.html src/main/resources/mails/projectDefaultIncludeEmail.html src/main/resources/mails/projectManagerExcludeAndDefaultIncludeEmail.html src/main/resources/mails/projectManagerExcludeEmail.html src/main/resources/mails/projectManagerIncludeEmail.html src/main/resources/mails/regularPaymentCancelByAccountingManagerEmail.html src/main/resources/mails/regularPaymentCancelEmail.html src/main/resources/mails/regularPaymentEmail.html src/main/resources/mails/regularPaymentModifyEmail.html src/main/resources/mails/totalStatisticsEmail.html src/main/resources/mails/userJoinStatisticsEmail.html src/main/resources/mails/userSearchPasswordEmail.html src/main/resources/mails/userWithDrawEmail.html src/main/resources/mails/workspaceExpireAlarmEmail.html src/main/resources/mails/workspaceExpireEmail.html src/main/resources/mails/workspaceInviteNewUserEmail.html src/main/resources/mails/workspaceInviteSystemUserEmail.html src/main/resources/mails/workspaceJoinEmail.html src/main/resources/mails/workspaceMaxStorageExcessEmail.html src/main/resources/mails/workspaceMaxUserExcessEmail.html src/main/resources/migration/V1_1__Initial_Setup.sql src/main/resources/migration/V1_2__Alter_Table.sql src/main/resources/migration/V1_3__Alter_Table.sql src/main/resources/migration/V1_4__Alter_Table.sql src/main/resources/migration/V1_5__Alter_Table.sql src/main/resources/migration/V1_6__Alter_Table.sql src/main/resources/migration/V1_7__Alter_Table.sql src/main/resources/migration/V1_8__Alter_Table.sql src/main/resources/migration/V1_9__Alter_Table.sql src/main/resources/mybatis/config/mybatis-config.xml src/main/resources/mybatis/query-template/attachedFile-template.xml src/main/resources/mybatis/query-template/customField-template.xml src/main/resources/mybatis/query-template/event-template.xml src/main/resources/mybatis/query-template/faq-template.xml src/main/resources/mybatis/query-template/guide-template.xml src/main/resources/mybatis/query-template/issue-template.xml src/main/resources/mybatis/query-template/issueCustomFieldValue-template.xml src/main/resources/mybatis/query-template/issueHistory-template.xml src/main/resources/mybatis/query-template/issueStatus-template.xml src/main/resources/mybatis/query-template/issueType-template.xml src/main/resources/mybatis/query-template/issueUser-template.xml src/main/resources/mybatis/query-template/notice-template.xml src/main/resources/mybatis/query-template/project-template.xml src/main/resources/mybatis/query-template/projectRoleUser-template.xml src/main/resources/mybatis/query-template/qna-template.xml src/main/resources/mybatis/query-template/user-template.xml src/main/resources/mybatis/query-template/userWorkspace-template.xml src/main/resources/mybatis/query-template/widget-template.xml src/main/resources/mybatis/query-template/workflow-template.xml src/main/resources/mybatis/query-template/workspace-template.xml src/main/resources/system_design.properties src/main/resources/system_dev.properties src/main/resources/system_prod.properties src/main/resources/system_test.properties