diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 4b1f86cd..e69de29b 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -1,82 +0,0 @@ -name: Deploy to Elastic Beanstalk - -on: - push: - branches: - - main - - feat/backend-operation - -jobs: - deploy: - runs-on: ubuntu-latest - - env: - AWS_REGION: ap-northeast-2 - APPLICATION_NAME: "motive-backend" - ENVIRONMENT_NAME: "Motive-backend-env" - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Ensure Docker Compose is available - run: | - docker --version - docker compose version - - - name: Build Docker images - run: docker compose build - - - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'corretto' - - - name: Set executable permission for gradlew - run: chmod +x gradlew - - - name: Install Elastic Beanstalk CLI - run: | - sudo apt update - sudo apt install -y python3-pip - pip install awsebcli --upgrade - - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v3 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} - aws-region: ${{ env.AWS_REGION }} - - - name: Set Environment Variables - run: | - export SPRING_APP_NAME=${{ secrets.SPRING_APP_NAME }} - export DATABASE_URL=${{ secrets.DATABASE_URL }} - export MARIA_DATABASE_PORT=${{ secrets.MARIA_DATABASE_PORT }} - export MARIA_DATABASE_NAME=${{ secrets.MARIA_DATABASE_NAME }} - export DB_USERNAME=${{ secrets.DB_USERNAME }} - export DB_PASSWORD=${{ secrets.DB_PASSWORD }} - export REDIS_HOST=${{ secrets.REDIS_HOST }} - export REDIS_PORT=${{ secrets.REDIS_PORT }} - export SPRING_PROFILES_ACTIVE=${{ secrets.SPRING_PROFILES_ACTIVE }} - export SECRET_KEY=${{ secrets.SECRET_KEY }} - export JWT_SECRET_DEFAULT_VALUE=${{ secrets.JWT_SECRET_DEFAULT_VALUE }} - export JWT_HEADER=${{ secrets.JWT_HEADER }} - - # Gradle 빌드 - - name: Build and package the application - run: ./gradlew clean build -x test - - - - # Elastic Beanstalk 배포 - - name: Deploy to Elastic Beanstalk - run: | - eb init -p "Docker" ${{ env.APPLICATION_NAME }} --region ${{ env.AWS_REGION }} - eb use ${{ env.ENVIRONMENT_NAME }} - eb deploy --staged \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 89150d20..e69de29b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,30 +0,0 @@ -# 단계 1: 빌드 단계 (Gradle 빌드) -FROM openjdk:17-jdk-slim AS build -WORKDIR /app - -# 필요한 파일 복사 -COPY gradlew ./gradlew -COPY gradle/ ./gradle -COPY build.gradle settings.gradle ./ - -# gradlew 실행 권한 추가 및 의존성 설치 -RUN chmod +x gradlew -RUN ./gradlew dependencies --no-daemon - -# 소스 코드 복사 및 빌드 -COPY . . -RUN ./gradlew clean build -x test --no-daemon - -RUN ls -la build/libs - -# 단계 2: 실행 단계 (빌드된 JAR 파일 실행) -FROM openjdk:17-jdk-slim -WORKDIR /app - -COPY --from=build /app/build/libs/*.jar app.jar - -# 포트 노출 -EXPOSE 8081 - -# JAR 파일 실행 -ENTRYPOINT ["java", "-jar", "app.jar", "--server.port=8081"] \ No newline at end of file diff --git a/README.md b/README.md index 45d78aff..1a4c98d3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ # Final_Backend 파이널 백엔드 프로젝트 -http://localhost:7777/swagger-ui/index.html +
+http://localhost:8080/swagger-ui/index.html +
+http://motive-backend-env.eba-n6hhmwaa.ap-northeast-2.elasticbeanstalk.com/swagger-ui/index.html diff --git a/build.gradle b/build.gradle index 24a2496f..49e312ff 100644 --- a/build.gradle +++ b/build.gradle @@ -23,12 +23,15 @@ repositories { mavenCentral() } + dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' testImplementation 'org.springframework.security:spring-security-test' testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.3' compileOnly 'org.projectlombok:lombok' @@ -43,6 +46,8 @@ dependencies { //swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' + + // modelmapper implementation 'org.modelmapper:modelmapper:3.2.0' // jwt 토큰 라이브러리 추가 @@ -53,9 +58,23 @@ dependencies { // redis implementation 'org.springframework.boot:spring-boot-starter-data-redis' + //cache + implementation 'org.springframework.boot:spring-boot-starter-cache' + //hibernate core implementation 'org.hibernate.orm:hibernate-core:6.5.2.Final' + // excel을 위한 poi 라이브러리 + implementation 'org.apache.poi:poi-ooxml:5.2.2' + implementation 'org.apache.poi:poi:5.2.2' + + // S3 + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' + + // SMTP + implementation 'org.springframework.boot:spring-boot-starter-mail' + implementation 'ognl:ognl:3.2.20' + } tasks.named('test') { diff --git a/docker-compose.yml b/docker-compose.yml index 90d751dc..e69de29b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,29 +0,0 @@ -version: '3.8' - -services: - backend: - build: - context: . - dockerfile: Dockerfile - container_name: backend - environment: - - SPRING_PROFILES_ACTIVE=prod - expose: - - "8081" - networks: - - app-network - - nginx: - build: - context: ./nginx - dockerfile: Dockerfile - container_name: nginx - depends_on: - - backend - ports: - - "80:80" # Nginx가 8080 포트로 내부 백엔드에 접근 - networks: - - app-network -networks: - app-network: - driver: bridge diff --git a/nginx/Dockerfile b/nginx/Dockerfile index 8b8e0c7b..e69de29b 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -1,5 +0,0 @@ -FROM nginx:alpine - -# Nginx 설정 파일 복사 -COPY default.conf /etc/nginx/conf.d/default.conf -RUN ls -la /etc/nginx/conf.d/ # 설정 파일 복사 확인 diff --git a/nginx/default.conf b/nginx/default.conf index d4d08e66..e69de29b 100644 --- a/nginx/default.conf +++ b/nginx/default.conf @@ -1,18 +0,0 @@ -upstream backend { - server backend:8081; # 백엔드 컨테이너의 포트를 8080으로 수정 -} - -server { - listen 80; - - location / { - proxy_pass http://backend; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Connection "keep-alive"; - proxy_cache_bypass $http_upgrade; - } -} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/FinalBackendApplication.java b/src/main/java/stanl_2/final_backend/FinalBackendApplication.java index 5f0cb0ad..c2da41b7 100644 --- a/src/main/java/stanl_2/final_backend/FinalBackendApplication.java +++ b/src/main/java/stanl_2/final_backend/FinalBackendApplication.java @@ -2,7 +2,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; +@EnableScheduling @SpringBootApplication public class FinalBackendApplication { diff --git a/src/main/java/stanl_2/final_backend/domain/A_sample/command/application/controller/SampleController.java b/src/main/java/stanl_2/final_backend/domain/A_sample/command/application/controller/SampleController.java index b5eb399b..602b5973 100644 --- a/src/main/java/stanl_2/final_backend/domain/A_sample/command/application/controller/SampleController.java +++ b/src/main/java/stanl_2/final_backend/domain/A_sample/command/application/controller/SampleController.java @@ -5,15 +5,19 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import stanl_2.final_backend.domain.A_sample.command.application.dto.request.SampleRegistRequestDTO; -import stanl_2.final_backend.domain.A_sample.command.application.dto.request.SampleModifyRequestDTO; -import stanl_2.final_backend.domain.A_sample.command.application.dto.response.SampleModifyResponseDTO; +import org.springframework.web.multipart.MultipartFile; +import stanl_2.final_backend.domain.A_sample.command.application.dto.SampleRegistDTO; +import stanl_2.final_backend.domain.A_sample.command.application.dto.SampleModifyDTO; import stanl_2.final_backend.domain.A_sample.command.application.service.SampleCommandService; -import stanl_2.final_backend.domain.A_sample.common.response.ResponseMessage; +import stanl_2.final_backend.domain.A_sample.common.response.SampleResponseMessage; +import java.security.Principal; + +@Slf4j @RestController("commandSampleController") @RequestMapping("/api/v1/sample") public class SampleController { @@ -36,20 +40,47 @@ public SampleController(SampleCommandService sampleCommandService) { @Operation(summary = "샘플 요청 테스트") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "성공", - content = {@Content(schema = @Schema(implementation = ResponseMessage.class))}) + content = {@Content(schema = @Schema(implementation = SampleResponseMessage.class))}) }) @PostMapping("") - public ResponseEntity postTest(@RequestBody SampleRegistRequestDTO sampleRegistRequestDTO) { + public ResponseEntity postTest(@RequestBody SampleRegistDTO sampleRegistRequestDTO, + Principal principal) { + + + log.info("현재 접속한 회원정보(MEM_LOGIN_ID)"); + log.info(principal.getName()); sampleCommandService.registerSample(sampleRegistRequestDTO); - return ResponseEntity.ok(ResponseMessage.builder() + return ResponseEntity.ok(SampleResponseMessage.builder() .httpStatus(200) .msg("성공") .result(null) .build()); } + @Operation(summary = "샘플 파일 요청 테스트") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = SampleResponseMessage.class))}) + }) + @PostMapping("/file") + public ResponseEntity postTestFile(@RequestPart("dto") SampleRegistDTO sampleRegistRequestDTO, + Principal principal, + @RequestPart("file") MultipartFile imageUrl) { + + + log.info("현재 접속한 회원정보(MEM_LOGIN_ID)"); + log.info(principal.getName()); + sampleCommandService.registerSampleFile(sampleRegistRequestDTO, imageUrl); + + return ResponseEntity.ok(SampleResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(null) + .build()); + } + /** * [PUT] http://localhost:7777/api/v1/sample?mem_id=SAM_000001 * Request @@ -60,19 +91,23 @@ public ResponseEntity postTest(@RequestBody SampleRegistRequest @Operation(summary = "샘플 수정 테스트") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "성공", - content = {@Content(schema = @Schema(implementation = ResponseMessage.class))}) + content = {@Content(schema = @Schema(implementation = SampleResponseMessage.class))}) }) @PutMapping("{id}") - public ResponseEntity putTest(@PathVariable String id, - @RequestBody SampleModifyRequestDTO sampleModifyRequestDTO) { + public ResponseEntity putTest(@PathVariable String id, + @RequestBody SampleModifyDTO sampleModifyRequestDTO, + Principal principal) { + + log.info("현재 접속한 회원정보(MEM_LOGIN_ID)"); + log.info(principal.getName()); sampleModifyRequestDTO.setId(id); - SampleModifyResponseDTO sampleModifyResponseDTO = sampleCommandService.modifySample(id, sampleModifyRequestDTO); + SampleModifyDTO sampleModifyDTO = sampleCommandService.modifySample(id, sampleModifyRequestDTO); - return ResponseEntity.ok(ResponseMessage.builder() + return ResponseEntity.ok(SampleResponseMessage.builder() .httpStatus(200) .msg("성공") - .result(sampleModifyResponseDTO) + .result(sampleModifyDTO) .build()); } @@ -82,19 +117,22 @@ public ResponseEntity putTest(@PathVariable String id, @Operation(summary = "샘플 삭제 테스트") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "성공", - content = {@Content(schema = @Schema(implementation = ResponseMessage.class))}) + content = {@Content(schema = @Schema(implementation = SampleResponseMessage.class))}) }) @DeleteMapping("{id}") - public ResponseEntity deleteTest(@PathVariable String id) { + public ResponseEntity deleteTest(@PathVariable String id, + Principal principal) { + + log.info("현재 접속한 회원정보(MEM_LOGIN_ID)"); + log.info(principal.getName()); sampleCommandService.deleteSample(id); - return ResponseEntity.ok(ResponseMessage.builder() + return ResponseEntity.ok(SampleResponseMessage.builder() .httpStatus(200) .msg("성공") .result(null) .build()); } - } diff --git a/src/main/java/stanl_2/final_backend/domain/A_sample/command/application/dto/request/SampleModifyRequestDTO.java b/src/main/java/stanl_2/final_backend/domain/A_sample/command/application/dto/SampleModifyDTO.java similarity index 77% rename from src/main/java/stanl_2/final_backend/domain/A_sample/command/application/dto/request/SampleModifyRequestDTO.java rename to src/main/java/stanl_2/final_backend/domain/A_sample/command/application/dto/SampleModifyDTO.java index ca49903d..57e4d857 100644 --- a/src/main/java/stanl_2/final_backend/domain/A_sample/command/application/dto/request/SampleModifyRequestDTO.java +++ b/src/main/java/stanl_2/final_backend/domain/A_sample/command/application/dto/SampleModifyDTO.java @@ -1,4 +1,4 @@ -package stanl_2.final_backend.domain.A_sample.command.application.dto.request; +package stanl_2.final_backend.domain.A_sample.command.application.dto; import lombok.*; @@ -6,8 +6,7 @@ @NoArgsConstructor @Setter @Getter -@ToString -public class SampleModifyRequestDTO { +public class SampleModifyDTO { private String id; private String name; private Integer num; diff --git a/src/main/java/stanl_2/final_backend/domain/A_sample/command/application/dto/request/SampleRegistRequestDTO.java b/src/main/java/stanl_2/final_backend/domain/A_sample/command/application/dto/SampleRegistDTO.java similarity index 76% rename from src/main/java/stanl_2/final_backend/domain/A_sample/command/application/dto/request/SampleRegistRequestDTO.java rename to src/main/java/stanl_2/final_backend/domain/A_sample/command/application/dto/SampleRegistDTO.java index cefe0f79..042f5d53 100644 --- a/src/main/java/stanl_2/final_backend/domain/A_sample/command/application/dto/request/SampleRegistRequestDTO.java +++ b/src/main/java/stanl_2/final_backend/domain/A_sample/command/application/dto/SampleRegistDTO.java @@ -1,4 +1,4 @@ -package stanl_2.final_backend.domain.A_sample.command.application.dto.request; +package stanl_2.final_backend.domain.A_sample.command.application.dto; import lombok.*; @@ -6,9 +6,9 @@ @NoArgsConstructor @Setter @Getter -@ToString -public class SampleRegistRequestDTO { +public class SampleRegistDTO { private String id; private String name; private Integer num; + private String imageUrl; } diff --git a/src/main/java/stanl_2/final_backend/domain/A_sample/command/application/dto/response/SampleModifyResponseDTO.java b/src/main/java/stanl_2/final_backend/domain/A_sample/command/application/dto/response/SampleModifyResponseDTO.java deleted file mode 100644 index f8ddc296..00000000 --- a/src/main/java/stanl_2/final_backend/domain/A_sample/command/application/dto/response/SampleModifyResponseDTO.java +++ /dev/null @@ -1,13 +0,0 @@ -package stanl_2.final_backend.domain.A_sample.command.application.dto.response; - -import lombok.*; - -@AllArgsConstructor -@NoArgsConstructor -@Setter -@Getter -@ToString -public class SampleModifyResponseDTO { - private String name; - private Integer num; -} diff --git a/src/main/java/stanl_2/final_backend/domain/A_sample/command/application/service/SampleCommandService.java b/src/main/java/stanl_2/final_backend/domain/A_sample/command/application/service/SampleCommandService.java index 2e08f5f1..7a2976d1 100644 --- a/src/main/java/stanl_2/final_backend/domain/A_sample/command/application/service/SampleCommandService.java +++ b/src/main/java/stanl_2/final_backend/domain/A_sample/command/application/service/SampleCommandService.java @@ -1,13 +1,15 @@ package stanl_2.final_backend.domain.A_sample.command.application.service; -import stanl_2.final_backend.domain.A_sample.command.application.dto.request.SampleRegistRequestDTO; -import stanl_2.final_backend.domain.A_sample.command.application.dto.request.SampleModifyRequestDTO; -import stanl_2.final_backend.domain.A_sample.command.application.dto.response.SampleModifyResponseDTO; +import org.springframework.web.multipart.MultipartFile; +import stanl_2.final_backend.domain.A_sample.command.application.dto.SampleRegistDTO; +import stanl_2.final_backend.domain.A_sample.command.application.dto.SampleModifyDTO; public interface SampleCommandService { - void registerSample(SampleRegistRequestDTO sampleRegistRequestDTO); + void registerSample(SampleRegistDTO sampleRegistRequestDTO); - SampleModifyResponseDTO modifySample(String id, SampleModifyRequestDTO sampleModifyRequestDTO); + SampleModifyDTO modifySample(String id, SampleModifyDTO sampleModifyDTO); void deleteSample(String id); + + void registerSampleFile(SampleRegistDTO sampleRegistRequestDTO, MultipartFile imageUrl); } diff --git a/src/main/java/stanl_2/final_backend/domain/A_sample/command/domain/aggregate/entity/Sample.java b/src/main/java/stanl_2/final_backend/domain/A_sample/command/domain/aggregate/entity/Sample.java index 33b0696b..dd6b58e9 100644 --- a/src/main/java/stanl_2/final_backend/domain/A_sample/command/domain/aggregate/entity/Sample.java +++ b/src/main/java/stanl_2/final_backend/domain/A_sample/command/domain/aggregate/entity/Sample.java @@ -11,6 +11,7 @@ import java.sql.Timestamp; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; @NoArgsConstructor @AllArgsConstructor @@ -36,35 +37,37 @@ public class Sample { private Integer num; @Column(name = "CREATED_AT", nullable = false, updatable = false) - private Timestamp createdAt; + private String createdAt; @Column(name = "UPDATED_AT", nullable = false) - private Timestamp updatedAt; + private String updatedAt; @Column(name = "DELETED_AT") - private Timestamp deletedAt; + private String deletedAt; @Column(name = "ACTIVE") private Boolean active = true; + @Column(name = "IMAGE_URL") + private String imageUrl; + /* 설명. updatedAt 자동화 */ // Insert 되기 전에 실행 @PrePersist private void prePersist() { - this.createdAt = getCurrentTimestamp(); + this.createdAt = getCurrentTime(); this.updatedAt = this.createdAt; } // Update 되기 전에 실행 @PreUpdate private void preUpdate() { - this.updatedAt = getCurrentTimestamp(); + this.updatedAt = getCurrentTime(); } - private Timestamp getCurrentTimestamp() { + private String getCurrentTime() { ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); - return Timestamp.from(nowKst.toInstant()); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } - } diff --git a/src/main/java/stanl_2/final_backend/domain/A_sample/command/domain/repository/SampleRepository.java b/src/main/java/stanl_2/final_backend/domain/A_sample/command/domain/repository/SampleRepository.java index c6bb2636..5299a8a9 100644 --- a/src/main/java/stanl_2/final_backend/domain/A_sample/command/domain/repository/SampleRepository.java +++ b/src/main/java/stanl_2/final_backend/domain/A_sample/command/domain/repository/SampleRepository.java @@ -1,7 +1,9 @@ package stanl_2.final_backend.domain.A_sample.command.domain.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; import stanl_2.final_backend.domain.A_sample.command.domain.aggregate.entity.Sample; +@Repository public interface SampleRepository extends JpaRepository { } diff --git a/src/main/java/stanl_2/final_backend/domain/A_sample/command/domain/service/SampleCommandServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/A_sample/command/domain/service/SampleCommandServiceImpl.java index ac1a2886..5fab444e 100644 --- a/src/main/java/stanl_2/final_backend/domain/A_sample/command/domain/service/SampleCommandServiceImpl.java +++ b/src/main/java/stanl_2/final_backend/domain/A_sample/command/domain/service/SampleCommandServiceImpl.java @@ -4,52 +4,65 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import stanl_2.final_backend.domain.A_sample.command.application.dto.request.SampleRegistRequestDTO; -import stanl_2.final_backend.domain.A_sample.command.application.dto.request.SampleModifyRequestDTO; -import stanl_2.final_backend.domain.A_sample.command.application.dto.response.SampleModifyResponseDTO; +import org.springframework.web.multipart.MultipartFile; +import stanl_2.final_backend.domain.A_sample.command.application.dto.SampleRegistDTO; +import stanl_2.final_backend.domain.A_sample.command.application.dto.SampleModifyDTO; import stanl_2.final_backend.domain.A_sample.command.application.service.SampleCommandService; import stanl_2.final_backend.domain.A_sample.command.domain.aggregate.entity.Sample; import stanl_2.final_backend.domain.A_sample.command.domain.repository.SampleRepository; -import stanl_2.final_backend.domain.A_sample.common.exception.CommonException; -import stanl_2.final_backend.domain.A_sample.common.exception.ErrorCode; -import stanl_2.final_backend.domain.A_sample.query.dto.SampleDTO; +import stanl_2.final_backend.domain.A_sample.common.exception.SampleCommonException; +import stanl_2.final_backend.domain.A_sample.common.exception.SampleErrorCode; +import stanl_2.final_backend.domain.s3.command.application.service.S3FileService; -import java.sql.Timestamp; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; @Service("commandSampleService") public class SampleCommandServiceImpl implements SampleCommandService { private final SampleRepository sampleRepository; private final ModelMapper modelMapper; + private final S3FileService s3FileService; @Autowired - public SampleCommandServiceImpl(SampleRepository sampleRepository, ModelMapper modelMapper) { + public SampleCommandServiceImpl(SampleRepository sampleRepository, ModelMapper modelMapper, S3FileService s3FileService) { this.sampleRepository = sampleRepository; this.modelMapper = modelMapper; + this.s3FileService = s3FileService; } - private Timestamp getCurrentTimestamp() { + private String getCurrentTimestamp() { ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); - return Timestamp.from(nowKst.toInstant()); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } @Override @Transactional - public void registerSample(SampleRegistRequestDTO sampleRegistRequestDTO) { + public void registerSample(SampleRegistDTO sampleRegistRequestDTO) { Sample newSample = modelMapper.map(sampleRegistRequestDTO, Sample.class); sampleRepository.save(newSample); } + @Override + public void registerSampleFile(SampleRegistDTO sampleRegistRequestDTO, MultipartFile imageUrl) { + Sample newSample = modelMapper.map(sampleRegistRequestDTO, Sample.class); + + + // s3 사용 + newSample.setImageUrl(s3FileService.uploadOneFile(imageUrl)); + + sampleRepository.save(newSample); + } + @Override @Transactional - public SampleModifyResponseDTO modifySample(String id, SampleModifyRequestDTO sampleModifyRequestDTO) { + public SampleModifyDTO modifySample(String id, SampleModifyDTO sampleModifyRequestDTO) { Sample sample = sampleRepository.findById(id) - .orElseThrow(() -> new CommonException(ErrorCode.SAMPLE_NOT_FOUND)); + .orElseThrow(() -> new SampleCommonException(SampleErrorCode.SAMPLE_NOT_FOUND)); sampleModifyRequestDTO.setId(id); Sample updateSample = modelMapper.map(sampleModifyRequestDTO, Sample.class); @@ -58,7 +71,7 @@ public SampleModifyResponseDTO modifySample(String id, SampleModifyRequestDTO sa sampleRepository.save(updateSample); - SampleModifyResponseDTO sampleModifyResponseDTO= modelMapper.map(updateSample, SampleModifyResponseDTO.class); + SampleModifyDTO sampleModifyResponseDTO= modelMapper.map(updateSample, SampleModifyDTO.class); return sampleModifyResponseDTO; } @@ -68,11 +81,12 @@ public SampleModifyResponseDTO modifySample(String id, SampleModifyRequestDTO sa public void deleteSample(String id) { Sample sample = sampleRepository.findById(id) - .orElseThrow(() -> new CommonException(ErrorCode.SAMPLE_NOT_FOUND)); + .orElseThrow(() -> new SampleCommonException(SampleErrorCode.SAMPLE_NOT_FOUND)); sample.setActive(false); sample.setDeletedAt(getCurrentTimestamp()); sampleRepository.save(sample); } + } diff --git a/src/main/java/stanl_2/final_backend/domain/A_sample/common/exception/CommonException.java b/src/main/java/stanl_2/final_backend/domain/A_sample/common/exception/SampleCommonException.java similarity index 62% rename from src/main/java/stanl_2/final_backend/domain/A_sample/common/exception/CommonException.java rename to src/main/java/stanl_2/final_backend/domain/A_sample/common/exception/SampleCommonException.java index 2e2becf4..3777925c 100644 --- a/src/main/java/stanl_2/final_backend/domain/A_sample/common/exception/CommonException.java +++ b/src/main/java/stanl_2/final_backend/domain/A_sample/common/exception/SampleCommonException.java @@ -5,12 +5,12 @@ @Getter @RequiredArgsConstructor -public class CommonException extends RuntimeException { - private final ErrorCode errorCode; +public class SampleCommonException extends RuntimeException { + private final SampleErrorCode sampleErrorCode; // 에러 발생시 ErroCode 별 메시지 @Override public String getMessage() { - return this.errorCode.getMsg(); + return this.sampleErrorCode.getMsg(); } } diff --git a/src/main/java/stanl_2/final_backend/domain/A_sample/common/exception/ErrorCode.java b/src/main/java/stanl_2/final_backend/domain/A_sample/common/exception/SampleErrorCode.java similarity index 94% rename from src/main/java/stanl_2/final_backend/domain/A_sample/common/exception/ErrorCode.java rename to src/main/java/stanl_2/final_backend/domain/A_sample/common/exception/SampleErrorCode.java index c03f67bc..5706689d 100644 --- a/src/main/java/stanl_2/final_backend/domain/A_sample/common/exception/ErrorCode.java +++ b/src/main/java/stanl_2/final_backend/domain/A_sample/common/exception/SampleErrorCode.java @@ -6,7 +6,7 @@ @Getter @AllArgsConstructor -public enum ErrorCode { +public enum SampleErrorCode { /** * 400(Bad Request) @@ -39,10 +39,8 @@ public enum ErrorCode { * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. */ SAMPLE_NOT_FOUND(404001, HttpStatus.NOT_FOUND, "sample 데이터를 찾지 못했습니다"), - - + CENTER_NOT_FOUND(404002, HttpStatus.NOT_FOUND, "center 데이터를 찾지 못했습니다."), /** - * 500(Internal Server Error) * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. */ INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); diff --git a/src/main/java/stanl_2/final_backend/domain/A_sample/common/exception/SampleExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/A_sample/common/exception/SampleExceptionResponse.java new file mode 100644 index 00000000..cc5d7648 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/A_sample/common/exception/SampleExceptionResponse.java @@ -0,0 +1,22 @@ +package stanl_2.final_backend.domain.A_sample.common.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class SampleExceptionResponse { + private final Integer code; + private final String msg; + private final HttpStatus httpStatus; + + public SampleExceptionResponse(SampleErrorCode sampleErrorCode) { + this.code = sampleErrorCode.getCode(); + this.msg = sampleErrorCode.getMsg(); + this.httpStatus = sampleErrorCode.getHttpStatus(); + } + + public static SampleExceptionResponse of(SampleErrorCode sampleErrorCode) { + return new SampleExceptionResponse(sampleErrorCode); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/A_sample/common/response/ResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/A_sample/common/response/SampleResponseMessage.java similarity index 74% rename from src/main/java/stanl_2/final_backend/domain/A_sample/common/response/ResponseMessage.java rename to src/main/java/stanl_2/final_backend/domain/A_sample/common/response/SampleResponseMessage.java index 8cd45bcd..f1fb01b7 100644 --- a/src/main/java/stanl_2/final_backend/domain/A_sample/common/response/ResponseMessage.java +++ b/src/main/java/stanl_2/final_backend/domain/A_sample/common/response/SampleResponseMessage.java @@ -7,8 +7,8 @@ @Builder @Getter @Setter -public class ResponseMessage { - private int httpStatus; +public class SampleResponseMessage { + private Integer httpStatus; private String msg; private Object result; } \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/A_sample/query/controller/SampleController.java b/src/main/java/stanl_2/final_backend/domain/A_sample/query/controller/SampleController.java index fc0177cb..f0228da6 100644 --- a/src/main/java/stanl_2/final_backend/domain/A_sample/query/controller/SampleController.java +++ b/src/main/java/stanl_2/final_backend/domain/A_sample/query/controller/SampleController.java @@ -5,22 +5,27 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import stanl_2.final_backend.domain.A_sample.common.response.ResponseMessage; +import stanl_2.final_backend.domain.A_sample.common.response.SampleResponseMessage; import stanl_2.final_backend.domain.A_sample.query.dto.SampleDTO; -import stanl_2.final_backend.domain.A_sample.query.service.SampleService; +import stanl_2.final_backend.domain.A_sample.query.service.SampleQueryService; +import java.security.Principal; + +@Slf4j @RestController(value = "querySampleController") @RequestMapping("/api/v1/sample") public class SampleController { - private final SampleService sampleService; + private final SampleQueryService sampleQueryService; @Autowired - public SampleController(SampleService sampleService) { - this.sampleService = sampleService; + public SampleController(SampleQueryService sampleQueryService) { + this.sampleQueryService = sampleQueryService; } /** @@ -29,16 +34,20 @@ public SampleController(SampleService sampleService) { @Operation(summary = "샘플 조회 테스트") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "성공", - content = {@Content(schema = @Schema(implementation = ResponseMessage.class))}), + content = {@Content(schema = @Schema(implementation = SampleResponseMessage.class))}), @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", content = @Content(mediaType = "application/json")) }) @GetMapping("{id}") - public ResponseEntity getTest(@PathVariable String id){ + public ResponseEntity getTest(@PathVariable String id, + Principal principal){ + + log.info("현재 접속한 회원정보(MEM_LOGIN_ID)"); + log.info(principal.getName()); - SampleDTO sampleDTO = sampleService.selectSampleInfo(id); + SampleDTO sampleDTO = sampleQueryService.selectSampleInfo(id); - return ResponseEntity.ok(ResponseMessage.builder() + return ResponseEntity.ok(SampleResponseMessage.builder() .httpStatus(200) .msg("성공") .result(sampleDTO) @@ -51,20 +60,32 @@ public ResponseEntity getTest(@PathVariable String id){ @Operation(summary = "샘플 상세 조회 테스트") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "성공", - content = {@Content(schema = @Schema(implementation = ResponseMessage.class))}), + content = {@Content(schema = @Schema(implementation = SampleResponseMessage.class))}), @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", content = @Content(mediaType = "application/json")) }) @GetMapping("/detail/{id}") - public ResponseEntity getDetailTest(@PathVariable String id) { + public ResponseEntity getDetailTest(@PathVariable String id) { - String name = sampleService.selectSampleName(id); + String name = sampleQueryService.selectSampleName(id); - return ResponseEntity.ok(ResponseMessage.builder() + return ResponseEntity.ok(SampleResponseMessage.builder() .httpStatus(200) .msg("성공") .result(name) .build()); } + @Operation(summary = "샘플 엑셀 다운 테스트") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "샘플 엑설 다운 테스트 성공", + content = {@Content(schema = @Schema(implementation = SampleResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/excel") + public void exportSample(HttpServletResponse response){ + + sampleQueryService.exportSamplesToExcel(response); + } } diff --git a/src/main/java/stanl_2/final_backend/domain/A_sample/query/dto/SampleDTO.java b/src/main/java/stanl_2/final_backend/domain/A_sample/query/dto/SampleDTO.java index a3732d58..ae0985c1 100644 --- a/src/main/java/stanl_2/final_backend/domain/A_sample/query/dto/SampleDTO.java +++ b/src/main/java/stanl_2/final_backend/domain/A_sample/query/dto/SampleDTO.java @@ -1,16 +1,13 @@ package stanl_2.final_backend.domain.A_sample.query.dto; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.ToString; +import lombok.*; import java.sql.Timestamp; @NoArgsConstructor @AllArgsConstructor @Getter -@ToString +@Setter public class SampleDTO { private String id; private String name; diff --git a/src/main/java/stanl_2/final_backend/domain/A_sample/query/dto/SampleExcelDownload.java b/src/main/java/stanl_2/final_backend/domain/A_sample/query/dto/SampleExcelDownload.java new file mode 100644 index 00000000..458ddb4e --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/A_sample/query/dto/SampleExcelDownload.java @@ -0,0 +1,22 @@ +package stanl_2.final_backend.domain.A_sample.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import stanl_2.final_backend.global.excel.ExcelColumnName; + +@Getter +@AllArgsConstructor +public class SampleExcelDownload { + + @ExcelColumnName(name = "이름") + private String name; + + @ExcelColumnName(name = "갯수") + private String num; + + @ExcelColumnName(name = "판매상태") + private Boolean active; + + @ExcelColumnName(name = "생성일자") + private String createdAt; +} diff --git a/src/main/java/stanl_2/final_backend/domain/A_sample/query/repository/SampleMapper.java b/src/main/java/stanl_2/final_backend/domain/A_sample/query/repository/SampleMapper.java index 5ccb65cd..a10bde45 100644 --- a/src/main/java/stanl_2/final_backend/domain/A_sample/query/repository/SampleMapper.java +++ b/src/main/java/stanl_2/final_backend/domain/A_sample/query/repository/SampleMapper.java @@ -3,10 +3,15 @@ import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import stanl_2.final_backend.domain.A_sample.query.dto.SampleDTO; +import stanl_2.final_backend.domain.A_sample.query.dto.SampleExcelDownload; + +import java.util.List; @Mapper public interface SampleMapper { String selectNameById(@Param("id") String id); SampleDTO selectById(@Param("id") String id); + + List findSamplesForExcel(); } diff --git a/src/main/java/stanl_2/final_backend/domain/A_sample/query/service/SampleService.java b/src/main/java/stanl_2/final_backend/domain/A_sample/query/service/SampleQueryService.java similarity index 59% rename from src/main/java/stanl_2/final_backend/domain/A_sample/query/service/SampleService.java rename to src/main/java/stanl_2/final_backend/domain/A_sample/query/service/SampleQueryService.java index a53ea073..c4538594 100644 --- a/src/main/java/stanl_2/final_backend/domain/A_sample/query/service/SampleService.java +++ b/src/main/java/stanl_2/final_backend/domain/A_sample/query/service/SampleQueryService.java @@ -1,9 +1,12 @@ package stanl_2.final_backend.domain.A_sample.query.service; +import jakarta.servlet.http.HttpServletResponse; import stanl_2.final_backend.domain.A_sample.query.dto.SampleDTO; -public interface SampleService { +public interface SampleQueryService { String selectSampleName(String id); SampleDTO selectSampleInfo(String id); + + void exportSamplesToExcel(HttpServletResponse response); } diff --git a/src/main/java/stanl_2/final_backend/domain/A_sample/query/service/SampleQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/A_sample/query/service/SampleQueryServiceImpl.java new file mode 100644 index 00000000..90d7103f --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/A_sample/query/service/SampleQueryServiceImpl.java @@ -0,0 +1,65 @@ +package stanl_2.final_backend.domain.A_sample.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.A_sample.common.exception.SampleCommonException; +import stanl_2.final_backend.domain.A_sample.common.exception.SampleErrorCode; +import stanl_2.final_backend.domain.A_sample.query.dto.SampleDTO; +import stanl_2.final_backend.domain.A_sample.query.dto.SampleExcelDownload; +import stanl_2.final_backend.domain.A_sample.query.repository.SampleMapper; +import stanl_2.final_backend.global.excel.ExcelUtilsV1; + +import java.util.List; + +@Slf4j +@Service +public class SampleQueryServiceImpl implements SampleQueryService { + + private final SampleMapper sampleMapper; + private final ExcelUtilsV1 excelUtilsV1; + + @Autowired + public SampleQueryServiceImpl(SampleMapper sampleMapper, ExcelUtilsV1 excelUtilsV1) { + this.sampleMapper = sampleMapper; + this.excelUtilsV1 = excelUtilsV1; + } + + @Override + @Transactional(readOnly = true) + public String selectSampleName(String id) { + + String name = sampleMapper.selectNameById(id);; + + if(name == null){ + throw new SampleCommonException(SampleErrorCode.SAMPLE_NOT_FOUND); + } + + return name; + } + + @Override + @Transactional(readOnly = true) + public SampleDTO selectSampleInfo(String id) { + + SampleDTO sampleDTO = sampleMapper.selectById(id); + + if(sampleDTO == null){ + throw new SampleCommonException(SampleErrorCode.SAMPLE_NOT_FOUND); + } + + return sampleDTO; + } + + @Override + public void exportSamplesToExcel(HttpServletResponse response) { + + List sampleList = sampleMapper.findSamplesForExcel(); + + excelUtilsV1.download(SampleExcelDownload.class, sampleList, "sampleExcel", response); + } + + +} diff --git a/src/main/java/stanl_2/final_backend/domain/A_sample/query/service/SampleServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/A_sample/query/service/SampleServiceImpl.java deleted file mode 100644 index 880be723..00000000 --- a/src/main/java/stanl_2/final_backend/domain/A_sample/query/service/SampleServiceImpl.java +++ /dev/null @@ -1,50 +0,0 @@ -package stanl_2.final_backend.domain.A_sample.query.service; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import stanl_2.final_backend.domain.A_sample.common.exception.CommonException; -import stanl_2.final_backend.domain.A_sample.common.exception.ErrorCode; -import stanl_2.final_backend.domain.A_sample.query.dto.SampleDTO; -import stanl_2.final_backend.domain.A_sample.query.repository.SampleMapper; - -@Slf4j -@Service(value = "querySampleService") -public class SampleServiceImpl implements SampleService{ - - private final SampleMapper sampleMapper; - - @Autowired - public SampleServiceImpl(SampleMapper sampleMapper) { - this.sampleMapper = sampleMapper; - } - - @Override - @Transactional(readOnly = true) - public String selectSampleName(String id) { - - String name = sampleMapper.selectNameById(id);; - - if(name == null){ - throw new CommonException(ErrorCode.SAMPLE_NOT_FOUND); - } - - return name; - } - - @Override - @Transactional(readOnly = true) - public SampleDTO selectSampleInfo(String id) { - - SampleDTO sampleDTO = sampleMapper.selectById(id); - - if(sampleDTO == null){ - throw new CommonException(ErrorCode.SAMPLE_NOT_FOUND); - } - - return sampleDTO; - } - - -} diff --git a/src/main/java/stanl_2/final_backend/domain/alarm/command/application/controller/AlarmController.java b/src/main/java/stanl_2/final_backend/domain/alarm/command/application/controller/AlarmController.java new file mode 100644 index 00000000..a6df8811 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/alarm/command/application/controller/AlarmController.java @@ -0,0 +1,69 @@ +package stanl_2.final_backend.domain.alarm.command.application.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; +import stanl_2.final_backend.domain.alarm.command.application.dto.AlarmRegistDTO; +import stanl_2.final_backend.domain.alarm.command.application.service.AlarmCommandService; +import stanl_2.final_backend.domain.alarm.common.response.AlarmResponseMessage; +import stanl_2.final_backend.domain.schedule.common.response.ScheduleResponseMessage; + +import java.security.Principal; + +@RestController("commandAlarmController") +@RequestMapping("/api/v1/alarm") +public class AlarmController { + + private final AlarmCommandService alarmCommandService; + + @Autowired + public AlarmController(AlarmCommandService alarmCommandService) { + this.alarmCommandService = alarmCommandService; + } + + @Operation(summary = "sse 연결") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "sse 연결 성공", + content = {@Content(schema = @Schema(implementation = ScheduleResponseMessage.class))}) + }) + @GetMapping(value= "/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public ResponseEntity subscribe(Principal principal, + @RequestHeader(value = "Last-Event-ID", required = false, + defaultValue = "") String lastEventId, + HttpServletResponse response){ + + String memberLoginId = principal.getName(); + + AlarmRegistDTO alarmRegistDTO = new AlarmRegistDTO(); + alarmRegistDTO.setMemberLoginId(memberLoginId); + alarmRegistDTO.setLastEventId(lastEventId); + + return ResponseEntity.ok(alarmCommandService.subscribe(alarmRegistDTO, response)); + } + + + @Operation(summary = "회원 알림 읽음 처리") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "회원 알림 읽음 처리 완료", + content = {@Content(schema = @Schema(implementation = ScheduleResponseMessage.class))}) + }) + @PutMapping("{alarmId}") + public ResponseEntity updateReadStatus(@PathVariable String alarmId){ + + Boolean answer = alarmCommandService.updateReadStatus(alarmId); + + return ResponseEntity.ok(AlarmResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(answer) + .build()); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/alarm/command/application/dto/AlarmRegistDTO.java b/src/main/java/stanl_2/final_backend/domain/alarm/command/application/dto/AlarmRegistDTO.java new file mode 100644 index 00000000..b8aca5bb --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/alarm/command/application/dto/AlarmRegistDTO.java @@ -0,0 +1,15 @@ +package stanl_2.final_backend.domain.alarm.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class AlarmRegistDTO { + + private String memberId; + private String memberLoginId; + private String lastEventId; + +} diff --git a/src/main/java/stanl_2/final_backend/domain/alarm/command/application/service/AlarmCommandService.java b/src/main/java/stanl_2/final_backend/domain/alarm/command/application/service/AlarmCommandService.java new file mode 100644 index 00000000..de0169b6 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/alarm/command/application/service/AlarmCommandService.java @@ -0,0 +1,37 @@ +package stanl_2.final_backend.domain.alarm.command.application.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; +import stanl_2.final_backend.domain.alarm.command.application.dto.AlarmRegistDTO; +import stanl_2.final_backend.domain.alarm.command.domain.aggregate.entity.Alarm; +import stanl_2.final_backend.domain.contract.command.application.dto.ContractAlarmDTO; +import stanl_2.final_backend.domain.contract.command.domain.aggregate.entity.Contract; +import stanl_2.final_backend.domain.notices.command.application.dto.NoticeAlarmDTO; +import stanl_2.final_backend.domain.order.command.application.dto.OrderAlarmDTO; +import stanl_2.final_backend.domain.order.command.domain.aggregate.entity.Order; +import stanl_2.final_backend.domain.purchase_order.command.application.dto.PurchaseOrderAlarmDTO; +import stanl_2.final_backend.domain.purchase_order.command.domain.aggregate.entity.PurchaseOrder; + +import java.security.GeneralSecurityException; + +public interface AlarmCommandService { + SseEmitter subscribe(AlarmRegistDTO alarmRegistDTO, HttpServletResponse response); + + void sendToClient(SseEmitter emitter, String emitterId, Object data); + + void send(String memberId, String adminId, String contentId, String message, String redirectUrl, String tag, + String type, String createdAt); + + Alarm createAlarm(String memberId, String adminId,String contentId, String message, String redirectUrl, String tag, + String type, String createdAt); + + void sendNoticeAlarm(NoticeAlarmDTO noticeAlarmDTO); + + Boolean updateReadStatus(String alarmId); + + void sendContractAlarm(ContractAlarmDTO contractAlarmDTO) throws GeneralSecurityException; + + void sendPurchaseOrderAlarm(PurchaseOrderAlarmDTO purchaseOrderAlarmDTO) throws GeneralSecurityException; + + void sendOrderAlarm(OrderAlarmDTO orderAlarmDTO) throws GeneralSecurityException; +} diff --git a/src/main/java/stanl_2/final_backend/domain/alarm/command/domain/aggregate/entity/Alarm.java b/src/main/java/stanl_2/final_backend/domain/alarm/command/domain/aggregate/entity/Alarm.java new file mode 100644 index 00000000..2d9c26da --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/alarm/command/domain/aggregate/entity/Alarm.java @@ -0,0 +1,57 @@ +package stanl_2.final_backend.domain.alarm.command.domain.aggregate.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + +@Entity +@Table(name = "TB_ALARM") +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class Alarm { + + @Id + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @Parameter(name = "prefix", value = "ALR") + ) + @Column(name = "ALR_ID", nullable = false) + private String alarmId; + + @Column(name = "ALR_MSG", nullable = false) + private String message; + + @Column(columnDefinition = "TEXT", name = "ALR_URL", nullable = false) + private String redirectUrl; + + @Column(name = "ALR_TYPE", nullable = false) + private String type = "NOTICE"; + + @Column(name = "ALR_TAG", nullable = false) + private String tag; + + @Column(name = "ALR_READ_STAT", nullable = false) + private Boolean readStatus; + + @Column(name = "CREATED_AT", nullable = false) + private String createdAt; + + @Column(name = "CONT_ID", nullable = false) + private String contentId; + + @Column(name = "ADMIN_ID", nullable = false) + private String adminId; + + @Column(name = "MEM_ID", nullable = false) + private String memberId; + + +} diff --git a/src/main/java/stanl_2/final_backend/domain/alarm/command/domain/repository/AlarmRepository.java b/src/main/java/stanl_2/final_backend/domain/alarm/command/domain/repository/AlarmRepository.java new file mode 100644 index 00000000..5421ce10 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/alarm/command/domain/repository/AlarmRepository.java @@ -0,0 +1,10 @@ +package stanl_2.final_backend.domain.alarm.command.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import stanl_2.final_backend.domain.alarm.command.domain.aggregate.entity.Alarm; + +@Repository +public interface AlarmRepository extends JpaRepository { + Alarm findByAlarmId(String alarmId); +} diff --git a/src/main/java/stanl_2/final_backend/domain/alarm/command/domain/repository/EmitterRepository.java b/src/main/java/stanl_2/final_backend/domain/alarm/command/domain/repository/EmitterRepository.java new file mode 100644 index 00000000..23ccda09 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/alarm/command/domain/repository/EmitterRepository.java @@ -0,0 +1,23 @@ +package stanl_2.final_backend.domain.alarm.command.domain.repository; + +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.util.Map; + +public interface EmitterRepository { + SseEmitter save(String emitterId, SseEmitter sseEmitter); + + void deleteAllByEmitterId(String emitterId); + + Map findAllEmitterStartWithByMemberId(String memberId); + + Map findAllEventCacheStartWithByMemberId(String memberId); + + void saveEventCache(String eventCacheId, Object event); + + void deleteAllEmitterStartWithMemberId(String memberId); + + void deleteAllEventCacheStartWithmemberId(String memberId); + + SseEmitter findEmitterByMemberId(String memberId); +} diff --git a/src/main/java/stanl_2/final_backend/domain/alarm/command/domain/repository/EmitterRepositoryImpl.java b/src/main/java/stanl_2/final_backend/domain/alarm/command/domain/repository/EmitterRepositoryImpl.java new file mode 100644 index 00000000..723e27be --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/alarm/command/domain/repository/EmitterRepositoryImpl.java @@ -0,0 +1,81 @@ +package stanl_2.final_backend.domain.alarm.command.domain.repository; + +import org.springframework.stereotype.Repository; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +@Repository +public class EmitterRepositoryImpl implements EmitterRepository{ + + private final Map emitters = new ConcurrentHashMap<>(); + private final Map eventCache = new ConcurrentHashMap<>(); + + @Override + public SseEmitter save(String emitterId, SseEmitter sseEmitter) { + + emitters.put(emitterId, sseEmitter); + return sseEmitter; + } + + @Override + public void deleteAllByEmitterId(String emitterId) { + emitters.forEach((key, emitter) -> { + if (key.startsWith(emitterId)) { + emitters.remove(key); + } + }); + } + + @Override + public Map findAllEmitterStartWithByMemberId(String memberId) { + return emitters.entrySet().stream() + .filter(entry -> entry.getKey().startsWith(memberId)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + @Override + public Map findAllEventCacheStartWithByMemberId(String memberId) { + return eventCache.entrySet().stream() + .filter(entry -> entry.getKey().startsWith(memberId)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + @Override + public void saveEventCache(String eventCacheId, Object event) { + eventCache.put(eventCacheId, event); + } + + @Override + public void deleteAllEmitterStartWithMemberId(String memberId) { + emitters.forEach( + (key, emitter) -> { + if (key.startsWith(memberId)){ + emitters.remove(key); + } + } + ); + } + + @Override + public void deleteAllEventCacheStartWithmemberId(String memberId) { + eventCache.forEach( + (key, emitter) -> { + if (key.startsWith(memberId)){ + eventCache.remove(key); + } + } + ); + } + + @Override + public SseEmitter findEmitterByMemberId(String memberId) { + return emitters.entrySet().stream() + .filter(entry -> entry.getKey().startsWith(memberId)) // memberId로 시작하는 키 검색 + .map(Map.Entry::getValue) + .findFirst() + .orElse(null); // 없으면 null 반환 + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/alarm/command/domain/service/AlarmCommandServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/alarm/command/domain/service/AlarmCommandServiceImpl.java new file mode 100644 index 00000000..a628d70d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/alarm/command/domain/service/AlarmCommandServiceImpl.java @@ -0,0 +1,312 @@ +package stanl_2.final_backend.domain.alarm.command.domain.service; + +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; +import stanl_2.final_backend.domain.alarm.command.application.dto.AlarmRegistDTO; +import stanl_2.final_backend.domain.alarm.command.domain.aggregate.entity.Alarm; +import stanl_2.final_backend.domain.alarm.command.application.service.AlarmCommandService; +import stanl_2.final_backend.domain.alarm.command.domain.repository.AlarmRepository; +import stanl_2.final_backend.domain.alarm.command.domain.repository.EmitterRepository; +import stanl_2.final_backend.domain.alarm.common.exception.AlarmCommonException; +import stanl_2.final_backend.domain.alarm.common.exception.AlarmErrorCode; +import stanl_2.final_backend.domain.contract.command.application.dto.ContractAlarmDTO; +import stanl_2.final_backend.domain.contract.command.domain.aggregate.entity.Contract; +import stanl_2.final_backend.domain.member.query.dto.MemberDTO; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.domain.notices.command.application.dto.NoticeAlarmDTO; +import stanl_2.final_backend.domain.order.command.application.dto.OrderAlarmDTO; +import stanl_2.final_backend.domain.order.command.domain.aggregate.entity.Order; +import stanl_2.final_backend.domain.purchase_order.command.application.dto.PurchaseOrderAlarmDTO; +import stanl_2.final_backend.domain.purchase_order.command.domain.aggregate.entity.PurchaseOrder; +import stanl_2.final_backend.domain.schedule.common.exception.ScheduleCommonException; +import stanl_2.final_backend.domain.schedule.common.exception.ScheduleErrorCode; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +@Slf4j +@Service +public class AlarmCommandServiceImpl implements AlarmCommandService { + + private final EmitterRepository emitterRepository; + private final AlarmRepository alarmRepository; + private final AuthQueryService authQueryService; + private final MemberQueryService memberQueryService; + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + + private static final Long DEFAULT_TIMEOUT = 60L * 1000 * 60; + + @Autowired + public AlarmCommandServiceImpl(AlarmRepository alarmRepository, EmitterRepository emitterRepository, + AuthQueryService authQueryService, MemberQueryService memberQueryService){ + this.alarmRepository = alarmRepository; + this.emitterRepository = emitterRepository; + this.authQueryService = authQueryService; + this.memberQueryService = memberQueryService; + } + + @Override + public SseEmitter subscribe(AlarmRegistDTO alarmRegistDTO, HttpServletResponse response) { + + String lastEventId = alarmRegistDTO.getLastEventId(); + String memberId = authQueryService.selectMemberIdByLoginId(alarmRegistDTO.getMemberLoginId()); + String emitterId = memberId + "_" + System.currentTimeMillis(); + + // 기존 Emitter 확인 및 삭제 + SseEmitter existingEmitter = emitterRepository.findEmitterByMemberId(memberId); + if (existingEmitter != null) { + emitterRepository.deleteAllByEmitterId(memberId); // 기존 Emitter 제거 + existingEmitter.complete(); // 기존 연결 종료 + } + + // 클라이언트의 sse 연결 요청에 응답하기 위한 SseEmitter 객체 생성 + // 유효시간 지정으로 시간이 지나면 클라이언트에서 자동으로 재연결 요청함 + SseEmitter emitter = emitterRepository.save(emitterId, new SseEmitter(DEFAULT_TIMEOUT)); + response.setHeader("X-Accel-Buffering", "no"); // NGINX PROXY 에서의 필요설정 불필요한 버퍼링방지 + + // SseEmitter의 완료/시간초과/에러로 인한 전송 불가 시 sseEmitter 삭제 + emitter.onCompletion(() -> emitterRepository.deleteAllByEmitterId(emitterId)); + emitter.onTimeout(() -> emitterRepository.deleteAllByEmitterId(emitterId)); + emitter.onError((e) -> emitterRepository.deleteAllByEmitterId(emitterId)); + + // 연결 직후, 데이터 전송이 없을 시 503 에러 발생. 에러 방지 위한 더미데이터 전송 + sendToClient(emitter, emitterId, emitterId + "님 연결되었습니다."); + + // 클라이언트가 미수신한 Event 유실 예방, 연결이 끊켰거나 미수신된 데이터를 다 찾아서 보내줌 + if (!lastEventId.isEmpty()) { + Map events = emitterRepository.findAllEventCacheStartWithByMemberId(memberId); + events.entrySet().stream() + .filter(entry -> lastEventId.compareTo(entry.getKey()) < 0) + .forEach(entry -> sendToClient(emitter, entry.getKey(), entry.getValue())); + } + + return emitter; + } + + @Override + public void sendToClient(SseEmitter emitter, String emitterId, Object data) { + + try { + emitter.send(SseEmitter.event() + .id(emitterId) + .name("sse") + .data(data)); + } catch (IOException e){ + emitterRepository.deleteAllByEmitterId(emitterId); + log.error("SSE 연결 오류 발생", e); + } + } + + @Override + @Transactional + public void send(String memberId, String adminId, String contentId, String message, String redirectUrl, String tag, + String type, String createdAt){ + + Alarm alarm = alarmRepository.save(createAlarm(memberId, adminId, contentId, message, redirectUrl, + tag, type, createdAt)); + + Map sseEmitters = emitterRepository.findAllEmitterStartWithByMemberId(memberId); + sseEmitters.forEach( + (key, emitter) -> { + emitterRepository.saveEventCache(key, alarm); + sendToClient(emitter, key, alarm); + } + ); + } + + @Override + @Transactional + public Alarm createAlarm(String memberId, String adminId, String contentId, String message, String redirectUrl + , String tag, String type, String createdAt) { + + Alarm alarm = new Alarm(); + alarm.setMemberId(memberId); + alarm.setAdminId(adminId); + alarm.setContentId(contentId); + alarm.setMessage(message); + alarm.setRedirectUrl(redirectUrl); + alarm.setTag(tag); + alarm.setType(type); + alarm.setReadStatus(false); + alarm.setCreatedAt(createdAt); + + return alarm; + } + + @Override + @Transactional + public void sendNoticeAlarm(NoticeAlarmDTO noticeAlarmDTO){ + + List memberIdList = new ArrayList<>(); + + if(noticeAlarmDTO.getTag().equals("ALL")){ + // 결과 합치기 + memberIdList.addAll(memberQueryService.selectMemberByRole("EMPLOYEE")); + memberIdList.addAll(memberQueryService.selectMemberByRole("ADMIN")); + memberIdList.addAll(memberQueryService.selectMemberByRole("DIRECTOR")); + memberIdList.addAll(memberQueryService.selectMemberByRole("GOD")); + // 중복 제거 + memberIdList = new ArrayList<>(new HashSet<>(memberIdList)); + } else if (noticeAlarmDTO.getTag().equals("ADMIN")){ + memberIdList.addAll(memberQueryService.selectMemberByRole("ADMIN")); + memberIdList.addAll(memberQueryService.selectMemberByRole("DIRECTOR")); + memberIdList.addAll(memberQueryService.selectMemberByRole("GOD")); + }else { + memberIdList.addAll(memberQueryService.selectMemberByRole(noticeAlarmDTO.getTag())); + } + + memberIdList.forEach(member -> { + String targetId = member; + String type = "NOTICE"; + String tag = null; + if(noticeAlarmDTO.getClassification().equals("NORMAL")){ + tag = "일반"; + } else if(noticeAlarmDTO.getClassification().equals("GOAL")) { + tag = "영업 목표"; + } else { + tag = "영업 전략"; + } + + String target = null; + if(noticeAlarmDTO.getTag().equals("ALL")){ + target = "전체"; + } else if(noticeAlarmDTO.getTag().equals("ADMIN")) { + target = "영업관리자"; + } else { + target = "영업담당자"; + } + + String message = target + " 대상 공지사항이 등록되었습니다."; + String redirectUrl = "/notice/detail?tag=" + noticeAlarmDTO.getTag() + "&classification=" + noticeAlarmDTO.getClassification() + + "¬iceTitle=" + noticeAlarmDTO.getTitle() + "¬iceContent=" + noticeAlarmDTO.getContent() + + "¬iceId=" + noticeAlarmDTO.getNoticeId(); + String createdAt = getCurrentTime(); + + send(targetId, noticeAlarmDTO.getMemberId(), noticeAlarmDTO.getNoticeId(), message, redirectUrl, tag, type, createdAt); + }); + } + + @Override + @Transactional + public void sendContractAlarm(ContractAlarmDTO contractAlarmDTO) throws GeneralSecurityException { + + String type = "CONTRACT"; + String tag = "계약서"; + String message = contractAlarmDTO.getCustomerName() +" 고객님의 계약서가 승인되었습니다."; + + String redirectUrl = null; + String memberRole = memberQueryService.selectMemberRoleById(contractAlarmDTO.getMemberId()); + + // 권한에 따른 경로 변경 + if (memberRole == "EMPLOYEE") { + redirectUrl = "/contract/emlist"; + } else if (memberRole == "ADMIN") { + redirectUrl = "/contract/Elist"; + } else if (memberRole == "DIRECTOR") { + redirectUrl = "/contract/dlist"; + } else { + redirectUrl = "/contract/emlist"; + } + + String createdAt = getCurrentTime(); + + send(contractAlarmDTO.getMemberId(), contractAlarmDTO.getAdminId(),contractAlarmDTO.getContractId(), message, + redirectUrl, tag, type, createdAt); + } + + @Override + @Transactional + public void sendPurchaseOrderAlarm(PurchaseOrderAlarmDTO purchaseOrderAlarmDTO) throws GeneralSecurityException { + + String type = "CONTRACT"; + String tag = "발주서"; + String message = purchaseOrderAlarmDTO.getTitle() +" 가 승인되었습니다."; + + String redirectUrl = null; + String memberRole = memberQueryService.selectMemberRoleById(purchaseOrderAlarmDTO.getMemberId()); + + // 권한에 따른 경로 변경 + if (memberRole == "EMPLOYEE") { + redirectUrl = "/purchaseOrder/emlist"; + } else if (memberRole == "ADMIN"){ + redirectUrl = "/purchaseOrder/Elist"; + } else if (memberRole == "DIRECTOR"){ + redirectUrl = "/purchaseOrder/dlist"; + } else { + redirectUrl = "/purchaseOrder/emlist"; + } + + String createdAt = getCurrentTime(); + + send(purchaseOrderAlarmDTO.getMemberId(), purchaseOrderAlarmDTO.getAdminId(), purchaseOrderAlarmDTO.getPurchaseOrderId(), + message, redirectUrl, tag, type, createdAt); + } + + @Override + @Transactional + public void sendOrderAlarm(OrderAlarmDTO orderAlarmDTO) throws GeneralSecurityException { + + String type = "CONTRACT"; + String tag = "수주서"; + String message = orderAlarmDTO.getTitle() +" 가 승인되었습니다."; + + String redirectUrl = null; + String memberRole = memberQueryService.selectMemberRoleById(orderAlarmDTO.getMemberId()); + + // 권한에 따른 경로 변경 + if (memberRole == "EMPLOYEE") { + redirectUrl = "/order/emlist"; + } else if(memberRole == "ADMIN") { + redirectUrl = "/order/Elist"; + } else if (memberRole == "DIRECTOR") { + redirectUrl = "/order/dlist"; + } else { + redirectUrl = "/order/emlist"; + } + String createdAt = getCurrentTime(); + + send(orderAlarmDTO.getMemberId(), orderAlarmDTO.getAdminId(),orderAlarmDTO.getOrderId(), message, redirectUrl, + tag, type, createdAt); + } + + @Override + @Transactional + public Boolean updateReadStatus(String alarmId) { + + Alarm alarm = alarmRepository.findByAlarmId(alarmId); + + if(alarm == null){ + throw new AlarmCommonException(AlarmErrorCode.ALARM_NOT_FOUND); + } + + try { + alarm.setReadStatus(true); + + alarmRepository.save(alarm); + return true; + } catch (DataIntegrityViolationException e) { + // 데이터 무결성 위반 예외 처리 + throw new AlarmCommonException(AlarmErrorCode.DATA_INTEGRITY_VIOLATION); + } catch (Exception e) { + // 서버 오류 + throw new AlarmCommonException(AlarmErrorCode.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/alarm/common/exception/AlarmCommonException.java b/src/main/java/stanl_2/final_backend/domain/alarm/common/exception/AlarmCommonException.java new file mode 100644 index 00000000..6edea1cf --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/alarm/common/exception/AlarmCommonException.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.alarm.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class AlarmCommonException extends RuntimeException { + private final AlarmErrorCode alarmErrorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.alarmErrorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/alarm/common/exception/AlarmErrorCode.java b/src/main/java/stanl_2/final_backend/domain/alarm/common/exception/AlarmErrorCode.java new file mode 100644 index 00000000..56f8b51a --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/alarm/common/exception/AlarmErrorCode.java @@ -0,0 +1,54 @@ +package stanl_2.final_backend.domain.alarm.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum AlarmErrorCode { + + /** + * 400(Bad Request) + * 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. + */ + + + + /** + * 401(Unauthorized) + * 비록 HTTP 표준에서는 "미승인(unauthorized)"를 명확히 하고 있지만, + * 의미상 이 응답은 "비인증(unauthenticated)"을 의미합니다. + * 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로를 인증해야 합니다. + */ + + + /** + * 403(Forbidden) + * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. + * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. + */ + + + /** + * 404(Not Found) + * 서버는 요청받은 리소스를 찾을 수 없습니다. 브라우저에서는 알려지지 않은 URL을 의미합니다. + * 이것은 API에서 종점은 적절하지만 리소스 자체는 존재하지 않음을 의미할 수도 있습니다. + * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. + * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. + */ + ALARM_NOT_FOUND(404001, HttpStatus.NOT_FOUND, "해당하는 alarm 데이터를 찾지 못했습니다"), + + /** + * 500(Internal Server Error) + * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. + */ + DATA_INTEGRITY_VIOLATION(40001, HttpStatus.BAD_REQUEST, "데이터 무결 위반하였습니다."), + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); + + + + private final Integer code; + private final HttpStatus httpStatus; + private final String msg; +} diff --git a/src/main/java/stanl_2/final_backend/domain/alarm/common/exception/AlarmExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/alarm/common/exception/AlarmExceptionResponse.java new file mode 100644 index 00000000..075a2bbd --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/alarm/common/exception/AlarmExceptionResponse.java @@ -0,0 +1,22 @@ +package stanl_2.final_backend.domain.alarm.common.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class AlarmExceptionResponse { + private final Integer code; + private final String msg; + private final HttpStatus httpStatus; + + public AlarmExceptionResponse(AlarmErrorCode alarmErrorCode) { + this.code = alarmErrorCode.getCode(); + this.msg = alarmErrorCode.getMsg(); + this.httpStatus = alarmErrorCode.getHttpStatus(); + } + + public static AlarmExceptionResponse of(AlarmErrorCode alarmErrorCode) { + return new AlarmExceptionResponse(alarmErrorCode); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/alarm/common/response/AlarmResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/alarm/common/response/AlarmResponseMessage.java new file mode 100644 index 00000000..b5f9d40d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/alarm/common/response/AlarmResponseMessage.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.alarm.common.response; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +public class AlarmResponseMessage { + private Integer httpStatus; + private String msg; + private Object result; +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/alarm/query/controller/AlarmController.java b/src/main/java/stanl_2/final_backend/domain/alarm/query/controller/AlarmController.java new file mode 100644 index 00000000..1f6a9e06 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/alarm/query/controller/AlarmController.java @@ -0,0 +1,81 @@ +package stanl_2.final_backend.domain.alarm.query.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import stanl_2.final_backend.domain.alarm.common.response.AlarmResponseMessage; +import stanl_2.final_backend.domain.alarm.query.dto.AlarmSelectDTO; +import stanl_2.final_backend.domain.alarm.query.dto.AlarmSelectReadDTO; +import stanl_2.final_backend.domain.alarm.query.dto.AlarmSelectTypeDTO; +import stanl_2.final_backend.domain.alarm.query.dto.AlarmSelectUnreadDTO; +import stanl_2.final_backend.domain.alarm.query.service.AlarmQueryService; +import stanl_2.final_backend.domain.schedule.common.response.ScheduleResponseMessage; + +import java.security.Principal; + +@RestController("queryAlarmController") +@RequestMapping("/api/v1/alarm") +public class AlarmController { + + private final AlarmQueryService alarmQueryService; + + @Autowired + public AlarmController(AlarmQueryService alarmQueryService) { + this.alarmQueryService = alarmQueryService; + } + + @Operation(summary = "회원 알림창 전체 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "회원 알림창 전체 조회 완료", + content = {@Content(schema = @Schema(implementation = ScheduleResponseMessage.class))}) + }) + @GetMapping("") + public ResponseEntity selectMemberAlarmType(Principal principal){ + + String memberLoginId = principal.getName(); + + AlarmSelectTypeDTO AlarmSelectTypeDTO = alarmQueryService.selectMemberByAlarmType(memberLoginId); + + return ResponseEntity.ok(AlarmResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(AlarmSelectTypeDTO) + .build()); + } + + @Operation(summary = "회원 알림 상세 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "회원 알림 상세 조회 완료", + content = {@Content(schema = @Schema(implementation = ScheduleResponseMessage.class))}) + }) + @GetMapping("/{type}") + public ResponseEntity selectReadAlarm(Principal principal, + @PathVariable String type, + @PageableDefault(size = 8) Pageable pageable){ + + String memberLoginId = principal.getName(); + AlarmSelectDTO alarmSelectDTO = new AlarmSelectDTO(); + alarmSelectDTO.setMemberLoginId(memberLoginId); + alarmSelectDTO.setType(type); + + Page allAlarms + = alarmQueryService.selectAlarmByType(alarmSelectDTO , pageable); + + return ResponseEntity.ok(AlarmResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(allAlarms) + .build()); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/alarm/query/dto/AlarmSelectDTO.java b/src/main/java/stanl_2/final_backend/domain/alarm/query/dto/AlarmSelectDTO.java new file mode 100644 index 00000000..62f46136 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/alarm/query/dto/AlarmSelectDTO.java @@ -0,0 +1,26 @@ +package stanl_2.final_backend.domain.alarm.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class AlarmSelectDTO { + + private String alarmId; + private String message; + private String type; + private String tag; + private String redirectUrl; + private Boolean readStatus; + private String createdAt; + + private String memberLoginId; + private String memberId; + private String adminId; + private String contentId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/alarm/query/dto/AlarmSelectReadDTO.java b/src/main/java/stanl_2/final_backend/domain/alarm/query/dto/AlarmSelectReadDTO.java new file mode 100644 index 00000000..5b0625fb --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/alarm/query/dto/AlarmSelectReadDTO.java @@ -0,0 +1,18 @@ +package stanl_2.final_backend.domain.alarm.query.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class AlarmSelectReadDTO { + + private String message; + private String type; + private String tag; + private String redirectUrl; + private Boolean readStatus; + + private String memberLoginId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/alarm/query/dto/AlarmSelectTypeDTO.java b/src/main/java/stanl_2/final_backend/domain/alarm/query/dto/AlarmSelectTypeDTO.java new file mode 100644 index 00000000..af77e787 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/alarm/query/dto/AlarmSelectTypeDTO.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.alarm.query.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class AlarmSelectTypeDTO { + + private Integer scheduleAlarmCount; + private Integer noticeAlarmCount; + private Integer contractAlarmCount; +} diff --git a/src/main/java/stanl_2/final_backend/domain/alarm/query/dto/AlarmSelectUnreadDTO.java b/src/main/java/stanl_2/final_backend/domain/alarm/query/dto/AlarmSelectUnreadDTO.java new file mode 100644 index 00000000..036fd1a6 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/alarm/query/dto/AlarmSelectUnreadDTO.java @@ -0,0 +1,18 @@ +package stanl_2.final_backend.domain.alarm.query.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class AlarmSelectUnreadDTO { + + private String message; + private String type; + private String tag; + private String redirectUrl; + private Boolean readStatus; + + private String memberLoginId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/alarm/query/repository/AlarmMapper.java b/src/main/java/stanl_2/final_backend/domain/alarm/query/repository/AlarmMapper.java new file mode 100644 index 00000000..7869bffc --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/alarm/query/repository/AlarmMapper.java @@ -0,0 +1,34 @@ +package stanl_2.final_backend.domain.alarm.query.repository; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import stanl_2.final_backend.domain.alarm.query.dto.AlarmSelectDTO; +import stanl_2.final_backend.domain.alarm.query.dto.AlarmSelectUnreadDTO; +import stanl_2.final_backend.domain.alarm.query.dto.AlarmSelectReadDTO; +import stanl_2.final_backend.domain.alarm.query.dto.AlarmSelectTypeDTO; + +import java.util.List; + +@Mapper +public interface AlarmMapper { + AlarmSelectTypeDTO findNumberOfAlarmsByType(String memberId); + + List findReadAlarmsByType(@Param("offset") Integer offset, + @Param("pageSize") Integer pageSize, + @Param("memberId") String memberId, + @Param("type") String type); + + List findUnReadAlarmsByType(@Param("offset") Integer offset, + @Param("pageSize") Integer pageSize, + @Param("memberId") String memberId, + @Param("type") String type); + + List findAllAlarmsByType(@Param("offset") Integer offset, + @Param("pageSize") Integer pageSize, + @Param("memberId") String memberId, + @Param("type") String type); + + Integer findReadAlarmsCountByMemberId(String memberId); + + Integer findUnreadAlarmsCountByMemberId(String memberId); +} diff --git a/src/main/java/stanl_2/final_backend/domain/alarm/query/service/AlarmQueryService.java b/src/main/java/stanl_2/final_backend/domain/alarm/query/service/AlarmQueryService.java new file mode 100644 index 00000000..ab46f4ea --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/alarm/query/service/AlarmQueryService.java @@ -0,0 +1,18 @@ +package stanl_2.final_backend.domain.alarm.query.service; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import stanl_2.final_backend.domain.alarm.query.dto.AlarmSelectDTO; +import stanl_2.final_backend.domain.alarm.query.dto.AlarmSelectReadDTO; +import stanl_2.final_backend.domain.alarm.query.dto.AlarmSelectTypeDTO; +import stanl_2.final_backend.domain.alarm.query.dto.AlarmSelectUnreadDTO; + +public interface AlarmQueryService { + AlarmSelectTypeDTO selectMemberByAlarmType(String memberLoginId); + + Page selectReadAlarmByType(AlarmSelectReadDTO alarmSelectReadDTO, Pageable pageable); + + Page selectUnreadAlarmByType(AlarmSelectUnreadDTO alarmSelectUnreadDTO, Pageable pageable); + + Page selectAlarmByType(AlarmSelectDTO alarmSelectDTO, Pageable pageable); +} diff --git a/src/main/java/stanl_2/final_backend/domain/alarm/query/service/AlarmQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/alarm/query/service/AlarmQueryServiceImpl.java new file mode 100644 index 00000000..08e89b66 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/alarm/query/service/AlarmQueryServiceImpl.java @@ -0,0 +1,105 @@ +package stanl_2.final_backend.domain.alarm.query.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.alarm.common.exception.AlarmCommonException; +import stanl_2.final_backend.domain.alarm.common.exception.AlarmErrorCode; +import stanl_2.final_backend.domain.alarm.query.dto.AlarmSelectDTO; +import stanl_2.final_backend.domain.alarm.query.dto.AlarmSelectReadDTO; +import stanl_2.final_backend.domain.alarm.query.dto.AlarmSelectTypeDTO; +import stanl_2.final_backend.domain.alarm.query.dto.AlarmSelectUnreadDTO; +import stanl_2.final_backend.domain.alarm.query.repository.AlarmMapper; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; + +import java.util.List; + +@Slf4j +@Service +public class AlarmQueryServiceImpl implements AlarmQueryService{ + + private final AlarmMapper alarmMapper; + private final AuthQueryService authQueryService; + + @Autowired + public AlarmQueryServiceImpl(AlarmMapper alarmMapper, AuthQueryService authQueryService) { + this.alarmMapper = alarmMapper; + this.authQueryService = authQueryService; + } + + @Override + @Transactional(readOnly = true) + public AlarmSelectTypeDTO selectMemberByAlarmType(String memberLoginId) { + + String memberId = authQueryService.selectMemberIdByLoginId(memberLoginId); + + AlarmSelectTypeDTO alarmSelectTypeDTO = alarmMapper.findNumberOfAlarmsByType(memberId); + + if(alarmSelectTypeDTO == null){ + AlarmSelectTypeDTO alarmNullSelectTypeDTO + = new AlarmSelectTypeDTO(0,0,0); + + return alarmNullSelectTypeDTO; + } + + return alarmSelectTypeDTO; + } + + @Override + @Transactional(readOnly = true) + public Page selectReadAlarmByType(AlarmSelectReadDTO alarmSelectReadDTO, Pageable pageable) { + + Integer offset = Math.toIntExact(pageable.getOffset()); + Integer pageSize = pageable.getPageSize(); + + String memberId = authQueryService.selectMemberIdByLoginId(alarmSelectReadDTO.getMemberLoginId()); + + List readAlarmList + = alarmMapper.findReadAlarmsByType(offset, pageSize, memberId, alarmSelectReadDTO.getType()); + + Integer count = alarmMapper.findReadAlarmsCountByMemberId(memberId); + int totalOrder = (count != null) ? count : 0; + + return new PageImpl<>(readAlarmList, pageable, totalOrder); + } + + @Override + @Transactional(readOnly = true) + public Page selectUnreadAlarmByType(AlarmSelectUnreadDTO alarmSelectUnreadDTO, Pageable pageable) { + + Integer offset = Math.toIntExact(pageable.getOffset()); + Integer pageSize = pageable.getPageSize(); + + String memberId = authQueryService.selectMemberIdByLoginId(alarmSelectUnreadDTO.getMemberLoginId()); + + List unReadAlarmList + = alarmMapper.findUnReadAlarmsByType(offset, pageSize, memberId, alarmSelectUnreadDTO.getType()); + + Integer count = alarmMapper.findUnreadAlarmsCountByMemberId(memberId); + int totalOrder = (count != null) ? count : 0; + + return new PageImpl<>(unReadAlarmList, pageable, totalOrder); + } + + @Override + public Page selectAlarmByType(AlarmSelectDTO alarmSelectDTO, Pageable pageable) { + + Integer offset = Math.toIntExact(pageable.getOffset()); + Integer pageSize = pageable.getPageSize(); + + String memberId = authQueryService.selectMemberIdByLoginId(alarmSelectDTO.getMemberLoginId()); + + List readAlarmList + = alarmMapper.findAllAlarmsByType(offset, pageSize, memberId, alarmSelectDTO.getType()); + + Integer count = alarmMapper.findReadAlarmsCountByMemberId(memberId); + int totalOrder = (count != null) ? count : 0; + + return new PageImpl<>(readAlarmList, pageable, totalOrder); + + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/alarm/scheduler/AlarmScheduler.java b/src/main/java/stanl_2/final_backend/domain/alarm/scheduler/AlarmScheduler.java new file mode 100644 index 00000000..fa7c4478 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/alarm/scheduler/AlarmScheduler.java @@ -0,0 +1,70 @@ +package stanl_2.final_backend.domain.alarm.scheduler; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.alarm.command.application.service.AlarmCommandService; +import stanl_2.final_backend.domain.schedule.query.dto.ScheduleDayDTO; +import stanl_2.final_backend.domain.schedule.query.service.ScheduleQueryService; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + + +@Service("AlarmSchdulerService") +@Slf4j +public class AlarmScheduler { + + private final AlarmCommandService alarmCommandService; + private final ScheduleQueryService scheduleQueryService; + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + + @Autowired + public AlarmScheduler(AlarmCommandService alarmCommandService, ScheduleQueryService scheduleQueryService) { + this.alarmCommandService = alarmCommandService; + this.scheduleQueryService = scheduleQueryService; + } + +// @Scheduled(cron = "0 0 2 * * *") // 매일 새벽 2시에 실행) + @Scheduled(cron = "0 27 14 * * *") + @Transactional + public void alarmTodaySchedule(){ + + String currentDay = getCurrentTime().substring(0,10); + + List todaySchedules = scheduleQueryService.findSchedulesByDate(currentDay); + + // 사용자 별로 알림 전송 + todaySchedules.forEach(schedule -> { + String Hour = schedule.getStartAt().substring(11,13); + String Minute = schedule.getStartAt().substring(14,16); + + String memberId = schedule.getMemberId(); + String type = "SCHEDULE"; + + String tag = null; + if(schedule.getTag().equals("MEETING")){ + tag = "미팅"; + } else if(schedule.getTag().equals("SESSION")){ + tag = "회의"; + } else if(schedule.getTag().equals("VACATION")){ + tag = "휴가"; + } else{ + tag = "교육"; + } + + String message = "금일 " + Hour + "시 " + Minute + "분에 '" + tag + "' 일정이 있습니다"; + String redirectUrl = "/schedule"; + String createdAt = getCurrentTime(); + + alarmCommandService.send(memberId, memberId, schedule.getScheduleId(), message, redirectUrl, tag, type, createdAt); + }); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/career/command/application/controller/CareerController.java b/src/main/java/stanl_2/final_backend/domain/career/command/application/controller/CareerController.java new file mode 100644 index 00000000..cecea16b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/career/command/application/controller/CareerController.java @@ -0,0 +1,53 @@ +package stanl_2.final_backend.domain.career.command.application.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stanl_2.final_backend.domain.career.command.application.dto.CareerRegistDTO; +import stanl_2.final_backend.domain.career.command.application.service.CareerCommandService; +import stanl_2.final_backend.domain.career.common.response.CareerResponseMessage; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; + +import java.security.Principal; + +@Slf4j +@RestController("commandCareerController") +@RequestMapping("/api/v1/career") +public class CareerController { + + private final CareerCommandService careerCommandService; + private final AuthQueryService authQueryService; + + @Autowired + public CareerController(CareerCommandService careerCommandService, + AuthQueryService authQueryService) { + this.careerCommandService = careerCommandService; + this.authQueryService = authQueryService; + } + + @Operation(summary = "경력 등록") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CareerResponseMessage.class))}) + }) + @PostMapping("") + public ResponseEntity postCareer(@RequestBody CareerRegistDTO careerRegistDTO, + Principal principal){ + + careerRegistDTO.setMemberId(authQueryService.selectMemberIdByLoginId(principal.getName())); + + careerCommandService.registCareer(careerRegistDTO); + + return ResponseEntity.ok(CareerResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(null) + .build()); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/career/command/application/dto/CareerRegistDTO.java b/src/main/java/stanl_2/final_backend/domain/career/command/application/dto/CareerRegistDTO.java new file mode 100644 index 00000000..966cd7ab --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/career/command/application/dto/CareerRegistDTO.java @@ -0,0 +1,15 @@ +package stanl_2.final_backend.domain.career.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class CareerRegistDTO { + private String emplDate; + private String resignDate; + private String name; + private String note; + private String memberId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/career/command/application/service/CareerCommandService.java b/src/main/java/stanl_2/final_backend/domain/career/command/application/service/CareerCommandService.java new file mode 100644 index 00000000..ca136437 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/career/command/application/service/CareerCommandService.java @@ -0,0 +1,8 @@ +package stanl_2.final_backend.domain.career.command.application.service; + +import stanl_2.final_backend.domain.career.command.application.dto.CareerRegistDTO; + +public interface CareerCommandService { + void registCareer(CareerRegistDTO careerRegistDTO); + +} diff --git a/src/main/java/stanl_2/final_backend/domain/career/command/domain/aggregate/entity/Career.java b/src/main/java/stanl_2/final_backend/domain/career/command/domain/aggregate/entity/Career.java new file mode 100644 index 00000000..1a1b32c0 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/career/command/domain/aggregate/entity/Career.java @@ -0,0 +1,59 @@ +package stanl_2.final_backend.domain.career.command.domain.aggregate.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "tb_CAREER") +public class Career { + @Id + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name = "prefix", value = "CAR") + ) + @Column(name = "CAR_ID", nullable = false) + private String careerId; + + @Column(name = "CAR_EMP_DATE", nullable = false) + private String emplDate; + + @Column(name = "CAR_RTR_DATE") + private String resignDate; + + @Column(name = "CAR_NAME", nullable = false) + private String name; + + @Column(name = "CAR_NOTE") + private String note; + + @Column(name = "CREATED_AT", nullable = false, updatable = false) + private String createdAt; + + @Column(name = "MEM_ID", nullable = false) + private String memberId; + + + @PrePersist + private void prePersist() { + this.createdAt = getCurrentTime(); + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/career/command/domain/repository/CareerRepository.java b/src/main/java/stanl_2/final_backend/domain/career/command/domain/repository/CareerRepository.java new file mode 100644 index 00000000..47758e96 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/career/command/domain/repository/CareerRepository.java @@ -0,0 +1,10 @@ +package stanl_2.final_backend.domain.career.command.domain.repository; + + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import stanl_2.final_backend.domain.career.command.domain.aggregate.entity.Career; + +@Repository +public interface CareerRepository extends JpaRepository { +} diff --git a/src/main/java/stanl_2/final_backend/domain/career/command/domain/service/CareerCommandServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/career/command/domain/service/CareerCommandServiceImpl.java new file mode 100644 index 00000000..d37e767b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/career/command/domain/service/CareerCommandServiceImpl.java @@ -0,0 +1,35 @@ +package stanl_2.final_backend.domain.career.command.domain.service; + +import lombok.extern.slf4j.Slf4j; +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.career.command.application.dto.CareerRegistDTO; +import stanl_2.final_backend.domain.career.command.application.service.CareerCommandService; +import stanl_2.final_backend.domain.career.command.domain.aggregate.entity.Career; +import stanl_2.final_backend.domain.career.command.domain.repository.CareerRepository; + +@Slf4j +@Service("commandCareerService") +public class CareerCommandServiceImpl implements CareerCommandService { + + private final CareerRepository careerRepository; + private final ModelMapper modelMapper; + + @Autowired + public CareerCommandServiceImpl(CareerRepository careerRepository, + ModelMapper modelMapper) { + this.careerRepository = careerRepository; + this.modelMapper = modelMapper; + } + + @Override + @Transactional + public void registCareer(CareerRegistDTO careerRegistDTO) { + + Career career = modelMapper.map(careerRegistDTO, Career.class); + + careerRepository.save(career); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/career/common/exception/CareerCommonException.java b/src/main/java/stanl_2/final_backend/domain/career/common/exception/CareerCommonException.java new file mode 100644 index 00000000..d062cd16 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/career/common/exception/CareerCommonException.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.career.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class CareerCommonException extends RuntimeException { + private final CareerErrorCode sampleErrorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.sampleErrorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/career/common/exception/CareerErrorCode.java b/src/main/java/stanl_2/final_backend/domain/career/common/exception/CareerErrorCode.java new file mode 100644 index 00000000..bb85bbaf --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/career/common/exception/CareerErrorCode.java @@ -0,0 +1,52 @@ +package stanl_2.final_backend.domain.career.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum CareerErrorCode { + + /** + * 400(Bad Request) + * 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. + */ + + + + /** + * 401(Unauthorized) + * 비록 HTTP 표준에서는 "미승인(unauthorized)"를 명확히 하고 있지만, + * 의미상 이 응답은 "비인증(unauthenticated)"을 의미합니다. + * 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로를 인증해야 합니다. + */ + + + /** + * 403(Forbidden) + * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. + * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. + */ + + + + /** + * 404(Not Found) + * 서버는 요청받은 리소스를 찾을 수 없습니다. 브라우저에서는 알려지지 않은 URL을 의미합니다. + * 이것은 API에서 종점은 적절하지만 리소스 자체는 존재하지 않음을 의미할 수도 있습니다. + * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. + * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. + */ + SAMPLE_NOT_FOUND(404001, HttpStatus.NOT_FOUND, "sample 데이터를 찾지 못했습니다"), + CENTER_NOT_FOUND(404002, HttpStatus.NOT_FOUND, "center 데이터를 찾지 못했습니다."), + /** + * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. + */ + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); + + + private final Integer code; + private final HttpStatus httpStatus; + private final String msg; +} diff --git a/src/main/java/stanl_2/final_backend/domain/career/common/exception/CareerExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/career/common/exception/CareerExceptionResponse.java new file mode 100644 index 00000000..21f05f1a --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/career/common/exception/CareerExceptionResponse.java @@ -0,0 +1,22 @@ +package stanl_2.final_backend.domain.career.common.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class CareerExceptionResponse { + private final Integer code; + private final String msg; + private final HttpStatus httpStatus; + + public CareerExceptionResponse(CareerErrorCode sampleErrorCode) { + this.code = sampleErrorCode.getCode(); + this.msg = sampleErrorCode.getMsg(); + this.httpStatus = sampleErrorCode.getHttpStatus(); + } + + public static CareerExceptionResponse of(CareerErrorCode sampleErrorCode) { + return new CareerExceptionResponse(sampleErrorCode); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/career/common/response/CareerResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/career/common/response/CareerResponseMessage.java new file mode 100644 index 00000000..906e82cd --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/career/common/response/CareerResponseMessage.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.career.common.response; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +public class CareerResponseMessage { + private int httpStatus; + private String msg; + private Object result; +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/career/query/config/MybatisConfiguration.java b/src/main/java/stanl_2/final_backend/domain/career/query/config/MybatisConfiguration.java new file mode 100644 index 00000000..3fbc136c --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/career/query/config/MybatisConfiguration.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.career.query.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Configuration; + +@Configuration("careerMybatisConfiguration") +@MapperScan(basePackages = "stanl_2.final_backend.domain.career.query.repository") +public class MybatisConfiguration { +} diff --git a/src/main/java/stanl_2/final_backend/domain/career/query/controller/CareerController.java b/src/main/java/stanl_2/final_backend/domain/career/query/controller/CareerController.java new file mode 100644 index 00000000..f695ea3b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/career/query/controller/CareerController.java @@ -0,0 +1,71 @@ +package stanl_2.final_backend.domain.career.query.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import stanl_2.final_backend.domain.career.common.response.CareerResponseMessage; +import stanl_2.final_backend.domain.career.query.dto.CareerDTO; +import stanl_2.final_backend.domain.career.query.service.CareerQueryService; + +import java.security.Principal; +import java.util.List; + +@Slf4j +@RestController(value = "queryCareerController") +@RequestMapping("/api/v1/career") +public class CareerController { + + private final CareerQueryService careerQueryService; + + @Autowired + public CareerController(CareerQueryService careerQueryService) { + this.careerQueryService = careerQueryService; + } + + @Operation(summary = "경력 조회(with 사번)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CareerResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/other/{loginId}") + public ResponseEntity getCareerByOther(@PathVariable String loginId){ + + List careerList = careerQueryService.selectCareerList(loginId); + + return ResponseEntity.ok(CareerResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(careerList) + .build()); + } + + @Operation(summary = "경력 조회(접속중인 사용자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CareerResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("") + public ResponseEntity getCareer(Principal principal){ + + List careerList = careerQueryService.selectCareerList(principal.getName()); + + return ResponseEntity.ok(CareerResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(careerList) + .build()); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/career/query/dto/CareerDTO.java b/src/main/java/stanl_2/final_backend/domain/career/query/dto/CareerDTO.java new file mode 100644 index 00000000..2561953f --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/career/query/dto/CareerDTO.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.career.query.dto; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Setter +@Getter +public class CareerDTO { + private String emplDate; + private String resignDate; + private String name; + private String note; +} diff --git a/src/main/java/stanl_2/final_backend/domain/career/query/repository/CareerMapper.java b/src/main/java/stanl_2/final_backend/domain/career/query/repository/CareerMapper.java new file mode 100644 index 00000000..d5d6485c --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/career/query/repository/CareerMapper.java @@ -0,0 +1,12 @@ +package stanl_2.final_backend.domain.career.query.repository; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import stanl_2.final_backend.domain.career.query.dto.CareerDTO; + +import java.util.List; + +@Mapper +public interface CareerMapper { + List selectCareerInfo(@Param("memberId") String memberId); +} diff --git a/src/main/java/stanl_2/final_backend/domain/career/query/service/CareerQueryService.java b/src/main/java/stanl_2/final_backend/domain/career/query/service/CareerQueryService.java new file mode 100644 index 00000000..f910dc85 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/career/query/service/CareerQueryService.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.career.query.service; + +import stanl_2.final_backend.domain.career.query.dto.CareerDTO; + +import java.util.List; + +public interface CareerQueryService { + List selectCareerList(String loginId); +} diff --git a/src/main/java/stanl_2/final_backend/domain/career/query/service/CareerQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/career/query/service/CareerQueryServiceImpl.java new file mode 100644 index 00000000..81b08c01 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/career/query/service/CareerQueryServiceImpl.java @@ -0,0 +1,37 @@ +package stanl_2.final_backend.domain.career.query.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.career.query.dto.CareerDTO; +import stanl_2.final_backend.domain.career.query.repository.CareerMapper; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; + +import java.util.List; + +@Slf4j +@Service("queryCareerService") +public class CareerQueryServiceImpl implements CareerQueryService { + + private final CareerMapper careerMapper; + private final AuthQueryService authQueryService; + + @Autowired + public CareerQueryServiceImpl(CareerMapper careerMapper, + AuthQueryService authQueryService) { + this.careerMapper = careerMapper; + this.authQueryService = authQueryService; + } + + @Override + @Transactional(readOnly = true) + public List selectCareerList(String loginId) { + + String memberId = authQueryService.selectMemberIdByLoginId(loginId); + + List careerList = careerMapper.selectCareerInfo(memberId); + + return careerList; + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/center/command/application/controller/CenterController.java b/src/main/java/stanl_2/final_backend/domain/center/command/application/controller/CenterController.java index 568317e6..8bcb4dce 100644 --- a/src/main/java/stanl_2/final_backend/domain/center/command/application/controller/CenterController.java +++ b/src/main/java/stanl_2/final_backend/domain/center/command/application/controller/CenterController.java @@ -1,12 +1,18 @@ package stanl_2.final_backend.domain.center.command.application.controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import stanl_2.final_backend.domain.center.command.application.dto.request.CenterRegistRequestDTO; -import stanl_2.final_backend.domain.center.command.application.dto.response.CenterRegistResponseDTO; +import org.springframework.web.multipart.MultipartFile; +import stanl_2.final_backend.domain.center.command.application.dto.request.CenterModifyDTO; +import stanl_2.final_backend.domain.center.command.application.dto.request.CenterRegistDTO; import stanl_2.final_backend.domain.center.command.application.service.CenterCommandService; -import stanl_2.final_backend.domain.center.common.response.ResponseMessage; +import stanl_2.final_backend.domain.center.common.response.CenterResponseMessage; @RestController("commandCenterController") @RequestMapping("/api/v1/center") @@ -19,33 +25,59 @@ public CenterController(CenterCommandService centerCommandService) { this.centerCommandService = centerCommandService; } - // 나중에 적용 예정 - swagger 설정 - // @Operation(summary = "Get center Test") - // @ApiResponses(value = { - // @ApiResponse(responseCode = "200", description = "성공", - // content = {@Content(schema = @Schema(implementation = ResponseMessage.class))}) - // }) - + @Operation(summary = "매장 등록") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CenterResponseMessage.class))}) + }) @PostMapping("") - public ResponseEntity postTest(@RequestBody CenterRegistRequestDTO centerRegistRequestDTO){ - - CenterRegistResponseDTO centerRegistResponseDTO = centerCommandService.registCenter(centerRegistRequestDTO); + public ResponseEntity postTest(@RequestPart("dto") CenterRegistDTO centerRegistDTO, + @RequestPart("file") MultipartFile imageUrl){ + centerCommandService.registCenter(centerRegistDTO, imageUrl); - return ResponseEntity.ok(new ResponseMessage(200, "post 성공", centerRegistResponseDTO)); + return ResponseEntity.ok(CenterResponseMessage.builder() + .httpStatus(200) + .msg("등록 성공") + .result(null) + .build()); } - @PutMapping("") - public ResponseEntity putTest(){ + @Operation(summary = "매장 수정") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CenterResponseMessage.class))}) + }) + @PutMapping("{centerId}") + public ResponseEntity putTest(@PathVariable("centerId") String centerId, + @RequestPart("dto") CenterModifyDTO centerModifyDTO, + @RequestPart("file") MultipartFile imageUrl){ + + centerModifyDTO.setCenterId(centerId); + centerCommandService.modifyCenter(centerModifyDTO, imageUrl); - return ResponseEntity.ok(new ResponseMessage(200, "put 성공", " ")); + return ResponseEntity.ok(CenterResponseMessage.builder() + .httpStatus(200) + .msg("수정 성공") + .result(null) + .build()); } - @DeleteMapping("") - public ResponseEntity deleteTest(){ + @Operation(summary = "매장 삭제") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CenterResponseMessage.class))}) + }) + @DeleteMapping("{id}") + public ResponseEntity deleteTest(@PathVariable("id") String id){ + centerCommandService.deleteCenter(id); - return ResponseEntity.ok(new ResponseMessage(200, "delete 성공", " ")); + return ResponseEntity.ok(CenterResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(null) + .build()); } } diff --git a/src/main/java/stanl_2/final_backend/domain/center/command/application/dto/response/CenterRegistResponseDTO.java b/src/main/java/stanl_2/final_backend/domain/center/command/application/dto/request/CenterModifyDTO.java similarity index 51% rename from src/main/java/stanl_2/final_backend/domain/center/command/application/dto/response/CenterRegistResponseDTO.java rename to src/main/java/stanl_2/final_backend/domain/center/command/application/dto/request/CenterModifyDTO.java index 2178977c..3515e4ed 100644 --- a/src/main/java/stanl_2/final_backend/domain/center/command/application/dto/response/CenterRegistResponseDTO.java +++ b/src/main/java/stanl_2/final_backend/domain/center/command/application/dto/request/CenterModifyDTO.java @@ -1,25 +1,17 @@ -package stanl_2.final_backend.domain.center.command.application.dto.response; +package stanl_2.final_backend.domain.center.command.application.dto.request; import lombok.*; -import java.sql.Timestamp; -import java.time.LocalDateTime; - @AllArgsConstructor @NoArgsConstructor -@Getter @Setter -@ToString -public class CenterRegistResponseDTO { - - private Long id; +@Getter +public class CenterModifyDTO { + private String centerId; private String name; private String address; private String phone; private Integer memberCount; private String operatingAt; - private Timestamp createdAt; - private Timestamp updatedAt; - private Timestamp deletedAt; - private Boolean active; + private String imageUrl; } diff --git a/src/main/java/stanl_2/final_backend/domain/center/command/application/dto/request/CenterRegistRequestDTO.java b/src/main/java/stanl_2/final_backend/domain/center/command/application/dto/request/CenterRegistDTO.java similarity index 80% rename from src/main/java/stanl_2/final_backend/domain/center/command/application/dto/request/CenterRegistRequestDTO.java rename to src/main/java/stanl_2/final_backend/domain/center/command/application/dto/request/CenterRegistDTO.java index d256f1a1..9680b48a 100644 --- a/src/main/java/stanl_2/final_backend/domain/center/command/application/dto/request/CenterRegistRequestDTO.java +++ b/src/main/java/stanl_2/final_backend/domain/center/command/application/dto/request/CenterRegistDTO.java @@ -6,13 +6,11 @@ @NoArgsConstructor @Setter @Getter -@ToString -public class CenterRegistRequestDTO { - -// private Long id; +public class CenterRegistDTO { private String name; private String address; private String phone; private Integer memberCount; private String operatingAt; + private String imageUrl; } diff --git a/src/main/java/stanl_2/final_backend/domain/center/command/application/service/CenterCommandService.java b/src/main/java/stanl_2/final_backend/domain/center/command/application/service/CenterCommandService.java index 47a9ad6d..98269eae 100644 --- a/src/main/java/stanl_2/final_backend/domain/center/command/application/service/CenterCommandService.java +++ b/src/main/java/stanl_2/final_backend/domain/center/command/application/service/CenterCommandService.java @@ -1,10 +1,14 @@ package stanl_2.final_backend.domain.center.command.application.service; -import stanl_2.final_backend.domain.center.command.application.dto.request.CenterRegistRequestDTO; -import stanl_2.final_backend.domain.center.command.application.dto.response.CenterRegistResponseDTO; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import stanl_2.final_backend.domain.center.command.application.dto.request.CenterModifyDTO; +import stanl_2.final_backend.domain.center.command.application.dto.request.CenterRegistDTO; +@Service public interface CenterCommandService { - CenterRegistResponseDTO registCenter(CenterRegistRequestDTO centerRegistRequestDTO); - + void registCenter(CenterRegistDTO centerRegistDTO, MultipartFile imageUrl); + void modifyCenter(CenterModifyDTO centerModifyDTO, MultipartFile imageUrl); + void deleteCenter(String id); } diff --git a/src/main/java/stanl_2/final_backend/domain/center/command/application/service/CenterCommandServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/center/command/application/service/CenterCommandServiceImpl.java deleted file mode 100644 index a9ec0226..00000000 --- a/src/main/java/stanl_2/final_backend/domain/center/command/application/service/CenterCommandServiceImpl.java +++ /dev/null @@ -1,47 +0,0 @@ -package stanl_2.final_backend.domain.center.command.application.service; - -import lombok.extern.slf4j.Slf4j; -import org.modelmapper.ModelMapper; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import stanl_2.final_backend.domain.center.command.application.dto.request.CenterRegistRequestDTO; -import stanl_2.final_backend.domain.center.command.application.dto.response.CenterRegistResponseDTO; -import stanl_2.final_backend.domain.center.command.domain.aggregate.entity.Center; -import stanl_2.final_backend.domain.center.command.domain.repository.CenterRepository; - -@Slf4j -@Service("commandCenterServiceImpl") -public class CenterCommandServiceImpl implements CenterCommandService { - - private final CenterRepository centerRepository; - private final ModelMapper modelMapper; - - - public CenterCommandServiceImpl(CenterRepository centerRepository, ModelMapper modelMapper) { - this.centerRepository = centerRepository; - this.modelMapper = modelMapper; - } - - @Override - @Transactional - public CenterRegistResponseDTO registCenter(CenterRegistRequestDTO centerRegistRequestDTO) { - -// Center center = new Center(); -// center.setName(centerRegistRequestDTO.getName()); -// center.setAddress(centerRegistRequestDTO.getAddress()); -// center.setPhone(centerRegistRequestDTO.getPhone()); -// center.setMemberCount(centerRegistRequestDTO.getMemberCount()); -// center.setOperatingAt(centerRegistRequestDTO.getOperatingAt()); - - - Center center = modelMapper.map(centerRegistRequestDTO, Center.class); - center.setCreatedAt(center.getCreatedAt()); - center.setUpdatedAt(center.getUpdatedAt()); - - centerRepository.save(center); - - CenterRegistResponseDTO centerRegistResponseDTO = modelMapper.map(center, CenterRegistResponseDTO.class); - - return centerRegistResponseDTO; - } -} diff --git a/src/main/java/stanl_2/final_backend/domain/center/command/domain/aggregate/entity/Center.java b/src/main/java/stanl_2/final_backend/domain/center/command/domain/aggregate/entity/Center.java index 99e43113..e8ef75dd 100644 --- a/src/main/java/stanl_2/final_backend/domain/center/command/domain/aggregate/entity/Center.java +++ b/src/main/java/stanl_2/final_backend/domain/center/command/domain/aggregate/entity/Center.java @@ -2,27 +2,30 @@ import jakarta.persistence.*; import lombok.*; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; import java.sql.Timestamp; import java.time.ZoneId; import java.time.ZonedDateTime; - -/* 설명. 테스트를 위한 어노테이션(나중에 삭제 예정)*/ -@ToString +import java.time.format.DateTimeFormatter; @Entity -@Table(name="CENTER") +@Table(name="TB_CENTER") @AllArgsConstructor @NoArgsConstructor @Setter @Getter public class Center { - /* 설명. 테스트를 위한 Long 선언 => mem_0000001 구현을 위해서는 고도화 진행 필요*/ @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "CENT_ID", nullable = false) - private Long id; + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name = "prefix", value = "CEN") + ) + @Column(name ="CENT_ID") + private String centerId; @Column(name = "CENT_NAME", nullable = false) private String name; @@ -40,36 +43,35 @@ public class Center { private String operatingAt; @Column(name = "CREATED_AT", nullable = false) - private Timestamp createdAt; + private String createdAt; @Column(name = "UPDATED_AT", nullable = false) - private Timestamp updatedAt; + private String updatedAt; @Column(name = "DELETED_AT") - private Timestamp deletedAt; + private String deletedAt; + + @Column(name = "IMAGE_URL") + private String imageUrl; @Column(name = "ACTIVE", nullable = false) private Boolean active = true; - - /* 설명. updatedAt 자동화 */ - // Insert 되기 전에 실행 @PrePersist - public void prePersist() { - Timestamp currentTimestamp = getCurrentTimestamp(); - this.createdAt = currentTimestamp; + private void prePersist() { + this.createdAt = getCurrentTime(); this.updatedAt = this.createdAt; } - // Update 되기 전에 실행 @PreUpdate - public void preUpdate() { - this.updatedAt = getCurrentTimestamp(); + private void preUpdate() { + this.updatedAt = getCurrentTime(); } - private Timestamp getCurrentTimestamp() { + private String getCurrentTime() { ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); - return Timestamp.from(nowKst.toInstant()); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } + } diff --git a/src/main/java/stanl_2/final_backend/domain/center/command/domain/service/CenterCommandServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/center/command/domain/service/CenterCommandServiceImpl.java new file mode 100644 index 00000000..09753336 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/center/command/domain/service/CenterCommandServiceImpl.java @@ -0,0 +1,84 @@ +package stanl_2.final_backend.domain.center.command.domain.service; + +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import stanl_2.final_backend.domain.center.command.application.dto.request.CenterModifyDTO; +import stanl_2.final_backend.domain.center.command.application.dto.request.CenterRegistDTO; +import stanl_2.final_backend.domain.center.command.application.service.CenterCommandService; +import stanl_2.final_backend.domain.center.command.domain.aggregate.entity.Center; +import stanl_2.final_backend.domain.center.command.domain.repository.CenterRepository; +import stanl_2.final_backend.domain.center.common.exception.CenterCommonException; +import stanl_2.final_backend.domain.center.common.exception.CenterErrorCode; +import stanl_2.final_backend.domain.s3.command.application.service.S3FileService; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@Service("commandCenterServiceImpl") +public class CenterCommandServiceImpl implements CenterCommandService { + + private final CenterRepository centerRepository; + private final ModelMapper modelMapper; + private final S3FileService s3FileService; + + @Autowired + public CenterCommandServiceImpl(CenterRepository centerRepository, ModelMapper modelMapper, S3FileService s3FileService) { + this.centerRepository = centerRepository; + this.modelMapper = modelMapper; + this.s3FileService = s3FileService; + } + + + @Override + @Transactional + public void registCenter(CenterRegistDTO centerRegistDTO, MultipartFile imageUrl) { + + Center newCenter = modelMapper.map(centerRegistDTO, Center.class); + + newCenter.setImageUrl(s3FileService.uploadOneFile(imageUrl)); + + centerRepository.save(newCenter); + } + + @Override + @Transactional + public void modifyCenter(CenterModifyDTO centerModifyDTO, MultipartFile imageUrl) { + Center center = centerRepository.findById(centerModifyDTO.getCenterId()) + .orElseThrow(() -> new CenterCommonException(CenterErrorCode.CENTER_NOT_FOUND)); + + s3FileService.deleteFile(center.getImageUrl()); + + Center updateCenter = modelMapper.map(centerModifyDTO, Center.class); + + updateCenter.setImageUrl(s3FileService.uploadOneFile(imageUrl)); + + updateCenter.setCenterId(center.getCenterId()); + updateCenter.setCreatedAt(center.getCreatedAt()); + updateCenter.setUpdatedAt(getCurrentTime()); + updateCenter.setActive(center.getActive()); + + centerRepository.save(updateCenter); + } + + @Override + @Transactional + public void deleteCenter(String id) { + Center center = centerRepository.findById(id) + .orElseThrow(() -> new CenterCommonException(CenterErrorCode.CENTER_NOT_FOUND)); + + center.setActive(false); + center.setDeletedAt(getCurrentTime()); + + centerRepository.save(center); + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/center/common/exception/CommonException.java b/src/main/java/stanl_2/final_backend/domain/center/common/exception/CenterCommonException.java similarity index 62% rename from src/main/java/stanl_2/final_backend/domain/center/common/exception/CommonException.java rename to src/main/java/stanl_2/final_backend/domain/center/common/exception/CenterCommonException.java index 6f9598f6..5b227b11 100644 --- a/src/main/java/stanl_2/final_backend/domain/center/common/exception/CommonException.java +++ b/src/main/java/stanl_2/final_backend/domain/center/common/exception/CenterCommonException.java @@ -5,12 +5,12 @@ @Getter @RequiredArgsConstructor -public class CommonException extends RuntimeException { - private final ErrorCode errorCode; +public class CenterCommonException extends RuntimeException { + private final CenterErrorCode centerErrorCode; // 에러 발생시 ErroCode 별 메시지 @Override public String getMessage() { - return this.errorCode.getMsg(); + return this.centerErrorCode.getMsg(); } } diff --git a/src/main/java/stanl_2/final_backend/domain/center/common/exception/CenterErrorCode.java b/src/main/java/stanl_2/final_backend/domain/center/common/exception/CenterErrorCode.java new file mode 100644 index 00000000..12175476 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/center/common/exception/CenterErrorCode.java @@ -0,0 +1,51 @@ +package stanl_2.final_backend.domain.center.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum CenterErrorCode { + + /** + * 400(Bad Request) + * 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. + */ + + + + /** + * 401(Unauthorized) + * 비록 HTTP 표준에서는 "미승인(unauthorized)"를 명확히 하고 있지만, + * 의미상 이 응답은 "비인증(unauthenticated)"을 의미합니다. + * 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로를 인증해야 합니다. + */ + + + /** + * 403(Forbidden) + * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. + * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. + */ + + + + /** + * 404(Not Found) + * 서버는 요청받은 리소스를 찾을 수 없습니다. 브라우저에서는 알려지지 않은 URL을 의미합니다. + * 이것은 API에서 종점은 적절하지만 리소스 자체는 존재하지 않음을 의미할 수도 있습니다. + * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. + * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. + */ + CENTER_NOT_FOUND(404001, HttpStatus.NOT_FOUND, "CENTER 데이터를 찾지 못했습니다"), + /** + * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. + */ + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); + + + private final Integer code; + private final HttpStatus httpStatus; + private final String msg; +} diff --git a/src/main/java/stanl_2/final_backend/domain/center/common/exception/CenterExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/center/common/exception/CenterExceptionResponse.java new file mode 100644 index 00000000..4c5000d5 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/center/common/exception/CenterExceptionResponse.java @@ -0,0 +1,22 @@ +package stanl_2.final_backend.domain.center.common.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class CenterExceptionResponse { + private final Integer code; + private final String msg; + private final HttpStatus httpStatus; + + public CenterExceptionResponse(CenterErrorCode centerErrorCode) { + this.code = centerErrorCode.getCode(); + this.msg = centerErrorCode.getMsg(); + this.httpStatus = centerErrorCode.getHttpStatus(); + } + + public static CenterExceptionResponse of(CenterErrorCode centerErrorCode) { + return new CenterExceptionResponse(centerErrorCode); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/center/common/response/ResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/center/common/response/CenterResponseMessage.java similarity index 85% rename from src/main/java/stanl_2/final_backend/domain/center/common/response/ResponseMessage.java rename to src/main/java/stanl_2/final_backend/domain/center/common/response/CenterResponseMessage.java index 60766c56..840dc755 100644 --- a/src/main/java/stanl_2/final_backend/domain/center/common/response/ResponseMessage.java +++ b/src/main/java/stanl_2/final_backend/domain/center/common/response/CenterResponseMessage.java @@ -7,7 +7,7 @@ @Builder @Getter @Setter -public class ResponseMessage { +public class CenterResponseMessage { private int httpStatus; private String msg; private Object result; diff --git a/src/main/java/stanl_2/final_backend/domain/center/query/controller/CenterController.java b/src/main/java/stanl_2/final_backend/domain/center/query/controller/CenterController.java index 2571517c..05f7cf4b 100644 --- a/src/main/java/stanl_2/final_backend/domain/center/query/controller/CenterController.java +++ b/src/main/java/stanl_2/final_backend/domain/center/query/controller/CenterController.java @@ -1,31 +1,151 @@ package stanl_2.final_backend.domain.center.query.controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import stanl_2.final_backend.domain.center.common.response.ResponseMessage; +import org.springframework.web.bind.annotation.*; +import stanl_2.final_backend.domain.center.common.response.CenterResponseMessage; +import stanl_2.final_backend.domain.center.query.dto.CenterSearchRequestDTO; +import stanl_2.final_backend.domain.center.query.dto.CenterSelectAllDTO; import stanl_2.final_backend.domain.center.query.dto.CenterSelectIdDTO; -import stanl_2.final_backend.domain.center.query.service.CenterService; +import stanl_2.final_backend.domain.center.query.service.CenterQueryService; + +import java.util.List; +import java.util.Map; @RestController("queryCenterController") @RequestMapping("/api/v1/center") public class CenterController { - private final CenterService centerService; + private final CenterQueryService centerQueryService; @Autowired - public CenterController(CenterService centerService) { - this.centerService = centerService; + public CenterController(CenterQueryService centerQueryService) { + this.centerQueryService = centerQueryService; + } + + @Operation(summary = "영업매장 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CenterResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("") + public ResponseEntity getCenterAll(@PageableDefault(size = 20) Pageable pageable, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder){ + + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + Page responseCenters = centerQueryService.selectAll(pageable); + + return ResponseEntity.ok(CenterResponseMessage.builder() + .httpStatus(200) + .msg("조회 성공") + .result(responseCenters) + .build()); } + @Operation(summary = "영업매장 상세 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CenterResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) @GetMapping("{centerId}") - public ResponseEntity getTest(@PathVariable Long centerId){ + public ResponseEntity getCenterById(@PathVariable("centerId") String centerId){ - CenterSelectIdDTO centerSelectIdDTO = centerService.selectByCenterId(centerId); + CenterSelectIdDTO centerSelectIdDTO = centerQueryService.selectByCenterId(centerId); - return ResponseEntity.ok(new ResponseMessage(200, "get 성공", centerSelectIdDTO)); + return ResponseEntity.ok(CenterResponseMessage.builder() + .httpStatus(200) + .msg("상세 조회 성공") + .result(centerSelectIdDTO) + .build()); } + + @Operation(summary = "영업매장 검색") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CenterResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/search") + public ResponseEntity getCenterBySearch(@RequestParam Map params + ,@PageableDefault(size = 20) Pageable pageable, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder){ + + CenterSearchRequestDTO centerSearchRequestDTO = new CenterSearchRequestDTO(); + centerSearchRequestDTO.setCenterId(params.get("centerId")); + centerSearchRequestDTO.setName(params.get("name")); + centerSearchRequestDTO.setAddress(params.get("address")); + + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + Page responseCenters = centerQueryService.selectBySearch(centerSearchRequestDTO, pageable); + + return ResponseEntity.ok(CenterResponseMessage.builder() + .httpStatus(200) + .msg("영업매장 검색 성공") + .result(responseCenters) + .build()); + } + + @Operation(summary = "영업매장리스트 검색(통계용)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CenterResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/searchList") + public ResponseEntity getCenterListBySearch(@RequestParam Map params){ + + CenterSearchRequestDTO centerSearchRequestDTO = new CenterSearchRequestDTO(); + centerSearchRequestDTO.setCenterId(params.get("centerId")); + centerSearchRequestDTO.setName(params.get("name")); + centerSearchRequestDTO.setAddress(params.get("address")); + + List responseCenters = centerQueryService.selectCenterListBySearch(centerSearchRequestDTO); + + return ResponseEntity.ok(CenterResponseMessage.builder() + .httpStatus(200) + .msg("영업매장리스트 검색(통계용) 성공") + .result(responseCenters) + .build()); + } + + @Operation(summary = "매장 엑셀 다운") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "매장 엑셀 다운 테스트 성공", + content = {@Content(schema = @Schema(implementation = CenterResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/excel") + public void exportCenter(HttpServletResponse response){ + + centerQueryService.exportCenterToExcel(response); + } + } diff --git a/src/main/java/stanl_2/final_backend/domain/center/query/dto/CenterExcelDownload.java b/src/main/java/stanl_2/final_backend/domain/center/query/dto/CenterExcelDownload.java new file mode 100644 index 00000000..d436ea3d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/center/query/dto/CenterExcelDownload.java @@ -0,0 +1,26 @@ +package stanl_2.final_backend.domain.center.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import stanl_2.final_backend.global.excel.ExcelColumnName; + +@Getter +@AllArgsConstructor +public class CenterExcelDownload { + + @ExcelColumnName(name = "매장번호") + private String centerId; + + @ExcelColumnName(name = "지점 이름") + private String name; + + @ExcelColumnName(name = "주소") + private String address; + + @ExcelColumnName(name = "사원 수") + private Integer memberCount; + + @ExcelColumnName(name = "운영시간") + private String operatingAt; + +} diff --git a/src/main/java/stanl_2/final_backend/domain/center/query/dto/CenterSearchRequestDTO.java b/src/main/java/stanl_2/final_backend/domain/center/query/dto/CenterSearchRequestDTO.java new file mode 100644 index 00000000..1b5d05ce --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/center/query/dto/CenterSearchRequestDTO.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.center.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class CenterSearchRequestDTO { + private String centerId; + private String name; + private String address; +} diff --git a/src/main/java/stanl_2/final_backend/domain/center/query/dto/CenterSelectAllDTO.java b/src/main/java/stanl_2/final_backend/domain/center/query/dto/CenterSelectAllDTO.java new file mode 100644 index 00000000..a786ff46 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/center/query/dto/CenterSelectAllDTO.java @@ -0,0 +1,21 @@ +package stanl_2.final_backend.domain.center.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class CenterSelectAllDTO { + private String centerId; + private String name; + private String address; + private String phone; + private Integer memberCount; + private String operatingAt; + private String createdAt; + private String updatedAt; +} diff --git a/src/main/java/stanl_2/final_backend/domain/center/query/dto/CenterSelectIdDTO.java b/src/main/java/stanl_2/final_backend/domain/center/query/dto/CenterSelectIdDTO.java index f2a00838..452b529f 100644 --- a/src/main/java/stanl_2/final_backend/domain/center/query/dto/CenterSelectIdDTO.java +++ b/src/main/java/stanl_2/final_backend/domain/center/query/dto/CenterSelectIdDTO.java @@ -1,25 +1,26 @@ package stanl_2.final_backend.domain.center.query.dto; -import lombok.*; -import java.sql.Timestamp; -import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; @AllArgsConstructor @NoArgsConstructor @Getter @Setter -@ToString public class CenterSelectIdDTO { - private Long id; + private String centerId; private String name; private String address; private String phone; private Integer memberCount; private String operatingAt; - private Timestamp createdAt; - private Timestamp updatedAt; - private Timestamp deletedAt; + private String createdAt; + private String updatedAt; + private String deletedAt; private Boolean active; + private String imageUrl; } diff --git a/src/main/java/stanl_2/final_backend/domain/center/query/repository/CenterMapper.java b/src/main/java/stanl_2/final_backend/domain/center/query/repository/CenterMapper.java index 44b2e042..3b225c77 100644 --- a/src/main/java/stanl_2/final_backend/domain/center/query/repository/CenterMapper.java +++ b/src/main/java/stanl_2/final_backend/domain/center/query/repository/CenterMapper.java @@ -1,9 +1,36 @@ package stanl_2.final_backend.domain.center.query.repository; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import stanl_2.final_backend.domain.center.query.dto.CenterExcelDownload; +import stanl_2.final_backend.domain.center.query.dto.CenterSearchRequestDTO; +import stanl_2.final_backend.domain.center.query.dto.CenterSelectAllDTO; import stanl_2.final_backend.domain.center.query.dto.CenterSelectIdDTO; +import java.util.List; + @Mapper public interface CenterMapper { - CenterSelectIdDTO findCenterById(Long centerId); + CenterSelectIdDTO findCenterById(String id); + + List findCenterAll(@Param("size") int size + , @Param("offset") int offset, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + Integer findCenterCount(); + + Integer findCenterBySearchCount(@Param("centerSearchRequestDTO") CenterSearchRequestDTO centerSearchRequestDTO); + + List findCenterBySearch(@Param("size") int size + , @Param("offset") int offset + , @Param("centerSearchRequestDTO") CenterSearchRequestDTO centerSearchRequestDTO, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + List findCenterListBySearch(@Param("centerSearchRequestDTO") CenterSearchRequestDTO centerSearchRequestDTO); + + String findNameById(@Param("id") String id); + + List findCentersForExcel(); } diff --git a/src/main/java/stanl_2/final_backend/domain/center/query/service/CenterQueryService.java b/src/main/java/stanl_2/final_backend/domain/center/query/service/CenterQueryService.java new file mode 100644 index 00000000..95ecffd6 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/center/query/service/CenterQueryService.java @@ -0,0 +1,25 @@ +package stanl_2.final_backend.domain.center.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import stanl_2.final_backend.domain.center.query.dto.CenterSearchRequestDTO; +import stanl_2.final_backend.domain.center.query.dto.CenterSelectAllDTO; +import stanl_2.final_backend.domain.center.query.dto.CenterSelectIdDTO; + +import java.util.List; + +public interface CenterQueryService { + CenterSelectIdDTO selectByCenterId(String id); + + Page selectAll(Pageable pageable); + + Page selectBySearch(CenterSearchRequestDTO centerSearchRequestDTO, Pageable pageable); + + List selectCenterListBySearch(CenterSearchRequestDTO centerSearchRequestDTO); + + String selectNameById(String id); + + void exportCenterToExcel(HttpServletResponse response); +} diff --git a/src/main/java/stanl_2/final_backend/domain/center/query/service/CenterQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/center/query/service/CenterQueryServiceImpl.java new file mode 100644 index 00000000..6b1bdb34 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/center/query/service/CenterQueryServiceImpl.java @@ -0,0 +1,110 @@ +package stanl_2.final_backend.domain.center.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.center.query.dto.CenterExcelDownload; +import stanl_2.final_backend.domain.center.query.dto.CenterSearchRequestDTO; +import stanl_2.final_backend.domain.center.query.dto.CenterSelectAllDTO; +import stanl_2.final_backend.domain.center.query.dto.CenterSelectIdDTO; +import stanl_2.final_backend.domain.center.query.repository.CenterMapper; +import stanl_2.final_backend.global.excel.ExcelUtilsV1; +import org.springframework.data.domain.Sort; + +import java.util.List; + +@Service("queryCenterServiceImpl") +public class CenterQueryServiceImpl implements CenterQueryService { + + private final CenterMapper centerMapper; + private final RedisTemplate redisTemplate; + private final ExcelUtilsV1 excelUtilsV1; + + @Autowired + public CenterQueryServiceImpl(CenterMapper centerMapper, RedisTemplate redisTemplate, ExcelUtilsV1 excelUtilsV1) { + this.centerMapper = centerMapper; + this.redisTemplate = redisTemplate; + this.excelUtilsV1 = excelUtilsV1; + } + + @Override + @Transactional + public CenterSelectIdDTO selectByCenterId(String id) { + + CenterSelectIdDTO centerSelectIdDTO = centerMapper.findCenterById(id); + + return centerSelectIdDTO; + } + + @Override + @Transactional + public Page selectAll(Pageable pageable) { + + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + List centerList = centerMapper.findCenterAll(size, offset, sortField, sortOrder); + + int total = centerMapper.findCenterCount(); + + return new PageImpl<>(centerList, pageable, total); + } + + @Override + @Transactional + public Page selectBySearch(CenterSearchRequestDTO centerSearchRequestDTO, Pageable pageable){ + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + List centerList = centerMapper.findCenterBySearch(size, offset, centerSearchRequestDTO, sortField, sortOrder); + int total = centerMapper.findCenterBySearchCount(centerSearchRequestDTO); + + return new PageImpl<>(centerList, pageable, total); + } + + @Override + @Transactional + public List selectCenterListBySearch(CenterSearchRequestDTO centerSearchRequestDTO){ + + List centerList = centerMapper.findCenterListBySearch(centerSearchRequestDTO); + + return centerList; + } + + @Override + @Transactional + public String selectNameById(String id) { + + String centerName = centerMapper.findNameById(id); + return centerName; + } + + @Override + @Transactional + public void exportCenterToExcel(HttpServletResponse response) { + List centerList = centerMapper.findCentersForExcel(); + + excelUtilsV1.download(CenterExcelDownload.class, centerList, "centerExcel", response); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/center/query/service/CenterService.java b/src/main/java/stanl_2/final_backend/domain/center/query/service/CenterService.java deleted file mode 100644 index 23e8aacc..00000000 --- a/src/main/java/stanl_2/final_backend/domain/center/query/service/CenterService.java +++ /dev/null @@ -1,7 +0,0 @@ -package stanl_2.final_backend.domain.center.query.service; - -import stanl_2.final_backend.domain.center.query.dto.CenterSelectIdDTO; - -public interface CenterService { - CenterSelectIdDTO selectByCenterId(Long centerId); -} diff --git a/src/main/java/stanl_2/final_backend/domain/center/query/service/CenterServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/center/query/service/CenterServiceImpl.java deleted file mode 100644 index 7acff264..00000000 --- a/src/main/java/stanl_2/final_backend/domain/center/query/service/CenterServiceImpl.java +++ /dev/null @@ -1,29 +0,0 @@ -package stanl_2.final_backend.domain.center.query.service; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import stanl_2.final_backend.domain.center.query.dto.CenterSelectIdDTO; -import stanl_2.final_backend.domain.center.query.repository.CenterMapper; - -@Slf4j -@Service("queryCenterServiceImpl") -public class CenterServiceImpl implements CenterService{ - - private final CenterMapper centerMapper; - - @Autowired - public CenterServiceImpl(CenterMapper centerMapper) { - this.centerMapper = centerMapper; - } - - @Override - @Transactional(readOnly = true) - public CenterSelectIdDTO selectByCenterId(Long centerId) { - - CenterSelectIdDTO centerSelectIdDTO = centerMapper.findCenterById(centerId); - - return centerSelectIdDTO; - } -} diff --git a/src/main/java/stanl_2/final_backend/domain/certification/command/application/controller/CertificationController.java b/src/main/java/stanl_2/final_backend/domain/certification/command/application/controller/CertificationController.java new file mode 100644 index 00000000..a6547067 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/certification/command/application/controller/CertificationController.java @@ -0,0 +1,54 @@ +package stanl_2.final_backend.domain.certification.command.application.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import stanl_2.final_backend.domain.career.common.response.CareerResponseMessage; +import stanl_2.final_backend.domain.certification.command.application.dto.CertificationRegisterDTO; +import stanl_2.final_backend.domain.certification.command.application.service.CertificationCommandService; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; + +import java.security.Principal; + +@RestController("commandCertificationController") +@RequestMapping("/api/v1/certification") +public class CertificationController { + + private final CertificationCommandService certificationCommandService; + private final AuthQueryService authQueryService; + + @Autowired + public CertificationController(CertificationCommandService certificationCommandService, + AuthQueryService authQueryService) { + this.certificationCommandService = certificationCommandService; + this.authQueryService = authQueryService; + } + + @Operation(summary = "자격증/외국어 등록") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CareerResponseMessage.class))}) + }) + @PostMapping("") + public ResponseEntity postCertification(@RequestBody CertificationRegisterDTO certificationRegisterDTO, + Principal principal){ + + certificationRegisterDTO.setMemberId(authQueryService.selectMemberIdByLoginId(principal.getName())); + + certificationCommandService.registCertification(certificationRegisterDTO); + + return ResponseEntity.ok(CareerResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(null) + .build()); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/certification/command/application/dto/CertificationRegisterDTO.java b/src/main/java/stanl_2/final_backend/domain/certification/command/application/dto/CertificationRegisterDTO.java new file mode 100644 index 00000000..4735b7c0 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/certification/command/application/dto/CertificationRegisterDTO.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.certification.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class CertificationRegisterDTO { + private String acquisitionDate; + private String agency; + private String name; + private String score; + private String note; + private String memberId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/certification/command/application/service/CertificationCommandService.java b/src/main/java/stanl_2/final_backend/domain/certification/command/application/service/CertificationCommandService.java new file mode 100644 index 00000000..980f18d3 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/certification/command/application/service/CertificationCommandService.java @@ -0,0 +1,7 @@ +package stanl_2.final_backend.domain.certification.command.application.service; + +import stanl_2.final_backend.domain.certification.command.application.dto.CertificationRegisterDTO; + +public interface CertificationCommandService { + void registCertification(CertificationRegisterDTO certificationRegisterDTO); +} diff --git a/src/main/java/stanl_2/final_backend/domain/certification/command/domain/aggregate/entity/Certification.java b/src/main/java/stanl_2/final_backend/domain/certification/command/domain/aggregate/entity/Certification.java new file mode 100644 index 00000000..eac2c4c0 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/certification/command/domain/aggregate/entity/Certification.java @@ -0,0 +1,62 @@ +package stanl_2.final_backend.domain.certification.command.domain.aggregate.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "TB_CERTIFICATION") +public class Certification { + @Id + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name = "prefix", value = "CER") + ) + @Column(name = "CER_ID", nullable = false) + private String certificationId; + + @Column(name = "CER_NAME", nullable = false) + private String name; + + @Column(name = "CER_INST", nullable = false) + private String agency; + + @Column(name = "CER_DATE", nullable = false) + private String acquisitionDate; + + @Column(name = "CER_SCO", nullable = false) + private String score; + + @Column(name = "CER_NOTE") + private String note; + + @Column(name = "CREATED_AT", nullable = false, updatable = false) + private String createdAt; + + @Column(name = "MEM_ID", nullable = false) + private String memberId; + + // Insert 되기 전에 실행 + @PrePersist + private void prePersist() { + this.createdAt = getCurrentTime(); + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/certification/command/domain/repository/CertificationRepository.java b/src/main/java/stanl_2/final_backend/domain/certification/command/domain/repository/CertificationRepository.java new file mode 100644 index 00000000..a7cebf49 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/certification/command/domain/repository/CertificationRepository.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.certification.command.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import stanl_2.final_backend.domain.certification.command.domain.aggregate.entity.Certification; + +@Repository +public interface CertificationRepository extends JpaRepository { +} diff --git a/src/main/java/stanl_2/final_backend/domain/certification/command/domain/service/CertificationCommandServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/certification/command/domain/service/CertificationCommandServiceImpl.java new file mode 100644 index 00000000..a2cf52f1 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/certification/command/domain/service/CertificationCommandServiceImpl.java @@ -0,0 +1,33 @@ +package stanl_2.final_backend.domain.certification.command.domain.service; + +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.certification.command.application.dto.CertificationRegisterDTO; +import stanl_2.final_backend.domain.certification.command.application.service.CertificationCommandService; +import stanl_2.final_backend.domain.certification.command.domain.aggregate.entity.Certification; +import stanl_2.final_backend.domain.certification.command.domain.repository.CertificationRepository; + +@Service("commandCertificationService") +public class CertificationCommandServiceImpl implements CertificationCommandService { + + private final CertificationRepository certificationRepository; + private final ModelMapper modelMapper; + + @Autowired + public CertificationCommandServiceImpl(CertificationRepository certificationRepository, + ModelMapper modelMapper) { + this.certificationRepository = certificationRepository; + this.modelMapper = modelMapper; + } + + @Override + @Transactional + public void registCertification(CertificationRegisterDTO certificationRegisterDTO) { + + Certification certification = modelMapper.map(certificationRegisterDTO, Certification.class); + + certificationRepository.save(certification); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/certification/common/exception/CertificationCommonException.java b/src/main/java/stanl_2/final_backend/domain/certification/common/exception/CertificationCommonException.java new file mode 100644 index 00000000..2197fe72 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/certification/common/exception/CertificationCommonException.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.certification.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class CertificationCommonException extends RuntimeException { + private final CertificationErrorCode sampleErrorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.sampleErrorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/certification/common/exception/CertificationErrorCode.java b/src/main/java/stanl_2/final_backend/domain/certification/common/exception/CertificationErrorCode.java new file mode 100644 index 00000000..66d1d90b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/certification/common/exception/CertificationErrorCode.java @@ -0,0 +1,52 @@ +package stanl_2.final_backend.domain.certification.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum CertificationErrorCode { + + /** + * 400(Bad Request) + * 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. + */ + + + + /** + * 401(Unauthorized) + * 비록 HTTP 표준에서는 "미승인(unauthorized)"를 명확히 하고 있지만, + * 의미상 이 응답은 "비인증(unauthenticated)"을 의미합니다. + * 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로를 인증해야 합니다. + */ + + + /** + * 403(Forbidden) + * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. + * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. + */ + + + + /** + * 404(Not Found) + * 서버는 요청받은 리소스를 찾을 수 없습니다. 브라우저에서는 알려지지 않은 URL을 의미합니다. + * 이것은 API에서 종점은 적절하지만 리소스 자체는 존재하지 않음을 의미할 수도 있습니다. + * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. + * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. + */ + SAMPLE_NOT_FOUND(404001, HttpStatus.NOT_FOUND, "sample 데이터를 찾지 못했습니다"), + CENTER_NOT_FOUND(404002, HttpStatus.NOT_FOUND, "center 데이터를 찾지 못했습니다."), + /** + * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. + */ + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); + + + private final Integer code; + private final HttpStatus httpStatus; + private final String msg; +} diff --git a/src/main/java/stanl_2/final_backend/domain/certification/common/exception/CertificationExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/certification/common/exception/CertificationExceptionResponse.java new file mode 100644 index 00000000..69e7d7ac --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/certification/common/exception/CertificationExceptionResponse.java @@ -0,0 +1,22 @@ +package stanl_2.final_backend.domain.certification.common.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class CertificationExceptionResponse { + private final Integer code; + private final String msg; + private final HttpStatus httpStatus; + + public CertificationExceptionResponse(CertificationErrorCode sampleErrorCode) { + this.code = sampleErrorCode.getCode(); + this.msg = sampleErrorCode.getMsg(); + this.httpStatus = sampleErrorCode.getHttpStatus(); + } + + public static CertificationExceptionResponse of(CertificationErrorCode sampleErrorCode) { + return new CertificationExceptionResponse(sampleErrorCode); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/certification/common/response/CertificationResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/certification/common/response/CertificationResponseMessage.java new file mode 100644 index 00000000..4185ab6d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/certification/common/response/CertificationResponseMessage.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.certification.common.response; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +public class CertificationResponseMessage { + private int httpStatus; + private String msg; + private Object result; +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/certification/query/config/MybatisConfiguration.java b/src/main/java/stanl_2/final_backend/domain/certification/query/config/MybatisConfiguration.java new file mode 100644 index 00000000..86808188 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/certification/query/config/MybatisConfiguration.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.certification.query.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Configuration; + +@Configuration("certificationMybatisConfiguration") +@MapperScan(basePackages = "stanl_2.final_backend.domain.certification.query.repository") +public class MybatisConfiguration { +} diff --git a/src/main/java/stanl_2/final_backend/domain/certification/query/controller/CertificationController.java b/src/main/java/stanl_2/final_backend/domain/certification/query/controller/CertificationController.java new file mode 100644 index 00000000..70ffe9c5 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/certification/query/controller/CertificationController.java @@ -0,0 +1,69 @@ +package stanl_2.final_backend.domain.certification.query.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import stanl_2.final_backend.domain.career.common.response.CareerResponseMessage; +import stanl_2.final_backend.domain.certification.query.dto.CertificationDTO; +import stanl_2.final_backend.domain.certification.query.service.CertificationQueryService; + +import java.security.Principal; +import java.util.List; + +@RestController(value = "queryCertificationController") +@RequestMapping("/api/v1/certification") +public class CertificationController { + + private final CertificationQueryService certificationQueryService; + + @Autowired + public CertificationController(CertificationQueryService certificationQueryService) { + this.certificationQueryService = certificationQueryService; + } + + @Operation(summary = "자격증/외국어 조회(with 사번)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CareerResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/other/{loginId}") + public ResponseEntity getCertificationByOther(@PathVariable String loginId){ + + List certificationList = certificationQueryService.selectCertificationList(loginId); + + return ResponseEntity.ok(CareerResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(certificationList) + .build()); + } + + @Operation(summary = "자격증/외국어 조회(접속중인 사용자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CareerResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("") + public ResponseEntity getCertification(Principal principal){ + + List careerList = certificationQueryService.selectCertificationList(principal.getName()); + + return ResponseEntity.ok(CareerResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(careerList) + .build()); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/certification/query/dto/CertificationDTO.java b/src/main/java/stanl_2/final_backend/domain/certification/query/dto/CertificationDTO.java new file mode 100644 index 00000000..791c4cee --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/certification/query/dto/CertificationDTO.java @@ -0,0 +1,15 @@ +package stanl_2.final_backend.domain.certification.query.dto; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Setter +@Getter +public class CertificationDTO { + private String name; + private String agency; + private String acquisitionDate; + private String score; + private String note; +} diff --git a/src/main/java/stanl_2/final_backend/domain/certification/query/repository/CertificationMapper.java b/src/main/java/stanl_2/final_backend/domain/certification/query/repository/CertificationMapper.java new file mode 100644 index 00000000..ebd7eee5 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/certification/query/repository/CertificationMapper.java @@ -0,0 +1,12 @@ +package stanl_2.final_backend.domain.certification.query.repository; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import stanl_2.final_backend.domain.certification.query.dto.CertificationDTO; + +import java.util.List; + +@Mapper +public interface CertificationMapper { + List selectCertificationInfo(@Param("memberId") String memberId); +} diff --git a/src/main/java/stanl_2/final_backend/domain/certification/query/service/CertificationQueryService.java b/src/main/java/stanl_2/final_backend/domain/certification/query/service/CertificationQueryService.java new file mode 100644 index 00000000..f138996f --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/certification/query/service/CertificationQueryService.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.certification.query.service; + +import stanl_2.final_backend.domain.certification.query.dto.CertificationDTO; + +import java.util.List; + +public interface CertificationQueryService { + List selectCertificationList(String loginId); +} diff --git a/src/main/java/stanl_2/final_backend/domain/certification/query/service/CertificationQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/certification/query/service/CertificationQueryServiceImpl.java new file mode 100644 index 00000000..21ad4b13 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/certification/query/service/CertificationQueryServiceImpl.java @@ -0,0 +1,35 @@ +package stanl_2.final_backend.domain.certification.query.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.certification.query.dto.CertificationDTO; +import stanl_2.final_backend.domain.certification.query.repository.CertificationMapper; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; + +import java.util.List; + +@Service("queryCertificationService") +public class CertificationQueryServiceImpl implements CertificationQueryService { + + private final CertificationMapper certificationMapper; + private final AuthQueryService authQueryService; + + @Autowired + public CertificationQueryServiceImpl(CertificationMapper certificationMapper, + AuthQueryService authQueryService) { + this.certificationMapper = certificationMapper; + this.authQueryService = authQueryService; + } + + @Override + @Transactional(readOnly = true) + public List selectCertificationList(String loginId) { + + String memberId = authQueryService.selectMemberIdByLoginId(loginId); + + List certificationList = certificationMapper.selectCertificationInfo(memberId); + + return certificationList; + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/claude/ClaudeApiService.java b/src/main/java/stanl_2/final_backend/domain/claude/ClaudeApiService.java new file mode 100644 index 00000000..99b88057 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/claude/ClaudeApiService.java @@ -0,0 +1,47 @@ +package stanl_2.final_backend.domain.claude; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +@Service +public class ClaudeApiService { + + @Value("${claude.api.key}") + private String apiKey; + + @Value("${claude.api.url}") + private String apiUrl; + + private final RestTemplate restTemplate; + + public ClaudeApiService(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + public String getSummary(String comment) { + + String requestBody = String.format("{\"text\": \"%s\"}", comment); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", "Bearer " + apiKey); + + HttpEntity entity = new HttpEntity<>(requestBody, headers); + + try { + ResponseEntity response = restTemplate.exchange(apiUrl, HttpMethod.POST, entity, String.class); + + if (response.getStatusCode() == HttpStatus.OK) { + return response.getBody(); // Return the summary text + } else { + // 외부 API에서 오류가 발생한 경우 + return "Error: " + response.getStatusCode() + " - " + response.getBody(); + } + } catch (Exception e) { + // 예외 처리: API 호출 실패 + return "Error: Unable to connect to Claude API. " + e.getMessage(); + } + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/claude/ClaudeController.java b/src/main/java/stanl_2/final_backend/domain/claude/ClaudeController.java new file mode 100644 index 00000000..1cfcd2df --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/claude/ClaudeController.java @@ -0,0 +1,37 @@ +package stanl_2.final_backend.domain.claude; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import stanl_2.final_backend.domain.claude.dto.RequestDTO; + +@RestController +@RequestMapping("/api/v1/claude") +public class ClaudeController { + + private final ClaudeApiService claudeApiService; + + @Autowired + public ClaudeController(ClaudeApiService claudeApiService) { + this.claudeApiService = claudeApiService; + } + + @PostMapping("summary") + public ResponseEntity getSummary(@RequestBody RequestDTO requestDTO) { + + try { + String summary = claudeApiService.getSummary(requestDTO.getComment()); + if (summary != null) { + return ResponseEntity.ok(summary); + } else { + return ResponseEntity.status(500).body("Failed to get summary."); + } + } catch (Exception e) { + // 로깅 및 예외 처리 + return ResponseEntity.status(500).body("An error occurred while processing your request: " + e.getMessage()); + } + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/claude/dto/RequestDTO.java b/src/main/java/stanl_2/final_backend/domain/claude/dto/RequestDTO.java new file mode 100644 index 00000000..3100852a --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/claude/dto/RequestDTO.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.claude.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class RequestDTO { + private String comment; +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/command/application/controller/ContractController.java b/src/main/java/stanl_2/final_backend/domain/contract/command/application/controller/ContractController.java new file mode 100644 index 00000000..618c667d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/command/application/controller/ContractController.java @@ -0,0 +1,116 @@ +package stanl_2.final_backend.domain.contract.command.application.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stanl_2.final_backend.domain.contract.command.application.dto.ContractDeleteDTO; +import stanl_2.final_backend.domain.contract.command.application.dto.ContractModifyDTO; +import stanl_2.final_backend.domain.contract.command.application.dto.ContractRegistDTO; +import stanl_2.final_backend.domain.contract.command.application.dto.ContractStatusModifyDTO; +import stanl_2.final_backend.domain.contract.command.application.service.ContractCommandService; +import stanl_2.final_backend.domain.contract.common.response.ContractResponseMessage; + +import java.security.GeneralSecurityException; +import java.security.Principal; +@Slf4j +@RestController("commandContractController") +@RequestMapping("/api/v1/contract") +public class ContractController { + + private final ContractCommandService contractCommandService; + + @Autowired + public ContractController(ContractCommandService contractCommandService) { + this.contractCommandService = contractCommandService; + } + + @Operation(summary = "계약서 등록(영업사원, 관리자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "계약서 등록 성공", + content = {@Content(schema = @Schema(implementation = ContractResponseMessage.class))}) + }) + @PostMapping("") + public ResponseEntity postContract(@RequestBody ContractRegistDTO contractRegistRequestDTO, + Principal principal) throws GeneralSecurityException { + + contractRegistRequestDTO.setMemberId(principal.getName()); + contractCommandService.registerContract(contractRegistRequestDTO); + + return ResponseEntity.ok(ContractResponseMessage.builder() + .httpStatus(200) + .msg("계약서가 성공적으로 등록되었습니다.") + .result(null) + .build()); + } + + @Operation(summary = "계약서 수정(영업사원, 관리자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "계약서 수정 성공", + content = {@Content(schema = @Schema(implementation = ContractResponseMessage.class))}) + }) + @PutMapping("{contractId}") + public ResponseEntity putContract(@PathVariable String contractId, + Principal principal, + @RequestBody ContractModifyDTO contractModifyRequestDTO) throws GeneralSecurityException { + + contractModifyRequestDTO.setContractId(contractId); + contractModifyRequestDTO.setMemberId(principal.getName()); + contractCommandService.modifyContract(contractModifyRequestDTO); + + return ResponseEntity.ok(ContractResponseMessage.builder() + .httpStatus(200) + .msg("계약서가 성공적으로 수정되었습니다.") + .result(null) + .build()); + } + + @Operation(summary = "계약서 삭제(영업사원, 관리자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "계약서 삭제 성공", + content = {@Content(schema = @Schema(implementation = ContractResponseMessage.class))}) + }) + @DeleteMapping("{contractId}") + public ResponseEntity deleteContract(@PathVariable String contractId) { + + ContractDeleteDTO contractDeleteDTO = new ContractDeleteDTO(); + contractDeleteDTO.setContractId(contractId); + contractCommandService.deleteContract(contractDeleteDTO); + + return ResponseEntity.ok(ContractResponseMessage.builder() + .httpStatus(200) + .msg("계약서를 성공적으로 삭제하였습니다.") + .result(null) + .build()); + } + + @Operation(summary = "계약서 승인상태 수정(관리자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "계약서 승인상태 수정 성공", + content = {@Content(schema = @Schema(implementation = ContractResponseMessage.class))}) + }) + @PutMapping("/status/{contractId}") + public ResponseEntity putContractStatus(@PathVariable String contractId, + @RequestBody ContractStatusModifyDTO contractStatusModifyDTO, + Principal principal) throws GeneralSecurityException { + + // DTO에 설정 + contractStatusModifyDTO.setContractId(contractId); + contractStatusModifyDTO.setAdminId(principal.getName()); + + // 서비스 호출 + contractCommandService.modifyContractStatus(contractStatusModifyDTO); + + return ResponseEntity.ok(ContractResponseMessage.builder() + .httpStatus(200) + .msg("계약서 승인 상태가 성공적으로 변경되었습니다.") + .result(null) + .build()); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/command/application/dto/ContractAlarmDTO.java b/src/main/java/stanl_2/final_backend/domain/contract/command/application/dto/ContractAlarmDTO.java new file mode 100644 index 00000000..de574b0a --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/command/application/dto/ContractAlarmDTO.java @@ -0,0 +1,18 @@ +package stanl_2.final_backend.domain.contract.command.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class ContractAlarmDTO { + + private String contractId; + private String customerName; + private String memberId; + private String adminId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/command/application/dto/ContractDeleteDTO.java b/src/main/java/stanl_2/final_backend/domain/contract/command/application/dto/ContractDeleteDTO.java new file mode 100644 index 00000000..6248c747 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/command/application/dto/ContractDeleteDTO.java @@ -0,0 +1,19 @@ +package stanl_2.final_backend.domain.contract.command.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class ContractDeleteDTO { + private String contractId; + private String memberId; + private Collection roles; +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/command/application/dto/ContractModifyDTO.java b/src/main/java/stanl_2/final_backend/domain/contract/command/application/dto/ContractModifyDTO.java new file mode 100644 index 00000000..4bc04bb6 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/command/application/dto/ContractModifyDTO.java @@ -0,0 +1,46 @@ +package stanl_2.final_backend.domain.contract.command.application.dto; + +import lombok.*; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class ContractModifyDTO { + + private String contractId; + private String title; + private String customerName; + private String customerSex; + private String customerIdentifiNo; + private Integer customerAge; + private String customerAddress; + private String customerEmail; + private String customerPhone; + private String companyName; + private String carName; + private String customerClassifcation; + private String customerPurchaseCondition; + private String serialNum; + private String selectOption; + private Integer downPayment; + private Integer intermediatePayment; + private Integer vehiclePrice; + private Integer remainderPayment; + private Integer consignmentPayment; + private Integer totalSales; + private String deliveryDate; + private String deliveryLocation; + private String status; + private String numberOfVehicles; + private String createdUrl; + private String createdAt; + private String updatedAt; + private String memberId; + private String centerId; + private String customerId; + private String productId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/command/application/dto/ContractRegistDTO.java b/src/main/java/stanl_2/final_backend/domain/contract/command/application/dto/ContractRegistDTO.java new file mode 100644 index 00000000..31c8d9fa --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/command/application/dto/ContractRegistDTO.java @@ -0,0 +1,40 @@ +package stanl_2.final_backend.domain.contract.command.application.dto; + +import lombok.*; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class ContractRegistDTO { + + private String title; + private String customerName; + private String customerSex; + private String customerIdentifiNo; + private Integer customerAge; + private String customerAddress; + private String customerEmail; + private String customerPhone; + private String companyName; + private String customerClassifcation; + private String customerPurchaseCondition; + private String serialNum; + private String selectOption; + private Integer downPayment; + private Integer vehiclePrice; + private Integer intermediatePayment; + private Integer remainderPayment; + private Integer consignmentPayment; + private Integer totalSales; + private String carName; + private String deliveryDate; + private String deliveryLocation; + private String status; + private String numberOfVehicles; + private String createdUrl; + private String memberId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/command/application/dto/ContractStatusModifyDTO.java b/src/main/java/stanl_2/final_backend/domain/contract/command/application/dto/ContractStatusModifyDTO.java new file mode 100644 index 00000000..8c479cca --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/command/application/dto/ContractStatusModifyDTO.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.contract.command.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class ContractStatusModifyDTO { + private String contractId; + private String status; + private String adminId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/command/application/dto/UpdateHistoryRegistDTO.java b/src/main/java/stanl_2/final_backend/domain/contract/command/application/dto/UpdateHistoryRegistDTO.java new file mode 100644 index 00000000..330cf755 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/command/application/dto/UpdateHistoryRegistDTO.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.contract.command.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class UpdateHistoryRegistDTO { + private String content; + private String contractId; + private String memberId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/command/application/service/ContractCommandService.java b/src/main/java/stanl_2/final_backend/domain/contract/command/application/service/ContractCommandService.java new file mode 100644 index 00000000..55892140 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/command/application/service/ContractCommandService.java @@ -0,0 +1,18 @@ +package stanl_2.final_backend.domain.contract.command.application.service; + +import stanl_2.final_backend.domain.contract.command.application.dto.ContractDeleteDTO; +import stanl_2.final_backend.domain.contract.command.application.dto.ContractModifyDTO; +import stanl_2.final_backend.domain.contract.command.application.dto.ContractRegistDTO; +import stanl_2.final_backend.domain.contract.command.application.dto.ContractStatusModifyDTO; + +import java.security.GeneralSecurityException; + +public interface ContractCommandService { + void registerContract(ContractRegistDTO contractRegistRequestDTO) throws GeneralSecurityException; + + void modifyContract(ContractModifyDTO contractModifyRequestDTO) throws GeneralSecurityException; + + void deleteContract(ContractDeleteDTO contractDeleteDTO); + + void modifyContractStatus(ContractStatusModifyDTO contractStatusModifyDTO) throws GeneralSecurityException; +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/command/domain/aggregate/entity/Contract.java b/src/main/java/stanl_2/final_backend/domain/contract/command/domain/aggregate/entity/Contract.java new file mode 100644 index 00000000..3df5315b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/command/domain/aggregate/entity/Contract.java @@ -0,0 +1,158 @@ +package stanl_2.final_backend.domain.contract.command.domain.aggregate.entity; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + +import java.sql.Timestamp; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "TB_CONTRACT") +public class Contract { + + @Id + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name = "prefix", value = "CON") + ) + @Column(name = "CONR_ID") + private String contractId; + + @Column(name = "CONR_TTL", nullable = false) + private String title; + + @Column(name = "CONR_CUST_NAME", nullable = false) + private String customerName; + + @Column(name = "CONR_CUST_SEX", nullable = false) + private String customerSex; + + @Column(name = "CONR_CUST_IDEN_NO", nullable = false) + private String customerIdentifiNo; + + @Column(name = "CONR_CUST_AGE", nullable = false) + private String customerAge; + + @Column(name = "CONR_CUST_ADR", nullable = false) + private String customerAddress; + + @Column(name = "CONR_CUST_EMA", nullable = false) + private String customerEmail; + + @Column(name = "CONR_CUST_PHO", nullable = false) + private String customerPhone; + + @Column(name = "CONR_COMP_NAME") + private String companyName; + + @Column(name = "CONR_CUST_CLA", nullable = false) + @ColumnDefault("'PERSONAL'") + private String customerClassifcation; + + @Column(name = "CONR_CUST_PUR_COND", nullable = false) + @ColumnDefault("'CASH'") + private String customerPurchaseCondition; + + @Column(name = "CONR_SERI_NUM", nullable = false) + private String serialNum; + + @Column(name = "CONR_SELE_OPTI", nullable = false) + private String selectOption; + + @Column(name = "CONR_DOWN_PAY", nullable = false) + private Integer downPayment; + + @Column(name = "CONR_INTE_PAY", nullable = false) + private Integer intermediatePayment; + + @Column(name = "CONR_REM_PAY", nullable = false) + private Integer remainderPayment; + + @Column(name = "CONR_CONS_PAY", nullable = false) + private Integer consignmentPayment; + + @Column(name = "CONR_DELV_DATE") + private String deliveryDate; + + @Column(name = "CONR_DELV_LOC") + private String deliveryLocation; + + @Column(name = "CONR_CAR_NAME") + private String carName; + + @Column(name = "CONR_STAT", nullable = false) + private String status = "WAIT"; + + @Column(name = "CONR_NO_OF_VEH", nullable = false) + private Integer numberOfVehicles = 1; + + @Column(name = "CONR_TOTA_SALE", nullable = false) + private Integer totalSales = 0; + + @Column(name = "CONR_VEHI_PRIC", nullable = false) + private Integer vehiclePrice = 0; + + @Lob + @Column(name = "CREATED_URL", nullable = false, columnDefinition = "TEXT") + private String createdUrl; + + @Lob + @Column(name = "DELETED_URL", columnDefinition = "TEXT") + private String deletedUrl; + + @Column(name = "ACTIVE", nullable = false) + private boolean active = true; + + @Column(name = "CREATED_AT", nullable = false, updatable = false) + private String createdAt; + + @Column(name = "UPDATED_AT", nullable = false) + private String updatedAt; + + @Column(name = "DELETED_AT") + private String deletedAt; + + @Column(name = "MEM_ID", nullable = false) + private String memberId; + + @Column(name = "ADMI_ID") + private String adminId; + + @Column(name = "CENT_ID", nullable = false) + private String centerId; + + @Column(name = "CUST_ID", nullable = false) + private String customerId; + + @Column(name = "PROD_ID", nullable = false) + private String productId; + + /* 설명. updatedAt 자동화 */ + // Insert 되기 전에 실행 + @PrePersist + private void prePersist() { + this.createdAt = getCurrentTime(); + this.updatedAt = this.createdAt; + } + + // Update 되기 전에 실행 + @PreUpdate + private void preUpdate() { + this.updatedAt = getCurrentTime(); + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/command/domain/aggregate/entity/UpdateHistory.java b/src/main/java/stanl_2/final_backend/domain/contract/command/domain/aggregate/entity/UpdateHistory.java new file mode 100644 index 00000000..faaae2e7 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/command/domain/aggregate/entity/UpdateHistory.java @@ -0,0 +1,51 @@ +package stanl_2.final_backend.domain.contract.command.domain.aggregate.entity; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "TB_UPDATE_HISTORY") +public class UpdateHistory { + + @Id + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name = "prefix", value = "UPD_HIS") + ) + @Column(name = "UPD_ID", nullable = false) + private String updateHistoryId; + + @Column(name = "CREATED_AT", nullable = false) + private String createdAt; + + @Column(name = "UPD_CONT", nullable = false) + private String content; + + @Column(name = "CONR_ID", nullable = false) + private String contractId; + + @Column(name = "MEM_ID", nullable = false) + private String memberId; + + @PrePersist + private void prePersist() { + this.createdAt = getCurrentTime(); + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/command/domain/repository/ContractRepository.java b/src/main/java/stanl_2/final_backend/domain/contract/command/domain/repository/ContractRepository.java new file mode 100644 index 00000000..e6ba9d8c --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/command/domain/repository/ContractRepository.java @@ -0,0 +1,12 @@ +package stanl_2.final_backend.domain.contract.command.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import stanl_2.final_backend.domain.contract.command.domain.aggregate.entity.Contract; + +import java.util.Optional; + +public interface ContractRepository extends JpaRepository { + Optional findByContractIdAndMemberId(String contractId, String memberId); + + Contract findByContractId(String contractId); +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/command/domain/repository/UpdateHistoryRepository.java b/src/main/java/stanl_2/final_backend/domain/contract/command/domain/repository/UpdateHistoryRepository.java new file mode 100644 index 00000000..c4e31684 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/command/domain/repository/UpdateHistoryRepository.java @@ -0,0 +1,7 @@ +package stanl_2.final_backend.domain.contract.command.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import stanl_2.final_backend.domain.contract.command.domain.aggregate.entity.UpdateHistory; + +public interface UpdateHistoryRepository extends JpaRepository { +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/command/domain/service/ContractCommandServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/contract/command/domain/service/ContractCommandServiceImpl.java new file mode 100644 index 00000000..7eb107e9 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/command/domain/service/ContractCommandServiceImpl.java @@ -0,0 +1,335 @@ +package stanl_2.final_backend.domain.contract.command.domain.service; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringEscapeUtils; +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.alarm.command.application.service.AlarmCommandService; +import stanl_2.final_backend.domain.contract.command.application.dto.*; +import stanl_2.final_backend.domain.contract.command.application.service.ContractCommandService; +import stanl_2.final_backend.domain.contract.command.domain.aggregate.entity.Contract; +import stanl_2.final_backend.domain.contract.command.domain.aggregate.entity.UpdateHistory; +import stanl_2.final_backend.domain.contract.command.domain.repository.ContractRepository; +import stanl_2.final_backend.domain.contract.command.domain.repository.UpdateHistoryRepository; +import stanl_2.final_backend.domain.contract.common.exception.ContractCommonException; +import stanl_2.final_backend.domain.contract.common.exception.ContractErrorCode; +import stanl_2.final_backend.domain.customer.command.application.dto.CustomerModifyDTO; +import stanl_2.final_backend.domain.customer.command.application.dto.CustomerRegistDTO; +import stanl_2.final_backend.domain.customer.command.application.service.CustomerCommandService; +import stanl_2.final_backend.domain.customer.query.dto.CustomerDTO; +import stanl_2.final_backend.domain.customer.query.service.CustomerQueryService; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.domain.product.command.application.command.service.ProductCommandService; +import stanl_2.final_backend.domain.product.query.dto.ProductSelectIdDTO; +import stanl_2.final_backend.domain.product.query.service.ProductQueryService; +import stanl_2.final_backend.domain.s3.command.application.service.S3FileService; +import stanl_2.final_backend.domain.sales_history.command.application.service.SalesHistoryCommandService; +import stanl_2.final_backend.global.utils.AESUtils; + +import java.security.GeneralSecurityException; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@Slf4j +@Service("contractServiceImpl") +public class ContractCommandServiceImpl implements ContractCommandService { + + private final ContractRepository contractRepository; + private final UpdateHistoryRepository updateHistoryRepository; + private final AuthQueryService authQueryService; + private final CustomerQueryService customerQueryService; + private final MemberQueryService memberQueryService; + private final CustomerCommandService customerCommandService; + private final ProductQueryService productQueryService; + private final ProductCommandService productCommandService; + private final SalesHistoryCommandService salesHistoryCommandService; + private final ModelMapper modelMapper; + private final AESUtils aesUtils; + private final S3FileService s3FileService; + private final AlarmCommandService alarmCommandService; + + @Autowired + public ContractCommandServiceImpl(ContractRepository contractRepository, UpdateHistoryRepository updateHistoryRepository, + AuthQueryService authQueryService, CustomerQueryService customerQueryService, + MemberQueryService memberQueryService, CustomerCommandService customerCommandService, + ProductQueryService productQueryService, ProductCommandService productCommandService, + SalesHistoryCommandService salesHistoryCommandService, ModelMapper modelMapper, + AESUtils aesUtils, S3FileService s3FileService, AlarmCommandService alarmCommandService) { + this.contractRepository = contractRepository; + this.updateHistoryRepository = updateHistoryRepository; + this.authQueryService = authQueryService; + this.customerQueryService = customerQueryService; + this.memberQueryService = memberQueryService; + this.customerCommandService = customerCommandService; + this.productQueryService = productQueryService; + this.productCommandService = productCommandService; + this.salesHistoryCommandService = salesHistoryCommandService; + this.modelMapper = modelMapper; + this.aesUtils = aesUtils; + this.s3FileService = s3FileService; + this.alarmCommandService = alarmCommandService; + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + + private String handleCustomerInfo(ContractRegistDTO contractRegistRequestDTO, String memberId) throws GeneralSecurityException { + + // 고객 정보 조회 + CustomerDTO customerDTO = customerQueryService.selectCustomerInfoByPhone(contractRegistRequestDTO.getCustomerPhone()); + + if (customerDTO != null) { + + // 고객 정보 업데이트 + CustomerModifyDTO customerModifyDTO = new CustomerModifyDTO(); + customerModifyDTO.setCustomerId(customerDTO.getCustomerId()); + customerModifyDTO.setName(contractRegistRequestDTO.getCustomerName()); + customerModifyDTO.setAge(contractRegistRequestDTO.getCustomerAge()); + customerModifyDTO.setPhone(contractRegistRequestDTO.getCustomerPhone()); + customerModifyDTO.setEmail(contractRegistRequestDTO.getCustomerEmail()); + customerModifyDTO.setSex(contractRegistRequestDTO.getCustomerSex()); + customerModifyDTO.setMemberId(memberId); + + customerCommandService.modifyCustomerInfo(customerModifyDTO); + + return customerDTO.getCustomerId(); + } else if (customerDTO == null) { + CustomerRegistDTO customerRegistDTO = new CustomerRegistDTO(); + customerRegistDTO.setName(contractRegistRequestDTO.getCustomerName()); + customerRegistDTO.setAge(contractRegistRequestDTO.getCustomerAge()); + customerRegistDTO.setPhone(contractRegistRequestDTO.getCustomerPhone()); + customerRegistDTO.setEmail(contractRegistRequestDTO.getCustomerEmail()); + if (contractRegistRequestDTO.getCustomerSex().equals("여자")) { + customerRegistDTO.setSex("FEMALE"); + } else if (contractRegistRequestDTO.getCustomerSex().equals("남자")) { + customerRegistDTO.setSex("MALE"); + } + customerRegistDTO.setMemberId(memberId); + + // 고객 등록 + customerDTO = modelMapper.map(customerCommandService.registerCustomerInfo(customerRegistDTO), CustomerDTO.class); + + return customerDTO.getCustomerId(); + } + return customerDTO.getCustomerId(); + } + + private String updateCustomerInfo(ContractModifyDTO contractModifyDTO, String memberId) throws GeneralSecurityException { + // 고객 정보 조회 + CustomerDTO customerDTO = customerQueryService.selectCustomerInfoByPhone(contractModifyDTO.getCustomerPhone()); + + if (customerDTO != null) { + // 고객 정보 업데이트 + CustomerModifyDTO customerModifyDTO = new CustomerModifyDTO(); + customerModifyDTO.setCustomerId(customerDTO.getCustomerId()); + customerModifyDTO.setName(contractModifyDTO.getCustomerName()); + customerModifyDTO.setAge(contractModifyDTO.getCustomerAge()); + customerModifyDTO.setPhone(contractModifyDTO.getCustomerPhone()); + customerModifyDTO.setEmail(contractModifyDTO.getCustomerEmail()); + + if (contractModifyDTO.getCustomerSex().equals("여자")) { + customerModifyDTO.setSex("FEMALE"); + } else if (contractModifyDTO.getCustomerSex().equals("남자")) { + customerModifyDTO.setSex("MALE"); + } + + + customerModifyDTO.setMemberId(memberId); + + customerCommandService.modifyCustomerInfo(customerModifyDTO); + + // 업데이트된 고객 ID 반환 + return customerDTO.getCustomerId(); + } + + // 고객이 없으면 새로 등록 + CustomerRegistDTO customerRegistDTO = new CustomerRegistDTO(); + customerRegistDTO.setName(contractModifyDTO.getCustomerName()); + customerRegistDTO.setAge(contractModifyDTO.getCustomerAge()); + customerRegistDTO.setPhone(contractModifyDTO.getCustomerPhone()); + customerRegistDTO.setEmail(contractModifyDTO.getCustomerEmail()); + customerRegistDTO.setMemberId(memberId); + + // 등록 후 재조회 + customerDTO = modelMapper.map(customerCommandService.registerCustomerInfo(customerRegistDTO), CustomerDTO.class); + + if (customerDTO == null) { + throw new ContractCommonException(ContractErrorCode.CUSTOMER_NOT_FOUND); + } + + return customerDTO.getCustomerId(); + } + + @Override + @Transactional + public void registerContract(ContractRegistDTO contractRegistRequestDTO) throws GeneralSecurityException { + String memberId = authQueryService.selectMemberIdByLoginId(contractRegistRequestDTO.getMemberId()); + String productId = productQueryService.selectByProductSerialNumber(contractRegistRequestDTO.getSerialNum()).getProductId(); + String customerId = handleCustomerInfo(contractRegistRequestDTO, memberId); + String centerId = memberQueryService.selectMemberInfo(contractRegistRequestDTO.getMemberId()).getCenterId(); + + // 계약 생성 + String customerPurchaseCondition = contractRegistRequestDTO.getCustomerPurchaseCondition(); + String customerClassifcation = contractRegistRequestDTO.getCustomerClassifcation(); + String customerSex = contractRegistRequestDTO.getCustomerSex(); + + if (customerPurchaseCondition.equals("현금")) { + contractRegistRequestDTO.setCustomerPurchaseCondition("CASH"); + } else if (customerPurchaseCondition.equals("할부")) { + contractRegistRequestDTO.setCustomerPurchaseCondition("INSTALLMENT"); + } else if (customerPurchaseCondition.equals("리스")) { + contractRegistRequestDTO.setCustomerPurchaseCondition("LEASE"); + } + + if (customerClassifcation.equals("개인")) { + contractRegistRequestDTO.setCustomerClassifcation("PERSONAL"); + } else if (customerClassifcation.equals("법인")) { + contractRegistRequestDTO.setCustomerClassifcation("BUSINESS"); + } + + Contract contract = modelMapper.map(contractRegistRequestDTO, Contract.class); + contract.setMemberId(memberId); + contract.setCenterId(centerId); + contract.setProductId(productId); + contract.setCustomerId(customerId); + contract.setStatus("WAIT"); + + // 고객 정보 암호화 후 설정 + contract.setCustomerPhone(aesUtils.encrypt(contractRegistRequestDTO.getCustomerPhone())); + contract.setCustomerEmail(aesUtils.encrypt(contractRegistRequestDTO.getCustomerEmail())); + contract.setCustomerAddress(aesUtils.encrypt(contractRegistRequestDTO.getCustomerAddress())); + contract.setCustomerIdentifiNo(aesUtils.encrypt(contractRegistRequestDTO.getCustomerIdentifiNo())); + + String unescapedHtml = StringEscapeUtils.unescapeJson(contractRegistRequestDTO.getCreatedUrl()); + String updatedS3Url = s3FileService.uploadHtml(unescapedHtml, contractRegistRequestDTO.getTitle()); + + contract.setCreatedUrl(updatedS3Url); + + // 계약 저장 + contractRepository.save(contract); + } + + @Override + @Transactional + public void modifyContract(ContractModifyDTO contractModifyRequestDTO) throws GeneralSecurityException { + String memberId = authQueryService.selectMemberIdByLoginId(contractModifyRequestDTO.getMemberId()); + String productId = productQueryService.selectByProductSerialNumber(contractModifyRequestDTO.getSerialNum()).getProductId(); + String customerId = updateCustomerInfo(contractModifyRequestDTO, memberId); + String centerId = memberQueryService.selectMemberInfo(contractModifyRequestDTO.getMemberId()).getCenterId(); + + + // 계약 조회 + Contract contract = (Contract)contractRepository.findByContractId(contractModifyRequestDTO.getContractId()); + if (contract == null) { + throw new ContractCommonException(ContractErrorCode.CONTRACT_NOT_FOUND); + } + + // 계약 생성 + + String customerPurchaseCondition = contractModifyRequestDTO.getCustomerPurchaseCondition(); + String customerClassifcation = contractModifyRequestDTO.getCustomerClassifcation(); + + if (customerPurchaseCondition.equals("현금")) { + contractModifyRequestDTO.setCustomerPurchaseCondition("CASH"); + } else if (customerPurchaseCondition.equals("할부")) { + contractModifyRequestDTO.setCustomerPurchaseCondition("INSTALLMENT"); + } else if (customerPurchaseCondition.equals("리스")) { + contractModifyRequestDTO.setCustomerPurchaseCondition("LEASE"); + } + + if (customerClassifcation.equals("개인")) { + contractModifyRequestDTO.setCustomerClassifcation("PERSONAL"); + } else if (customerClassifcation.equals("법인")) { + contractModifyRequestDTO.setCustomerClassifcation("BUSINESS"); + } + + Contract updateContract = modelMapper.map(contractModifyRequestDTO, Contract.class); + updateContract.setMemberId(memberId); + updateContract.setCenterId(centerId); + updateContract.setProductId(productId); + updateContract.setCustomerId(customerId); + updateContract.setStatus("WAIT"); + + // 고객 정보가 수정된 경우 계약서의 고객 정보도 업데이트 + updateContract.setCustomerPhone(aesUtils.encrypt(contractModifyRequestDTO.getCustomerPhone())); + updateContract.setCustomerEmail(aesUtils.encrypt(contractModifyRequestDTO.getCustomerEmail())); + updateContract.setCustomerAddress(aesUtils.encrypt(contractModifyRequestDTO.getCustomerAddress())); + updateContract.setCustomerIdentifiNo(aesUtils.encrypt(contractModifyRequestDTO.getCustomerIdentifiNo())); + + // 수정된 계약 정보 저장 + contractRepository.save(updateContract); + + // 수정 이력 저장 + String unescapedHtml = StringEscapeUtils.unescapeJson(contractModifyRequestDTO.getCreatedUrl()); + String updatedS3Url = s3FileService.uploadHtml(unescapedHtml, contractModifyRequestDTO.getTitle()); + + UpdateHistoryRegistDTO updateHistoryRegistDTO = new UpdateHistoryRegistDTO(); + updateHistoryRegistDTO.setContent(updatedS3Url); + updateHistoryRegistDTO.setMemberId(memberId); + updateHistoryRegistDTO.setContractId(contractModifyRequestDTO.getContractId()); + + updateHistoryRepository.save(modelMapper.map(updateHistoryRegistDTO, UpdateHistory.class)); + } + + @Override + @Transactional + public void deleteContract(ContractDeleteDTO contractDeleteDTO) { + + Contract contract = (Contract) contractRepository.findByContractId(contractDeleteDTO.getContractId()); + + if (contract == null) { + throw new ContractCommonException(ContractErrorCode.CONTRACT_NOT_FOUND); + } + + contract.setActive(false); + contract.setDeletedAt(getCurrentTime()); + + contractRepository.save(contract); + } + + @Override + @Transactional + public void modifyContractStatus(ContractStatusModifyDTO contractStatusModifyDTO) throws GeneralSecurityException { + + // 관리자 ID 조회 + String adminId = authQueryService.selectMemberIdByLoginId(contractStatusModifyDTO.getAdminId()); + + // 계약 조회 및 수정 + Contract contract = contractRepository.findByContractId(contractStatusModifyDTO.getContractId()); + + if (contract == null) { + throw new ContractCommonException(ContractErrorCode.CONTRACT_NOT_FOUND); + } + contract.setStatus(contractStatusModifyDTO.getStatus()); + contract.setAdminId(adminId); + + contractRepository.save(contract); + + ContractAlarmDTO contractAlarmDTO = new ContractAlarmDTO(contract.getContractId(), contract.getCustomerName(), + contract.getMemberId(), contract.getAdminId()); + + alarmCommandService.sendContractAlarm(contractAlarmDTO); + + if (contractStatusModifyDTO.getStatus().equals("APPROVED")) { + // 판매 내역 등록 + salesHistoryCommandService.registerSalesHistory(contract.getContractId()); + + // 제품 재고 수 줄이기 + ProductSelectIdDTO productSelectIdDTO = productQueryService.selectByProductSerialNumber(contract.getSerialNum()); + String productId = productSelectIdDTO.getProductId(); + productCommandService.modifyProductStock(productId, contract.getNumberOfVehicles()); + } else if (contractStatusModifyDTO.getStatus().equals("CANCLED")) { + salesHistoryCommandService.deleteSalesHistory(contract.getContractId()); + + ProductSelectIdDTO productSelectIdDTO = productQueryService.selectByProductSerialNumber(contract.getSerialNum()); + String productId = productSelectIdDTO.getProductId(); + productCommandService.deleteProductStock(productId, contract.getNumberOfVehicles()); + } + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/common/exception/ContractCommonException.java b/src/main/java/stanl_2/final_backend/domain/contract/common/exception/ContractCommonException.java new file mode 100644 index 00000000..da28b8c1 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/common/exception/ContractCommonException.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.contract.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class ContractCommonException extends RuntimeException { + private final ContractErrorCode errorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.errorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/common/exception/ContractErrorCode.java b/src/main/java/stanl_2/final_backend/domain/contract/common/exception/ContractErrorCode.java new file mode 100644 index 00000000..cd190d81 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/common/exception/ContractErrorCode.java @@ -0,0 +1,56 @@ +package stanl_2.final_backend.domain.contract.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum ContractErrorCode { + + /** + * 400(Bad Request) + * 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. + */ + + + + /** + * 401(Unauthorized) + * 비록 HTTP 표준에서는 "미승인(unauthorized)"를 명확히 하고 있지만, + * 의미상 이 응답은 "비인증(unauthenticated)"을 의미합니다. + * 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로를 인증해야 합니다. + */ + + + /** + * 403(Forbidden) + * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. + * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. + */ + + + + /** + * 404(Not Found) + * 서버는 요청받은 리소스를 찾을 수 없습니다. 브라우저에서는 알려지지 않은 URL을 의미합니다. + * 이것은 API에서 종점은 적절하지만 리소스 자체는 존재하지 않음을 의미할 수도 있습니다. + * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. + * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. + */ + CONTRACT_NOT_FOUND(40401, HttpStatus.NOT_FOUND, "계약서를 찾을 수 없습니다."), + UPDATE_HISTORY_NOT_FOUND(40402, HttpStatus.NOT_FOUND, "수정내역에서 계약서를 찾을 수 없습니다."), + CUSTOMER_NOT_FOUND(40403, HttpStatus.NOT_FOUND, "고객 정보를 찾을 수 없습니다."), + + + /** + * 500(Internal Server Error) + * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. + */ + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); + + + private final Integer code; + private final HttpStatus httpStatus; + private final String msg; +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/common/exception/ContractExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/contract/common/exception/ContractExceptionResponse.java new file mode 100644 index 00000000..1f7f9b23 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/common/exception/ContractExceptionResponse.java @@ -0,0 +1,22 @@ +package stanl_2.final_backend.domain.contract.common.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class ContractExceptionResponse { + private final Integer code; + private final String msg; + private final HttpStatus httpStatus; + + public ContractExceptionResponse(ContractErrorCode errorCode) { + this.code = errorCode.getCode(); + this.msg = errorCode.getMsg(); + this.httpStatus = errorCode.getHttpStatus(); + } + + public static ContractExceptionResponse of(ContractErrorCode errorCode) { + return new ContractExceptionResponse(errorCode); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/common/response/ContractResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/contract/common/response/ContractResponseMessage.java new file mode 100644 index 00000000..bee55741 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/common/response/ContractResponseMessage.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.contract.common.response; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +public class ContractResponseMessage { + private int httpStatus; + private String msg; + private Object result; +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/contract/query/controller/ContractController.java b/src/main/java/stanl_2/final_backend/domain/contract/query/controller/ContractController.java new file mode 100644 index 00000000..ddc87aab --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/query/controller/ContractController.java @@ -0,0 +1,334 @@ +package stanl_2.final_backend.domain.contract.query.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stanl_2.final_backend.domain.contract.common.response.ContractResponseMessage; +import stanl_2.final_backend.domain.contract.query.dto.ContractSearchDTO; +import stanl_2.final_backend.domain.contract.query.dto.ContractSelectAllDTO; +import stanl_2.final_backend.domain.contract.query.dto.ContractSeletIdDTO; +import stanl_2.final_backend.domain.contract.query.service.ContractQueryService; + +import java.security.GeneralSecurityException; +import java.security.Principal; + +@Slf4j +@RestController("queryContractController") +@RequestMapping("/api/v1/contract") +public class ContractController { + + private final ContractQueryService contractQueryService; + + public ContractController(ContractQueryService contractQueryService) { + this.contractQueryService = contractQueryService; + } + + // 영업사원 조회 + @Operation(summary = "계약서 전체 조회(영업사원)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "계약서 전체 조회 성공", + content = {@Content(schema = @Schema(implementation = ContractResponseMessage.class))}) + }) + @GetMapping("employee") + public ResponseEntity getAllContractEmployee(@PageableDefault(size = 10) Pageable pageable, + Principal principal) { + + ContractSelectAllDTO contractSelectAllDTO = new ContractSelectAllDTO(); + contractSelectAllDTO.setMemberId(principal.getName()); + + Page responseContracts = contractQueryService.selectAllContractEmployee(contractSelectAllDTO, pageable); + + return ResponseEntity.ok(ContractResponseMessage.builder() + .httpStatus(200) + .msg("계약서 전체 조회 성공") + .result(responseContracts) + .build()); + } + + @Operation(summary = "계약서 상세 조회(영업사원)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "계약서 상세 조회 성공", + content = {@Content(schema = @Schema(implementation = ContractResponseMessage.class))}) + }) + @GetMapping("employee/{contractId}") + public ResponseEntity getDetailContractEmployee(@PathVariable String contractId, + Principal principal) { + + ContractSeletIdDTO contractSeletIdDTO = new ContractSeletIdDTO(); + contractSeletIdDTO.setContractId(contractId); + contractSeletIdDTO.setMemberId(principal.getName()); + + ContractSeletIdDTO responseContract = contractQueryService.selectDetailContractEmployee(contractSeletIdDTO); + + return ResponseEntity.ok(ContractResponseMessage.builder() + .httpStatus(200) + .msg("계약서 상세조회 성공") + .result(responseContract) + .build()); + } + + @Operation(summary = "계약서 검색 조회(영업사원)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "계약서 검색 조회 성공", + content = {@Content(schema = @Schema(implementation = ContractResponseMessage.class))}) + }) + @GetMapping("employee/search") + public ResponseEntity getContractBySearchEmployee(Principal principal, + @RequestParam(required = false) String searchMemberId, + @RequestParam(required = false) String centerId, + @RequestParam(required = false) String title, + @RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate, + @RequestParam(required = false) String customerName, + @RequestParam(required = false) String customerClassifcation, + @RequestParam(required = false) String productName, + @RequestParam(required = false) String status, + @RequestParam(required = false) String companyName, + @RequestParam(required = false) String customerPurchaseCondition, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder, + @PageableDefault(size = 10) Pageable pageable) { + + ContractSearchDTO contractSearchDTO = new ContractSearchDTO(); + contractSearchDTO.setMemberId(principal.getName()); + contractSearchDTO.setSearchMemberId(searchMemberId); + contractSearchDTO.setCenterId(centerId); + contractSearchDTO.setTitle(title); + contractSearchDTO.setStartDate(startDate); + contractSearchDTO.setEndDate(endDate); + contractSearchDTO.setCustomerName(customerName); + contractSearchDTO.setCustomerClassifcation(customerClassifcation); + contractSearchDTO.setProductName(productName); + contractSearchDTO.setStatus(status); + contractSearchDTO.setCompanyName(companyName); + contractSearchDTO.setCustomerPurchaseCondition(customerPurchaseCondition); + + // 정렬 추가 + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + Page responseContracts = contractQueryService.selectBySearchEmployee(contractSearchDTO, pageable); + + return ResponseEntity.ok(ContractResponseMessage.builder() + .httpStatus(200) + .msg("계약서 검색 조회 성공") + .result(responseContracts) + .build()); + } + + // 영업 관리자 조회 + @Operation(summary = "계약서 전체 조회(영업관리자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "계약서 전체 조회 성공", + content = {@Content(schema = @Schema(implementation = ContractResponseMessage.class))}) + }) + @GetMapping("center") + public ResponseEntity getAllContractAdmin(@PageableDefault(size = 10) Pageable pageable, + Principal principal) throws GeneralSecurityException { + + ContractSelectAllDTO contractSelectAllDTO = new ContractSelectAllDTO(); + contractSelectAllDTO.setMemberId(principal.getName()); + + Page responseContracts = contractQueryService.selectAllContractAdmin(contractSelectAllDTO, pageable); + + return ResponseEntity.ok(ContractResponseMessage.builder() + .httpStatus(200) + .msg("계약서 전체 조회 성공") + .result(responseContracts) + .build()); + } + + @Operation(summary = "계약서 상세 조회(영업관리자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "계약서 상세 조회 성공", + content = {@Content(schema = @Schema(implementation = ContractResponseMessage.class))}) + }) + @GetMapping("center/{contractId}") + public ResponseEntity getDetailContractAdmin(@PathVariable String contractId, + Principal principal) throws GeneralSecurityException { + + ContractSeletIdDTO contractSeletIdDTO = new ContractSeletIdDTO(); + contractSeletIdDTO.setContractId(contractId); + contractSeletIdDTO.setMemberId(principal.getName()); + + ContractSeletIdDTO responseContract = contractQueryService.selectDetailContractAdmin(contractSeletIdDTO); + + return ResponseEntity.ok(ContractResponseMessage.builder() + .httpStatus(200) + .msg("계약서 상세조회 성공") + .result(responseContract) + .build()); + } + + @Operation(summary = "계약서 검색 조회(영업관리자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "계약서 검색 조회 성공", + content = {@Content(schema = @Schema(implementation = ContractResponseMessage.class))}) + }) + @GetMapping("center/search") + public ResponseEntity getContractBySearchAdmin(Principal principal, + @RequestParam(required = false) String searchMemberId, + @RequestParam(required = false) String centerId, + @RequestParam(required = false) String title, + @RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate, + @RequestParam(required = false) String customerName, + @RequestParam(required = false) String customerClassifcation, + @RequestParam(required = false) String productName, + @RequestParam(required = false) String status, + @RequestParam(required = false) String companyName, + @RequestParam(required = false) String customerPurchaseCondition, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder, + @PageableDefault(size = 10) Pageable pageable) throws GeneralSecurityException { + + ContractSearchDTO contractSearchDTO = new ContractSearchDTO(); + contractSearchDTO.setMemberId(principal.getName()); + contractSearchDTO.setSearchMemberId(searchMemberId); + contractSearchDTO.setCenterId(centerId); + contractSearchDTO.setTitle(title); + contractSearchDTO.setStartDate(startDate); + contractSearchDTO.setEndDate(endDate); + contractSearchDTO.setCustomerName(customerName); + contractSearchDTO.setCustomerClassifcation(customerClassifcation); + contractSearchDTO.setProductName(productName); + contractSearchDTO.setStatus(status); + contractSearchDTO.setCompanyName(companyName); + contractSearchDTO.setCustomerPurchaseCondition(customerPurchaseCondition); + + // 정렬 추가 + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + Page responseContracts = contractQueryService.selectBySearchAdmin(contractSearchDTO, pageable); + + return ResponseEntity.ok(ContractResponseMessage.builder() + .httpStatus(200) + .msg("계약서 검색 조회 성공") + .result(responseContracts) + .build()); + } + + // 영업담당자 조회 + @Operation(summary = "계약서 전체 조회(영업담당자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "계약서 전체 조회 성공", + content = {@Content(schema = @Schema(implementation = ContractResponseMessage.class))}) + }) + @GetMapping("") + public ResponseEntity getAllContract(@PageableDefault(size = 10) Pageable pageable, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder) { + + ContractSelectAllDTO contractSelectAllDTO = new ContractSelectAllDTO(); + + // 정렬 추가 + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + Page responseContracts = contractQueryService.selectAllContract(contractSelectAllDTO, pageable); + + return ResponseEntity.ok(ContractResponseMessage.builder() + .httpStatus(200) + .msg("계약서 전체 조회 성공") + .result(responseContracts) + .build()); + } + + @Operation(summary = "계약서 상세 조회(영업담당자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "계약서 상세 조회 성공", + content = {@Content(schema = @Schema(implementation = ContractResponseMessage.class))}) + }) + @GetMapping("{contractId}") + public ResponseEntity getDetailContract(@PathVariable String contractId) { + + ContractSeletIdDTO contractSeletIdDTO = new ContractSeletIdDTO(); + contractSeletIdDTO.setContractId(contractId); + + ContractSeletIdDTO responseContract = contractQueryService.selectDetailContract(contractSeletIdDTO); + + return ResponseEntity.ok(ContractResponseMessage.builder() + .httpStatus(200) + .msg("계약서 상세조회 성공") + .result(responseContract) + .build()); + } + + @Operation(summary = "계약서 검색 조회(영업담당자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "계약서 검색 조회 성공", + content = {@Content(schema = @Schema(implementation = ContractResponseMessage.class))}) + }) + @GetMapping("search") + public ResponseEntity getContractBySearch(@RequestParam(required = false) String searchMemberId, + @RequestParam(required = false) String centerId, + @RequestParam(required = false) String title, + @RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate, + @RequestParam(required = false) String customerName, + @RequestParam(required = false) String customerClassifcation, + @RequestParam(required = false) String productName, + @RequestParam(required = false) String status, + @RequestParam(required = false) String companyName, + @RequestParam(required = false) String customerPurchaseCondition, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder, + @PageableDefault(size = 10) Pageable pageable) { + + // 정렬 추가 + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + ContractSearchDTO contractSearchDTO = new ContractSearchDTO(); + contractSearchDTO.setSearchMemberId(searchMemberId); + contractSearchDTO.setCenterId(centerId); + contractSearchDTO.setTitle(title); + contractSearchDTO.setStartDate(startDate); + contractSearchDTO.setEndDate(endDate); + contractSearchDTO.setCustomerName(customerName); + contractSearchDTO.setCustomerClassifcation(customerClassifcation); + contractSearchDTO.setProductName(productName); + contractSearchDTO.setStatus(status); + contractSearchDTO.setCompanyName(companyName); + contractSearchDTO.setCustomerPurchaseCondition(customerPurchaseCondition); + + Page responseContracts = contractQueryService.selectBySearch(contractSearchDTO, pageable); + + return ResponseEntity.ok(ContractResponseMessage.builder() + .httpStatus(200) + .msg("계약서 검색 조회 성공") + .result(responseContracts) + .build()); + } + + @Operation(summary = "엑셀 다운로드") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "계약서 검색 조회 성공", + content = {@Content(schema = @Schema(implementation = ContractResponseMessage.class))}) + }) + @GetMapping("excel") + public void exportContract(HttpServletResponse response) { + + contractQueryService.exportContractToExcel(response); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/query/dto/ContractExcelDTO.java b/src/main/java/stanl_2/final_backend/domain/contract/query/dto/ContractExcelDTO.java new file mode 100644 index 00000000..0e8b8b9a --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/query/dto/ContractExcelDTO.java @@ -0,0 +1,33 @@ +package stanl_2.final_backend.domain.contract.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import stanl_2.final_backend.global.excel.ExcelColumnName; + +@Getter +@AllArgsConstructor +public class ContractExcelDTO { + + @ExcelColumnName(name = "계약서 번호") + private String contractId; + + @ExcelColumnName(name = "계약서명") + private String title; + + @ExcelColumnName(name = "고객명") + private String customerName; + + @ExcelColumnName(name = "승인 상태") + private String status; + + @ExcelColumnName(name = "제품명") + private String carName; + + @ExcelColumnName(name = "고객 조건") + private String customerPurchaseCondition; + + @ExcelColumnName(name = "계약일자") + private String createdAt; +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/query/dto/ContractSearchDTO.java b/src/main/java/stanl_2/final_backend/domain/contract/query/dto/ContractSearchDTO.java new file mode 100644 index 00000000..cbd9b56f --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/query/dto/ContractSearchDTO.java @@ -0,0 +1,46 @@ +package stanl_2.final_backend.domain.contract.query.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class ContractSearchDTO { + + private String contractId; + private String title; + private String customerName; + private String customerIdentifiNo; + private String customerAddress; + private String customerEmail; + private String customerPhone; + private String companyName; + private String customerClassifcation; + private String customerPurchaseCondition; + private String serialNum; + private String selectOption; + private Integer downPayment; + private Integer intermediatePayment; + private Integer remainderPayment; + private Integer consignmentPayment; + private String deliveryDate; + private String deliveryLocationLoc; + private String status; + private String numberOfVehicles; + private String totalSales; + private String createdUrl; + private String updatedUrl; + private boolean active; + private String createdAt; + private String updatedAt; + private String deletedAt; + private String memberId; + private String searchMemberId; + private String centerId; + private String customerId; + private String startDate; + private String endDate; + private String carName; + private String productName; +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/query/dto/ContractSelectAllDTO.java b/src/main/java/stanl_2/final_backend/domain/contract/query/dto/ContractSelectAllDTO.java new file mode 100644 index 00000000..2555a849 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/query/dto/ContractSelectAllDTO.java @@ -0,0 +1,47 @@ +package stanl_2.final_backend.domain.contract.query.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class ContractSelectAllDTO { + + private String contractId; + private String title; + private String customerName; + private String companyName; + private String status; + private String memberId; + private String centerId; + private String customerId; + private String productId; + private String carName; + private String createdAt; + private String customerClassifcation; + private String customerPurchaseCondition; + private String customerIdentifiNo; + private String customerAddress; + private String customerEmail; + private String customerPhone; + private String serialNum; + private String selectOption; + private Integer downPayment; + private Integer intermediatePayment; + private Integer remainderPayment; + private Integer consignmentPayment; + private String deliveryDate; + private String deliveryLocationLoc; + private String numberOfVehicles; + private String totalSales; + private String createdUrl; + private String updatedUrl; + private boolean active; + private String updatedAt; + private String deletedAt; + private String searchMemberId; + private String startDate; + private String endDate; + private String productName; +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/query/dto/ContractSeletIdDTO.java b/src/main/java/stanl_2/final_backend/domain/contract/query/dto/ContractSeletIdDTO.java new file mode 100644 index 00000000..4071255e --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/query/dto/ContractSeletIdDTO.java @@ -0,0 +1,47 @@ +package stanl_2.final_backend.domain.contract.query.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class ContractSeletIdDTO { + + private String contractId; + private String title; + private String customerName; + private String customerSex; + private String customerIdentifiNo; + private Integer customerAge; + private String customerAddress; + private String customerEmail; + private String customerPhone; + private String companyName; + private String customerClassifcation; + private String customerPurchaseCondition; + private String serialNum; + private String selectOption; + private Integer downPayment; + private Integer intermediatePayment; + private Integer remainderPayment; + private Integer consignmentPayment; + private String deliveryDate; + private String deliveryLocation; + private String status; + private Integer numberOfVehicles; + private Integer totalSales; + private String carName; + private String createdUrl; + private String updatedUrl; + private String vehiclePrice; + private boolean active; + private String createdAt; + private String updatedAt; + private String deletedAt; + private String memberId; + private String centerId; + private String customerId; + private String productId; + private String productName; +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/query/repository/ContractMapper.java b/src/main/java/stanl_2/final_backend/domain/contract/query/repository/ContractMapper.java new file mode 100644 index 00000000..5bf2667b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/query/repository/ContractMapper.java @@ -0,0 +1,67 @@ +package stanl_2.final_backend.domain.contract.query.repository; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import stanl_2.final_backend.domain.contract.query.dto.ContractExcelDTO; +import stanl_2.final_backend.domain.contract.query.dto.ContractSearchDTO; +import stanl_2.final_backend.domain.contract.query.dto.ContractSelectAllDTO; +import stanl_2.final_backend.domain.contract.query.dto.ContractSeletIdDTO; + +import java.util.List; + +@Mapper +public interface ContractMapper { + ContractSeletIdDTO findContractByIdAndMemId(@Param("contractId") String contractId, + @Param("memberId") String memberId); + + List findContractBySearchAndMemberId(@Param("offset") int offset, + @Param("pageSize") int pageSize, + @Param("contractSearchDTO") ContractSearchDTO contractSearchDTO, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + int findContractBySearchAndMemberIdCount(@Param("contractSearchDTO") ContractSearchDTO contractSearchDTO); + + List findContractAllByMemId(@Param("offset") int offset, + @Param("pageSize") int pageSize, + @Param("memberId") String memberId); + + int findContractCountByMemId(String memberId); + + List findContractAll(@Param("offset") int offset, + @Param("pageSize") int pageSize, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + int findContractCount(); + + ContractSeletIdDTO findContractById(String contractId); + + List findContractBySearch(@Param("offset") int offset, + @Param("pageSize") int pageSize, + @Param("contractSearchDTO") ContractSearchDTO contractSearchDTO, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + int findContractBySearchCount(@Param("contractSearchDTO") ContractSearchDTO contractSearchDTO); + + List findContractAllByCenterId(@Param("offset") int offset, + @Param("pageSize") int pageSize, + @Param("centerId") String centerId); + + Integer findContractCountByCenterId(@Param("centerId") String centerId); + + ContractSeletIdDTO findContractByIdAndCenterId(@Param("contractId")String contractId, + @Param("centerId") String centerId); + + List findContractBySearchAndCenterId(@Param("offset") int offset, + @Param("pageSize") int pageSize, + @Param("contractSearchDTO") ContractSearchDTO contractSearchDTO, + @Param("centerId") String centerId, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + Integer findContractBySearchAndCenterCount(@Param("contractSearchDTO") ContractSearchDTO contractSearchDTO, @Param("centerId") String centerId); + + List findContractForExcel(); +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/query/repository/UpdateHistoryMapper.java b/src/main/java/stanl_2/final_backend/domain/contract/query/repository/UpdateHistoryMapper.java new file mode 100644 index 00000000..736296ef --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/query/repository/UpdateHistoryMapper.java @@ -0,0 +1,8 @@ +package stanl_2.final_backend.domain.contract.query.repository; + +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface UpdateHistoryMapper { + String selectUpdateHistoryByContractId(String contractId); +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/query/service/ContractQueryService.java b/src/main/java/stanl_2/final_backend/domain/contract/query/service/ContractQueryService.java new file mode 100644 index 00000000..a03c13e2 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/query/service/ContractQueryService.java @@ -0,0 +1,35 @@ +package stanl_2.final_backend.domain.contract.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import stanl_2.final_backend.domain.contract.query.dto.ContractSearchDTO; +import stanl_2.final_backend.domain.contract.query.dto.ContractSelectAllDTO; +import stanl_2.final_backend.domain.contract.query.dto.ContractSeletIdDTO; + +import java.security.GeneralSecurityException; + +public interface ContractQueryService { + + Page selectAllContract(ContractSelectAllDTO contractSelectAllDTO, Pageable pageable); + + ContractSeletIdDTO selectDetailContract(ContractSeletIdDTO contractDTO); + + Page selectBySearch(ContractSearchDTO contractSearchDTO, Pageable pageable); + + Page selectAllContractEmployee(ContractSelectAllDTO contractSelectAllDTO, Pageable pageable); + + ContractSeletIdDTO selectDetailContractEmployee(ContractSeletIdDTO contractSeletIdDTO); + + Page selectBySearchEmployee(ContractSearchDTO contractSearchDTO, Pageable pageable); + + + Page selectAllContractAdmin(ContractSelectAllDTO contractSelectAllDTO, Pageable pageable) throws GeneralSecurityException; + + ContractSeletIdDTO selectDetailContractAdmin(ContractSeletIdDTO contractSeletIdDTO) throws GeneralSecurityException; + + Page selectBySearchAdmin(ContractSearchDTO contractSearchDTO, Pageable pageable) throws GeneralSecurityException; + + + void exportContractToExcel(HttpServletResponse response); +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/query/service/ContractQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/contract/query/service/ContractQueryServiceImpl.java new file mode 100644 index 00000000..a9e7c84b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/query/service/ContractQueryServiceImpl.java @@ -0,0 +1,378 @@ +package stanl_2.final_backend.domain.contract.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringEscapeUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.contract.common.exception.ContractCommonException; +import stanl_2.final_backend.domain.contract.common.exception.ContractErrorCode; +import stanl_2.final_backend.domain.contract.query.dto.ContractExcelDTO; +import stanl_2.final_backend.domain.contract.query.dto.ContractSearchDTO; +import stanl_2.final_backend.domain.contract.query.dto.ContractSelectAllDTO; +import stanl_2.final_backend.domain.contract.query.dto.ContractSeletIdDTO; +import stanl_2.final_backend.domain.contract.query.repository.ContractMapper; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.global.excel.ExcelUtilsV1; + +import java.security.GeneralSecurityException; +import java.util.List; + +@Slf4j +@Service("queryContractService") +public class ContractQueryServiceImpl implements ContractQueryService { + + private final ContractMapper contractMapper; + private final AuthQueryService authQueryService; + private final MemberQueryService memberQueryService; + private final UpdateHistoryQueryService updateHistoryQueryService; + private final RedisTemplate redisTemplate; + private final ExcelUtilsV1 excelUtilsV1; + + @Autowired + public ContractQueryServiceImpl(ContractMapper contractMapper, AuthQueryService authQueryService, MemberQueryService memberQueryService, UpdateHistoryQueryService updateHistoryQueryService, @Qualifier("redisTemplate") RedisTemplate redisTemplate, ExcelUtilsV1 excelUtilsV1) { + this.contractMapper = contractMapper; + this.authQueryService = authQueryService; + this.memberQueryService = memberQueryService; + this.updateHistoryQueryService = updateHistoryQueryService; + this.redisTemplate = redisTemplate; + this.excelUtilsV1 = excelUtilsV1; + } + + // 영업사원 조회 + // 계약서 전체조회 + @Override + @Transactional(readOnly = true) + public Page selectAllContractEmployee(ContractSelectAllDTO contractSelectAllDTO, Pageable pageable) { + + String memberId = authQueryService.selectMemberIdByLoginId(contractSelectAllDTO.getMemberId()); + + int offset = Math.toIntExact(pageable.getOffset()); + int pageSize = pageable.getPageSize(); + + List contracts = contractMapper.findContractAllByMemId(offset, pageSize, memberId); + + if (contracts == null) { + throw new ContractCommonException(ContractErrorCode.CONTRACT_NOT_FOUND); + } + + int count = contractMapper.findContractCountByMemId(memberId); + + if (count == 0) { + throw new ContractCommonException(ContractErrorCode.CONTRACT_NOT_FOUND); + } + + return new PageImpl<>(contracts, pageable, count); + } + + // 계약서 상세조회 + @Override + @Transactional(readOnly = true) + public ContractSeletIdDTO selectDetailContractEmployee(ContractSeletIdDTO contractSeletIdDTO) { + + String memberId = authQueryService.selectMemberIdByLoginId(contractSeletIdDTO.getMemberId()); + + ContractSeletIdDTO responseContract = contractMapper.findContractByIdAndMemId(contractSeletIdDTO.getContractId(), memberId); + + if (responseContract == null) { + throw new ContractCommonException(ContractErrorCode.CONTRACT_NOT_FOUND); + } + + String content = updateHistoryQueryService.selectUpdateHistoryByContractId(responseContract.getContractId()); + + if (content == null) { + String unescapedHtml = StringEscapeUtils.unescapeJson(responseContract.getCreatedUrl()); + responseContract.setCreatedUrl(unescapedHtml); + return responseContract; + } + responseContract.setCreatedUrl(content); + + return responseContract; + } + + @Override + @Transactional(readOnly = true) + public Page selectBySearchEmployee(ContractSearchDTO contractSearchDTO, Pageable pageable) { + String memberId = authQueryService.selectMemberIdByLoginId(contractSearchDTO.getMemberId()); + contractSearchDTO.setMemberId(memberId); + + if ("대기".equals(contractSearchDTO.getStatus())) { + contractSearchDTO.setStatus("WAIT"); + } else if ("승인".equals(contractSearchDTO.getStatus())) { + contractSearchDTO.setStatus("APPROVED"); + } else if ("취소".equals(contractSearchDTO.getStatus())) { + contractSearchDTO.setStatus("LEASE"); + } + + if ("현금".equals(contractSearchDTO.getCustomerPurchaseCondition())) { + contractSearchDTO.setCustomerPurchaseCondition("CASH"); + } else if ("할부".equals(contractSearchDTO.getCustomerPurchaseCondition())) { + contractSearchDTO.setCustomerPurchaseCondition("INSTALLMENT"); + } else if ("리스".equals(contractSearchDTO.getCustomerPurchaseCondition())) { + contractSearchDTO.setCustomerPurchaseCondition("CANCEL"); + } + + if ("개인".equals(contractSearchDTO.getCustomerClassifcation())) { + contractSearchDTO.setCustomerClassifcation("PERSONAL"); + } else if ("법인".equals(contractSearchDTO.getCustomerClassifcation())) { + contractSearchDTO.setCustomerClassifcation("BUSINESS"); + } + + int offset = Math.toIntExact(pageable.getOffset()); + int pageSize = pageable.getPageSize(); + + // 정렬 정보 가져오기 + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + List contracts = contractMapper.findContractBySearchAndMemberId(offset, pageSize, contractSearchDTO, sortField, sortOrder); + + if (contracts == null) { + throw new ContractCommonException(ContractErrorCode.CONTRACT_NOT_FOUND); + } + + int count = contractMapper.findContractBySearchAndMemberIdCount(contractSearchDTO); + if (count == 0) { + throw new ContractCommonException(ContractErrorCode.CONTRACT_NOT_FOUND); + } + + return new PageImpl<>(contracts, pageable, count); + } + + // 영업 관리자 조회 + @Override + @Transactional(readOnly = true) + public Page selectAllContractAdmin(ContractSelectAllDTO contractSelectAllDTO, Pageable pageable) throws GeneralSecurityException { + String memberId = authQueryService.selectMemberIdByLoginId(contractSelectAllDTO.getMemberId()); + String centerId = memberQueryService.selectMemberInfo(contractSelectAllDTO.getMemberId()).getCenterId(); + contractSelectAllDTO.setMemberId(memberId); + + int offset = Math.toIntExact(pageable.getOffset()); + int pageSize = pageable.getPageSize(); + + List contracts = contractMapper.findContractAllByCenterId(offset, pageSize, centerId); + + if (contracts == null) { + throw new ContractCommonException(ContractErrorCode.CONTRACT_NOT_FOUND); + } + + Integer count = contractMapper.findContractCountByCenterId(centerId); + int totalContract = (count != null) ? count : 0; + + return new PageImpl<>(contracts, pageable, totalContract); + } + + @Override + @Transactional(readOnly = true) + public ContractSeletIdDTO selectDetailContractAdmin(ContractSeletIdDTO contractSeletIdDTO) throws GeneralSecurityException { + String memberId = authQueryService.selectMemberIdByLoginId(contractSeletIdDTO.getMemberId()); + String centerId = memberQueryService.selectMemberInfo(contractSeletIdDTO.getMemberId()).getCenterId(); + + contractSeletIdDTO.setMemberId(memberId); + + ContractSeletIdDTO responseContract = contractMapper.findContractByIdAndCenterId(contractSeletIdDTO.getContractId(), centerId); + + if (responseContract == null) { + throw new ContractCommonException(ContractErrorCode.CONTRACT_NOT_FOUND); + } + + String content = updateHistoryQueryService.selectUpdateHistoryByContractId(responseContract.getContractId()); + + if (content == null) { + String unescapedHtml = StringEscapeUtils.unescapeJson(responseContract.getCreatedUrl()); + responseContract.setCreatedUrl(unescapedHtml); + return responseContract; + } + responseContract.setCreatedUrl(content); + + return responseContract; + } + + @Override + @Transactional(readOnly = true) + public Page selectBySearchAdmin(ContractSearchDTO contractSearchDTO, Pageable pageable) throws GeneralSecurityException { + + String memberId = authQueryService.selectMemberIdByLoginId(contractSearchDTO.getMemberId()); + String centerId = memberQueryService.selectMemberInfo(contractSearchDTO.getMemberId()).getCenterId(); + + contractSearchDTO.setMemberId(memberId); + + if ("대기".equals(contractSearchDTO.getStatus())) { + contractSearchDTO.setStatus("WAIT"); + } else if ("승인".equals(contractSearchDTO.getStatus())) { + contractSearchDTO.setStatus("APPROVED"); + } else if ("취소".equals(contractSearchDTO.getStatus())) { + contractSearchDTO.setStatus("LEASE"); + } + + if ("현금".equals(contractSearchDTO.getCustomerPurchaseCondition())) { + contractSearchDTO.setCustomerPurchaseCondition("CASH"); + } else if ("할부".equals(contractSearchDTO.getCustomerPurchaseCondition())) { + contractSearchDTO.setCustomerPurchaseCondition("INSTALLMENT"); + } else if ("리스".equals(contractSearchDTO.getCustomerPurchaseCondition())) { + contractSearchDTO.setCustomerPurchaseCondition("CANCEL"); + } + + if ("개인".equals(contractSearchDTO.getCustomerClassifcation())) { + contractSearchDTO.setCustomerClassifcation("PERSONAL"); + } else if ("법인".equals(contractSearchDTO.getCustomerClassifcation())) { + contractSearchDTO.setCustomerClassifcation("BUSINESS"); + } + + int offset = Math.toIntExact(pageable.getOffset()); + int pageSize = pageable.getPageSize(); + + // 정렬 정보 가져오기 + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + List contracts = contractMapper.findContractBySearchAndCenterId(offset, pageSize, contractSearchDTO, centerId, sortField, sortOrder); + + if (contracts == null) { + throw new ContractCommonException(ContractErrorCode.CONTRACT_NOT_FOUND); + } + + int count = contractMapper.findContractBySearchAndCenterCount(contractSearchDTO, centerId); + if (count == 0) { + throw new ContractCommonException(ContractErrorCode.CONTRACT_NOT_FOUND); + } + + return new PageImpl<>(contracts, pageable, count); + } + + // 영업담당자 조회 + @Override + @Transactional(readOnly = true) + public Page selectAllContract(ContractSelectAllDTO contractSelectAllDTO, Pageable pageable) { + + int offset = Math.toIntExact(pageable.getOffset()); + int pageSize = pageable.getPageSize(); + + // 정렬 정보 가져오기 + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + List contracts = contractMapper.findContractAll(offset, pageSize, sortField, sortOrder); + + if (contracts == null) { + throw new ContractCommonException(ContractErrorCode.CONTRACT_NOT_FOUND); + } + + Integer count = contractMapper.findContractCount(); + int totalContract = (count != null) ? count : 0; + + return new PageImpl<>(contracts, pageable, totalContract); + } + + // 계약서 상세조회 + @Override + @Transactional(readOnly = true) + public ContractSeletIdDTO selectDetailContract(ContractSeletIdDTO contractSeletIdDTO) { + + ContractSeletIdDTO responseContract = contractMapper.findContractById(contractSeletIdDTO.getContractId()); + + if (responseContract == null) { + throw new ContractCommonException(ContractErrorCode.CONTRACT_NOT_FOUND); + } + + String content = updateHistoryQueryService.selectUpdateHistoryByContractId(responseContract.getContractId()); + + if (content == null) { + String unescapedHtml = StringEscapeUtils.unescapeJson(responseContract.getCreatedUrl()); + responseContract.setCreatedUrl(unescapedHtml); + return responseContract; + } + responseContract.setCreatedUrl(content); + + return responseContract; + } + + @Override + @Transactional(readOnly = true) + public Page selectBySearch(ContractSearchDTO contractSearchDTO, Pageable pageable) { + + if ("대기".equals(contractSearchDTO.getStatus())) { + contractSearchDTO.setStatus("WAIT"); + } else if ("승인".equals(contractSearchDTO.getStatus())) { + contractSearchDTO.setStatus("APPROVED"); + } else if ("취소".equals(contractSearchDTO.getStatus())) { + contractSearchDTO.setStatus("LEASE"); + } + + if ("현금".equals(contractSearchDTO.getCustomerPurchaseCondition())) { + contractSearchDTO.setCustomerPurchaseCondition("CASH"); + } else if ("할부".equals(contractSearchDTO.getCustomerPurchaseCondition())) { + contractSearchDTO.setCustomerPurchaseCondition("INSTALLMENT"); + } else if ("리스".equals(contractSearchDTO.getCustomerPurchaseCondition())) { + contractSearchDTO.setCustomerPurchaseCondition("CANCEL"); + } + + if ("개인".equals(contractSearchDTO.getCustomerClassifcation())) { + contractSearchDTO.setCustomerClassifcation("PERSONAL"); + } else if ("법인".equals(contractSearchDTO.getCustomerClassifcation())) { + contractSearchDTO.setCustomerClassifcation("BUSINESS"); + } + + int offset = Math.toIntExact(pageable.getOffset()); + int pageSize = pageable.getPageSize(); + + // 정렬 정보 가져오기 + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + List contracts = contractMapper.findContractBySearch(offset, pageSize, contractSearchDTO, sortField, sortOrder); + + if (contracts == null) { + throw new ContractCommonException(ContractErrorCode.CONTRACT_NOT_FOUND); + } + + int totalContract = contractMapper.findContractBySearchCount(contractSearchDTO); + + if (totalContract == 0) { + throw new ContractCommonException(ContractErrorCode.CONTRACT_NOT_FOUND); + } + + return new PageImpl<>(contracts, pageable, totalContract); + } + + @Override + @Transactional(readOnly = true) + public void exportContractToExcel(HttpServletResponse response) { + List contractExcels = contractMapper.findContractForExcel(); + + if (contractExcels == null) { + throw new ContractCommonException(ContractErrorCode.CONTRACT_NOT_FOUND); + } + + excelUtilsV1.download(ContractExcelDTO.class, contractExcels, "contractExcel", response); + } + +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/contract/query/service/UpdateHistoryQueryService.java b/src/main/java/stanl_2/final_backend/domain/contract/query/service/UpdateHistoryQueryService.java new file mode 100644 index 00000000..00da36e1 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/query/service/UpdateHistoryQueryService.java @@ -0,0 +1,6 @@ +package stanl_2.final_backend.domain.contract.query.service; + +public interface UpdateHistoryQueryService { + + String selectUpdateHistoryByContractId(String contractId); +} diff --git a/src/main/java/stanl_2/final_backend/domain/contract/query/service/UpdateHistoryQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/contract/query/service/UpdateHistoryQueryServiceImpl.java new file mode 100644 index 00000000..e8cfb32d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/contract/query/service/UpdateHistoryQueryServiceImpl.java @@ -0,0 +1,27 @@ +package stanl_2.final_backend.domain.contract.query.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.contract.query.repository.UpdateHistoryMapper; + +@Service +public class UpdateHistoryQueryServiceImpl implements UpdateHistoryQueryService { + + private final UpdateHistoryMapper updateHistoryMapper; + + @Autowired + public UpdateHistoryQueryServiceImpl(UpdateHistoryMapper updateHistoryMapper) { + this.updateHistoryMapper = updateHistoryMapper; + } + + + @Override + @Transactional(readOnly = true) + public String selectUpdateHistoryByContractId(String contractId) { + + String content = updateHistoryMapper.selectUpdateHistoryByContractId(contractId); + + return content; + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/customer/command/application/controller/CustomerController.java b/src/main/java/stanl_2/final_backend/domain/customer/command/application/controller/CustomerController.java new file mode 100644 index 00000000..e209189a --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/command/application/controller/CustomerController.java @@ -0,0 +1,98 @@ +package stanl_2.final_backend.domain.customer.command.application.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stanl_2.final_backend.domain.customer.command.application.dto.CustomerModifyDTO; +import stanl_2.final_backend.domain.customer.command.application.dto.CustomerRegistDTO; +import stanl_2.final_backend.domain.customer.command.application.service.CustomerCommandService; +import stanl_2.final_backend.domain.customer.common.response.CustomerResponseMessage; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; + +import java.security.GeneralSecurityException; +import java.security.Principal; + +@Slf4j +@RestController("commandCustomerController") +@RequestMapping("/api/v1/customer") +public class CustomerController { + + private final CustomerCommandService customerCommandService; + private final AuthQueryService authQueryService; + + @Autowired + public CustomerController(CustomerCommandService customerCommandService, + AuthQueryService authQueryService) { + this.customerCommandService = customerCommandService; + this.authQueryService = authQueryService; + } + + @Operation(summary = "고객정보 등록") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CustomerResponseMessage.class))}) + }) + @PostMapping("") + public ResponseEntity postCustomer(@RequestBody CustomerRegistDTO customerRegistDTO, + Principal principal) throws GeneralSecurityException { + + String memberId = authQueryService.selectMemberIdByLoginId(principal.getName()); + + customerRegistDTO.setMemberId(memberId); + + customerCommandService.registerCustomerInfo(customerRegistDTO); + + return ResponseEntity.ok(CustomerResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(null) + .build()); + } + + @Operation(summary = "고객정보 수정(자기 고객만)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CustomerResponseMessage.class))}) + }) + @PutMapping("/{customerId}") + public ResponseEntity postCustomer(@PathVariable String customerId, + @RequestBody CustomerModifyDTO customerModifyDTO, + Principal principal) throws GeneralSecurityException { + + String memberId = principal.getName(); + + customerModifyDTO.setCustomerId(customerId); + customerModifyDTO.setMemberId(memberId); + + customerCommandService.modifyCustomerInfo(customerModifyDTO); + + return ResponseEntity.ok(CustomerResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(null) + .build()); + } + + @Operation(summary = "고객정보 삭제(자기 고객만)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CustomerResponseMessage.class))}) + }) + @DeleteMapping("/{customerId}") + public ResponseEntity deleteCustomer(@PathVariable String customerId) { + + customerCommandService.deleteCustomerId(customerId); + + return ResponseEntity.ok(CustomerResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(null) + .build()); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/customer/command/application/dto/CustomerModifyDTO.java b/src/main/java/stanl_2/final_backend/domain/customer/command/application/dto/CustomerModifyDTO.java new file mode 100644 index 00000000..865289e0 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/command/application/dto/CustomerModifyDTO.java @@ -0,0 +1,18 @@ +package stanl_2.final_backend.domain.customer.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class CustomerModifyDTO { + private String memberId; + private String customerId; + private String name; + private Integer age; + private String sex; + private String phone; + private String emergePhone; + private String email; +} diff --git a/src/main/java/stanl_2/final_backend/domain/customer/command/application/dto/CustomerRegistDTO.java b/src/main/java/stanl_2/final_backend/domain/customer/command/application/dto/CustomerRegistDTO.java new file mode 100644 index 00000000..1bc6b4a5 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/command/application/dto/CustomerRegistDTO.java @@ -0,0 +1,20 @@ +package stanl_2.final_backend.domain.customer.command.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class CustomerRegistDTO { + private String name; + private Integer age; + private String sex; + private String phone; + private String emergePhone; + private String email; + private String memberId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/customer/command/application/dto/CustomerResponseDTO.java b/src/main/java/stanl_2/final_backend/domain/customer/command/application/dto/CustomerResponseDTO.java new file mode 100644 index 00000000..fb6eca54 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/command/application/dto/CustomerResponseDTO.java @@ -0,0 +1,25 @@ +package stanl_2.final_backend.domain.customer.command.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class CustomerResponseDTO { + private String customerId; + private String name; + private Integer age; + private String sex; + private String phone; + private String emergePhone; + private String email; + private String memberId; + private Boolean active; + private String createdAt; + private String updatedAt; + private String deletedAt; +} diff --git a/src/main/java/stanl_2/final_backend/domain/customer/command/application/service/CustomerCommandService.java b/src/main/java/stanl_2/final_backend/domain/customer/command/application/service/CustomerCommandService.java new file mode 100644 index 00000000..bd7b552f --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/command/application/service/CustomerCommandService.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.customer.command.application.service; + +import stanl_2.final_backend.domain.customer.command.application.dto.CustomerModifyDTO; +import stanl_2.final_backend.domain.customer.command.application.dto.CustomerRegistDTO; +import stanl_2.final_backend.domain.customer.command.application.dto.CustomerResponseDTO; +import stanl_2.final_backend.domain.customer.command.domain.aggregate.entity.Customer; + +import java.security.GeneralSecurityException; + +public interface CustomerCommandService { + CustomerResponseDTO registerCustomerInfo(CustomerRegistDTO customerRegistDTO) throws GeneralSecurityException; + + void modifyCustomerInfo(CustomerModifyDTO customerModifyDTO) throws GeneralSecurityException; + + void deleteCustomerId(String customerId); +} diff --git a/src/main/java/stanl_2/final_backend/domain/customer/command/domain/aggregate/entity/Customer.java b/src/main/java/stanl_2/final_backend/domain/customer/command/domain/aggregate/entity/Customer.java new file mode 100644 index 00000000..4ef5b4f7 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/command/domain/aggregate/entity/Customer.java @@ -0,0 +1,76 @@ +package stanl_2.final_backend.domain.customer.command.domain.aggregate.entity; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "TB_CUSTOMER_INFO") +public class Customer { + @Id + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name = "prefix", value = "CUS") + ) + @Column(name = "CUST_ID", nullable = false) + private String customerId; + + @Column(name = "CUST_NAME", nullable = false) + private String name; + + @Column(name = "CUST_AGE", nullable = false) + private Integer age; + + @Column(name = "CUST_SEX", nullable = false) + private String sex; + + @Column(name = "CUST_PHO", nullable = false) + private String phone; + + @Column(name = "CUST_EMER_PHO") + private String emergePhone; + + @Column(name = "CUST_EMA", nullable = false) + private String email; + + @Column(name = "ACTIVE", nullable = false) + private Boolean active = true; + + @Column(name = "CREATED_AT", nullable = false, updatable = false) + private String createdAt; + + @Column(name = "UPDATED_AT", nullable = false) + private String updatedAt; + + @Column(name = "DELETED_AT") + private String deletedAt; + + @Column(name = "MEM_ID", nullable = false) + private String memberId; + + @PrePersist + private void prePersist() { + this.createdAt = getCurrentTime(); + this.updatedAt = this.createdAt; + } + + @PreUpdate + private void preUpdate() { + this.updatedAt = getCurrentTime(); + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/customer/command/domain/repository/CustomerRepository.java b/src/main/java/stanl_2/final_backend/domain/customer/command/domain/repository/CustomerRepository.java new file mode 100644 index 00000000..d2ad757f --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/command/domain/repository/CustomerRepository.java @@ -0,0 +1,12 @@ +package stanl_2.final_backend.domain.customer.command.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import stanl_2.final_backend.domain.customer.command.domain.aggregate.entity.Customer; + +import java.util.Optional; + +@Repository +public interface CustomerRepository extends JpaRepository { + Customer findByCustomerId(String customerId); +} diff --git a/src/main/java/stanl_2/final_backend/domain/customer/command/domain/service/CustomerCommandServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/customer/command/domain/service/CustomerCommandServiceImpl.java new file mode 100644 index 00000000..b88826b3 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/command/domain/service/CustomerCommandServiceImpl.java @@ -0,0 +1,81 @@ +package stanl_2.final_backend.domain.customer.command.domain.service; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import lombok.extern.slf4j.Slf4j; +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.customer.command.application.dto.CustomerModifyDTO; +import stanl_2.final_backend.domain.customer.command.application.dto.CustomerRegistDTO; +import stanl_2.final_backend.domain.customer.command.application.dto.CustomerResponseDTO; +import stanl_2.final_backend.domain.customer.command.application.service.CustomerCommandService; +import stanl_2.final_backend.domain.customer.command.domain.aggregate.entity.Customer; +import stanl_2.final_backend.domain.customer.command.domain.repository.CustomerRepository; +import stanl_2.final_backend.domain.customer.common.exception.CustomerCommonException; +import stanl_2.final_backend.domain.customer.common.exception.CustomerErrorCode; +import stanl_2.final_backend.global.utils.AESUtils; + +import java.security.GeneralSecurityException; + +@Slf4j +@Service("commandCustomerService") +public class CustomerCommandServiceImpl implements CustomerCommandService { + + @PersistenceContext + private EntityManager em; + private final CustomerRepository customerRepository; + private final ModelMapper modelMapper; + private final AESUtils aesUtils; + + @Autowired + public CustomerCommandServiceImpl(CustomerRepository customerRepository, + ModelMapper modelMapper, + AESUtils aesUtils) { + this.customerRepository = customerRepository; + this.modelMapper = modelMapper; + this.aesUtils = aesUtils; + } + + @Override + @Transactional + public CustomerResponseDTO registerCustomerInfo(CustomerRegistDTO customerRegistDTO) throws GeneralSecurityException { + + Customer customer = modelMapper.map(customerRegistDTO, Customer.class); + + customer.setPhone(aesUtils.encrypt(customer.getPhone())); + customer.setEmergePhone(aesUtils.encrypt(customer.getEmergePhone())); + customer.setEmail(aesUtils.encrypt(customer.getEmail())); + + return modelMapper.map(customerRepository.saveAndFlush(customer), CustomerResponseDTO.class); + } + + @Override + @Transactional + public void modifyCustomerInfo(CustomerModifyDTO customerModifyDTO) throws GeneralSecurityException { + + Customer customer = customerRepository.findById(customerModifyDTO.getCustomerId()) + .orElseThrow(() -> new CustomerCommonException(CustomerErrorCode.CUSTOMER_NOT_FOUND)); + + customerModifyDTO.setPhone(aesUtils.encrypt(customerModifyDTO.getPhone())); + customerModifyDTO.setEmergePhone(aesUtils.encrypt(customerModifyDTO.getEmergePhone())); + customerModifyDTO.setEmail(aesUtils.encrypt(customerModifyDTO.getEmail())); + + modelMapper.map(customerModifyDTO, customer); + + customerRepository.save(customer); + } + + @Override + @Transactional + public void deleteCustomerId(String customerId) { + + Customer customer = customerRepository.findById(customerId) + .orElseThrow(() -> new CustomerCommonException(CustomerErrorCode.CUSTOMER_NOT_FOUND)); + + customer.setActive(false); + + customerRepository.save(customer); + } +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/customer/common/exception/CustomerCommonException.java b/src/main/java/stanl_2/final_backend/domain/customer/common/exception/CustomerCommonException.java new file mode 100644 index 00000000..251e7702 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/common/exception/CustomerCommonException.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.customer.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class CustomerCommonException extends RuntimeException { + private final CustomerErrorCode sampleErrorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.sampleErrorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/customer/common/exception/CustomerErrorCode.java b/src/main/java/stanl_2/final_backend/domain/customer/common/exception/CustomerErrorCode.java new file mode 100644 index 00000000..ab3e3b07 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/common/exception/CustomerErrorCode.java @@ -0,0 +1,52 @@ +package stanl_2.final_backend.domain.customer.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum CustomerErrorCode { + + /** + * 400(Bad Request) + * 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. + */ + + + + /** + * 401(Unauthorized) + * 비록 HTTP 표준에서는 "미승인(unauthorized)"를 명확히 하고 있지만, + * 의미상 이 응답은 "비인증(unauthenticated)"을 의미합니다. + * 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로를 인증해야 합니다. + */ + + + /** + * 403(Forbidden) + * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. + * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. + */ + + + + /** + * 404(Not Found) + * 서버는 요청받은 리소스를 찾을 수 없습니다. 브라우저에서는 알려지지 않은 URL을 의미합니다. + * 이것은 API에서 종점은 적절하지만 리소스 자체는 존재하지 않음을 의미할 수도 있습니다. + * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. + * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. + */ + CUSTOMER_NOT_FOUND(40401, HttpStatus.NOT_FOUND, "고객 정보를 찾을 수 없습니다."), + + /** + * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. + */ + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); + + + private final Integer code; + private final HttpStatus httpStatus; + private final String msg; +} diff --git a/src/main/java/stanl_2/final_backend/domain/customer/common/exception/CustomerExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/customer/common/exception/CustomerExceptionResponse.java new file mode 100644 index 00000000..383f18f9 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/common/exception/CustomerExceptionResponse.java @@ -0,0 +1,22 @@ +package stanl_2.final_backend.domain.customer.common.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class CustomerExceptionResponse { + private final Integer code; + private final String msg; + private final HttpStatus httpStatus; + + public CustomerExceptionResponse(CustomerErrorCode sampleErrorCode) { + this.code = sampleErrorCode.getCode(); + this.msg = sampleErrorCode.getMsg(); + this.httpStatus = sampleErrorCode.getHttpStatus(); + } + + public static CustomerExceptionResponse of(CustomerErrorCode sampleErrorCode) { + return new CustomerExceptionResponse(sampleErrorCode); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/customer/common/response/CustomerResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/customer/common/response/CustomerResponseMessage.java new file mode 100644 index 00000000..acefa132 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/common/response/CustomerResponseMessage.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.customer.common.response; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +public class CustomerResponseMessage { + private int httpStatus; + private String msg; + private Object result; +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/customer/query/config/MybatisConfiguration.java b/src/main/java/stanl_2/final_backend/domain/customer/query/config/MybatisConfiguration.java new file mode 100644 index 00000000..de0804f2 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/query/config/MybatisConfiguration.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.customer.query.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Configuration; + +@Configuration("customerMybatisConfiguration") +@MapperScan(basePackages = "stanl_2.final_backend.domain.customer.query.repository") +public class MybatisConfiguration { +} diff --git a/src/main/java/stanl_2/final_backend/domain/customer/query/controller/CustomerController.java b/src/main/java/stanl_2/final_backend/domain/customer/query/controller/CustomerController.java new file mode 100644 index 00000000..9e51bb24 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/query/controller/CustomerController.java @@ -0,0 +1,153 @@ +package stanl_2.final_backend.domain.customer.query.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stanl_2.final_backend.domain.customer.common.response.CustomerResponseMessage; +import stanl_2.final_backend.domain.customer.query.dto.CustomerContractDTO; +import stanl_2.final_backend.domain.customer.query.dto.CustomerDTO; +import stanl_2.final_backend.domain.customer.query.dto.CustomerSearchDTO; +import stanl_2.final_backend.domain.customer.query.service.CustomerQueryService; + +import java.security.GeneralSecurityException; + +@Slf4j +@RestController(value = "queryCustomerController") +@RequestMapping("/api/v1/customer") +public class CustomerController { + + private final CustomerQueryService customerQueryService; + + @Autowired + public CustomerController(CustomerQueryService customerQueryService) { + this.customerQueryService = customerQueryService; + } + + @Operation(summary = "고객정보 상세조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CustomerResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/{customerId}") + public ResponseEntity getCustomerInfo(@PathVariable String customerId) throws GeneralSecurityException { + + CustomerDTO customerInfoDTO = customerQueryService.selectCustomerInfo(customerId); + + return ResponseEntity.ok(CustomerResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(customerInfoDTO) + .build()); + } + + + @Operation(summary = "고객번호로 전체 목록 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CustomerResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/list") + public ResponseEntity getCustomers( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size + ) throws GeneralSecurityException { + Pageable pageable = PageRequest.of(page, size); + + Page customerDTOPage = customerQueryService.selectCustomerList(pageable); + + return ResponseEntity.ok(CustomerResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(customerDTOPage) + .build()); + } + + + @Operation(summary = "고객정보 조건별 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CustomerResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/search") + public ResponseEntity searchCustomer( + @RequestParam(required = false) String customerId, + @RequestParam(required = false) String name, + @RequestParam(required = false) String sex, + @RequestParam(required = false) String phone, + @RequestParam(required = false) String memberId, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder, + @PageableDefault(size = 10) Pageable pageable + ) throws GeneralSecurityException { + + // 정렬 추가 + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + CustomerSearchDTO customerSearchDTO = new CustomerSearchDTO(customerId , name, sex, phone, memberId); + Page customerDTOPage = customerQueryService.findCustomerByCondition(pageable, customerSearchDTO); + + return ResponseEntity.ok(CustomerResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(customerDTOPage) + .build()); + } + + + @Operation(summary = "고객별 계약서 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CustomerResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/contract/{customerId}") + public ResponseEntity searchCustomer(@PathVariable String customerId, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size){ + Pageable pageable = PageRequest.of(page, size); + + Page customerContractDTOList = customerQueryService.selectCustomerContractInfo(customerId, pageable); + + return ResponseEntity.ok(CustomerResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(customerContractDTOList) + .build()); + } + + + @Operation(summary = "엑셀 다운로드") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CustomerResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("excel") + public void exportCustomer(HttpServletResponse response){ + + customerQueryService.exportCustomerToExcel(response); + } +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/customer/query/dto/CustomerContractDTO.java b/src/main/java/stanl_2/final_backend/domain/customer/query/dto/CustomerContractDTO.java new file mode 100644 index 00000000..1ad718c0 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/query/dto/CustomerContractDTO.java @@ -0,0 +1,19 @@ +package stanl_2.final_backend.domain.customer.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class CustomerContractDTO { + private String contractId; + private String centerName; + private String contractCarName; + private String contractTTL; + private String contractTotalSale; + private String contractState; +} diff --git a/src/main/java/stanl_2/final_backend/domain/customer/query/dto/CustomerDTO.java b/src/main/java/stanl_2/final_backend/domain/customer/query/dto/CustomerDTO.java new file mode 100644 index 00000000..4f9c63db --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/query/dto/CustomerDTO.java @@ -0,0 +1,25 @@ +package stanl_2.final_backend.domain.customer.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class CustomerDTO { + private String customerId; + private String name; + private Integer age; + private String sex; + private String phone; + private String emergePhone; + private String email; + private String memberId; + private Boolean active; + private String createdAt; + private String updatedAt; + private String deletedAt; +} diff --git a/src/main/java/stanl_2/final_backend/domain/customer/query/dto/CustomerExcelDTO.java b/src/main/java/stanl_2/final_backend/domain/customer/query/dto/CustomerExcelDTO.java new file mode 100644 index 00000000..61abba59 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/query/dto/CustomerExcelDTO.java @@ -0,0 +1,34 @@ +package stanl_2.final_backend.domain.customer.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import stanl_2.final_backend.global.excel.ExcelColumnName; + +@Getter +@AllArgsConstructor +public class CustomerExcelDTO { + + @ExcelColumnName(name = "고객 번호") + private String customerId; + + @ExcelColumnName(name = "고객명") + private String name; + + @ExcelColumnName(name = "고객 나이") + private Integer age; + + @ExcelColumnName(name = "고객 성별") + private String sex; + + @ExcelColumnName(name = "고객 연락처") + private String phone; + + @ExcelColumnName(name = "고객 비상연락처") + private String emergePhone; + + @ExcelColumnName(name = "고객 메일") + private String email; + + @ExcelColumnName(name = "고객 담당자(사원) 번호") + private String memberId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/customer/query/dto/CustomerNameListDTO.java b/src/main/java/stanl_2/final_backend/domain/customer/query/dto/CustomerNameListDTO.java new file mode 100644 index 00000000..0d124769 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/query/dto/CustomerNameListDTO.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.customer.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class CustomerNameListDTO{ + private String name; +} diff --git a/src/main/java/stanl_2/final_backend/domain/customer/query/dto/CustomerSearchDTO.java b/src/main/java/stanl_2/final_backend/domain/customer/query/dto/CustomerSearchDTO.java new file mode 100644 index 00000000..4ca3e2b6 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/query/dto/CustomerSearchDTO.java @@ -0,0 +1,18 @@ +package stanl_2.final_backend.domain.customer.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class CustomerSearchDTO { + private String customerId; + private String name; + private String sex; + private String phone; + private String memberId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/customer/query/repository/CustomerMapper.java b/src/main/java/stanl_2/final_backend/domain/customer/query/repository/CustomerMapper.java new file mode 100644 index 00000000..a060aade --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/query/repository/CustomerMapper.java @@ -0,0 +1,34 @@ +package stanl_2.final_backend.domain.customer.query.repository; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import stanl_2.final_backend.domain.customer.query.dto.CustomerContractDTO; +import stanl_2.final_backend.domain.customer.query.dto.CustomerDTO; +import stanl_2.final_backend.domain.customer.query.dto.CustomerExcelDTO; +import stanl_2.final_backend.domain.customer.query.dto.CustomerSearchDTO; + +import java.util.List; +import java.util.Map; + +@Mapper +public interface CustomerMapper { + CustomerDTO selectCustomerInfoById(@Param("customerId") String customerId); + + List selectCustomerList(@Param("offset") int offset, @Param("size") int size); + + int selectCustomerCount(); + + List findCustomerByConditions(Map map); + + int findCustomerCnt(Map map); + + CustomerDTO selectCustomerInfoByPhone(String customerPhone); + + List findCustomerContractById(Map map); + + int selectCustomerContractCnt(String customerId); + + List findCustomerForExcel(); + + List findCustomerInfoByName(String customerName); +} diff --git a/src/main/java/stanl_2/final_backend/domain/customer/query/service/CustomerQueryService.java b/src/main/java/stanl_2/final_backend/domain/customer/query/service/CustomerQueryService.java new file mode 100644 index 00000000..a4cc866b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/query/service/CustomerQueryService.java @@ -0,0 +1,29 @@ +package stanl_2.final_backend.domain.customer.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import stanl_2.final_backend.domain.customer.query.dto.CustomerContractDTO; +import stanl_2.final_backend.domain.customer.query.dto.CustomerDTO; +import stanl_2.final_backend.domain.customer.query.dto.CustomerSearchDTO; + +import java.security.GeneralSecurityException; +import java.util.List; + +public interface CustomerQueryService { + CustomerDTO selectCustomerInfo(String customerId) throws GeneralSecurityException; + + List selectCustomerId(String customerName) throws GeneralSecurityException; + + Page selectCustomerList(Pageable pageable) throws GeneralSecurityException; + + Page findCustomerByCondition(Pageable pageable, CustomerSearchDTO customerSearchDTO) throws GeneralSecurityException; + + CustomerDTO selectCustomerInfoByPhone(String customerPhone) throws GeneralSecurityException; + + String selectCustomerNameById(String customerId) throws GeneralSecurityException; + + Page selectCustomerContractInfo(String customerId, Pageable pageable); + + void exportCustomerToExcel(HttpServletResponse response); +} diff --git a/src/main/java/stanl_2/final_backend/domain/customer/query/service/CustomerQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/customer/query/service/CustomerQueryServiceImpl.java new file mode 100644 index 00000000..bf97f3a7 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/customer/query/service/CustomerQueryServiceImpl.java @@ -0,0 +1,194 @@ +package stanl_2.final_backend.domain.customer.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.customer.common.exception.CustomerCommonException; +import stanl_2.final_backend.domain.customer.common.exception.CustomerErrorCode; +import stanl_2.final_backend.domain.customer.query.dto.CustomerContractDTO; +import stanl_2.final_backend.domain.customer.query.dto.CustomerDTO; +import stanl_2.final_backend.domain.customer.query.dto.CustomerExcelDTO; +import stanl_2.final_backend.domain.customer.query.dto.CustomerSearchDTO; +import stanl_2.final_backend.domain.customer.query.repository.CustomerMapper; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.global.excel.ExcelUtilsV1; +import stanl_2.final_backend.global.utils.AESUtils; + +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +@Service("queryCustomerService") +public class CustomerQueryServiceImpl implements CustomerQueryService{ + + private final CustomerMapper customerMapper; + private final AESUtils aesUtils; + private final MemberQueryService memberQueryService; + private final ExcelUtilsV1 excelUtilsV1; + + @Autowired + public CustomerQueryServiceImpl(CustomerMapper customerMapper, + AESUtils aesUtils, + MemberQueryService memberQueryService, ExcelUtilsV1 excelUtilsV1) { + this.customerMapper = customerMapper; + this.aesUtils = aesUtils; + this.memberQueryService = memberQueryService; + this.excelUtilsV1 = excelUtilsV1; + } + + @Override + @Transactional(readOnly = true) + public CustomerDTO selectCustomerInfo(String customerId) throws GeneralSecurityException { + + CustomerDTO customerInfoDTO = customerMapper.selectCustomerInfoById(customerId); + + customerInfoDTO.setPhone(aesUtils.decrypt(customerInfoDTO.getPhone())); + customerInfoDTO.setEmergePhone(aesUtils.decrypt(customerInfoDTO.getEmergePhone())); + customerInfoDTO.setEmail(aesUtils.decrypt(customerInfoDTO.getEmail())); + + return customerInfoDTO; + } + + @Override + @Transactional(readOnly = true) + public Page selectCustomerList(Pageable pageable) throws GeneralSecurityException { + int page = pageable.getPageNumber(); + int size = pageable.getPageSize(); + List customerList = customerMapper.selectCustomerList(page*size, size); + int totalElements = customerMapper.selectCustomerCount(); + + // 복호화 + for(int i=0;i< customerList.size();i++){ + customerList.get(i).setPhone(aesUtils.decrypt(customerList.get(i).getPhone())); + customerList.get(i).setEmergePhone(aesUtils.decrypt(customerList.get(i).getEmergePhone())); + customerList.get(i).setEmail(aesUtils.decrypt(customerList.get(i).getEmail())); + } + + + return new PageImpl<>(customerList, pageable, totalElements); + } + + @Override + @Transactional(readOnly = true) + public Page findCustomerByCondition(Pageable pageable, CustomerSearchDTO customerSearchDTO) throws GeneralSecurityException { + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + + // 정렬 정보 가져오기 + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + Map params = new HashMap<>(); + params.put("offset", offset); + params.put("size", size); + params.put("customerId", customerSearchDTO.getCustomerId()); + params.put("name", customerSearchDTO.getName()); + params.put("sex", customerSearchDTO.getSex()); + params.put("phone", aesUtils.encrypt(customerSearchDTO.getPhone())); + params.put("memberId", customerSearchDTO.getMemberId()); + + params.put("sortField", sortField); + params.put("sortOrder", sortOrder); + + List customerList = customerMapper.findCustomerByConditions(params); + + Integer count = customerMapper.findCustomerCnt(params); + + for(int i=0;i< customerList.size();i++){ + customerList.get(i).setPhone(aesUtils.decrypt(customerList.get(i).getPhone())); + // 이름으로 변환 + customerList.get(i).setMemberId(memberQueryService.selectNameById(customerList.get(i).getMemberId())); + } + + return new PageImpl<>(customerList, pageable, count); + } + + @Override + @Transactional(readOnly = true) + public CustomerDTO selectCustomerInfoByPhone(String customerPhone) throws GeneralSecurityException { + + String encryptedPhone = aesUtils.encrypt(customerPhone); + + CustomerDTO customerInfoDTO = customerMapper.selectCustomerInfoByPhone(encryptedPhone); + + if (customerInfoDTO == null) { + return null; + } + + customerInfoDTO.setPhone(aesUtils.decrypt(customerInfoDTO.getPhone())); + customerInfoDTO.setEmergePhone(aesUtils.decrypt(customerInfoDTO.getEmergePhone())); + customerInfoDTO.setEmail(aesUtils.decrypt(customerInfoDTO.getEmail())); + + return customerInfoDTO; + } + + @Override + @Transactional(readOnly = true) + public String selectCustomerNameById(String customerId) throws GeneralSecurityException { + + CustomerDTO customerInfoDTO = customerMapper.selectCustomerInfoById(customerId); + +// String customerName = aesUtils.decrypt(customerInfoDTO.getName()); + +// return customerName; + return customerInfoDTO.getName(); + } + + @Override + public List selectCustomerId(String customerName) throws GeneralSecurityException { + List customerInfoDTO = customerMapper.findCustomerInfoByName(customerName); + + List customerIds = new ArrayList<>(); + + customerInfoDTO.forEach(customerInfo -> { + customerIds.add(customerInfo.getCustomerId()); + }); + + return customerIds; + } + + @Override + @Transactional(readOnly = true) + public Page selectCustomerContractInfo(String customerId, Pageable pageable) { + + int page = pageable.getPageNumber(); + int size = pageable.getPageSize(); + + Map params = new HashMap<>(); + params.put("customerId", customerId); + params.put("offset", page*size); + params.put("size", size); + + List customerContractDTOList = customerMapper.findCustomerContractById(params); + + int totalElements = customerMapper.selectCustomerContractCnt(customerId); + + return new PageImpl<>(customerContractDTOList, pageable, totalElements); + } + + @Override + public void exportCustomerToExcel(HttpServletResponse response) { + List customerExcels = customerMapper.findCustomerForExcel(); + + + if(customerExcels == null) { + throw new CustomerCommonException(CustomerErrorCode.CUSTOMER_NOT_FOUND); + } + + excelUtilsV1.download(CustomerExcelDTO.class, customerExcels, "customerExcel", response); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/dashBoard/common/exception/DashBoardCommonException.java b/src/main/java/stanl_2/final_backend/domain/dashBoard/common/exception/DashBoardCommonException.java new file mode 100644 index 00000000..48085043 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/dashBoard/common/exception/DashBoardCommonException.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.dashBoard.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class DashBoardCommonException extends RuntimeException { + private final DashBoardErrorCode sampleErrorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.sampleErrorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/dashBoard/common/exception/DashBoardErrorCode.java b/src/main/java/stanl_2/final_backend/domain/dashBoard/common/exception/DashBoardErrorCode.java new file mode 100644 index 00000000..565e7b3b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/dashBoard/common/exception/DashBoardErrorCode.java @@ -0,0 +1,52 @@ +package stanl_2.final_backend.domain.dashBoard.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum DashBoardErrorCode { + + /** + * 400(Bad Request) + * 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. + */ + + + + /** + * 401(Unauthorized) + * 비록 HTTP 표준에서는 "미승인(unauthorized)"를 명확히 하고 있지만, + * 의미상 이 응답은 "비인증(unauthenticated)"을 의미합니다. + * 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로를 인증해야 합니다. + */ + + + /** + * 403(Forbidden) + * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. + * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. + */ + + + + /** + * 404(Not Found) + * 서버는 요청받은 리소스를 찾을 수 없습니다. 브라우저에서는 알려지지 않은 URL을 의미합니다. + * 이것은 API에서 종점은 적절하지만 리소스 자체는 존재하지 않음을 의미할 수도 있습니다. + * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. + * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. + */ + DATA_NOT_FOUND(40401, HttpStatus.NOT_FOUND, "해당 정보를 찾을 수 없습니다."), + + /** + * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. + */ + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); + + + private final Integer code; + private final HttpStatus httpStatus; + private final String msg; +} diff --git a/src/main/java/stanl_2/final_backend/domain/dashBoard/common/exception/DashBoardExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/dashBoard/common/exception/DashBoardExceptionResponse.java new file mode 100644 index 00000000..17739910 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/dashBoard/common/exception/DashBoardExceptionResponse.java @@ -0,0 +1,22 @@ +package stanl_2.final_backend.domain.dashBoard.common.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class DashBoardExceptionResponse { + private final Integer code; + private final String msg; + private final HttpStatus httpStatus; + + public DashBoardExceptionResponse(DashBoardErrorCode sampleErrorCode) { + this.code = sampleErrorCode.getCode(); + this.msg = sampleErrorCode.getMsg(); + this.httpStatus = sampleErrorCode.getHttpStatus(); + } + + public static DashBoardExceptionResponse of(DashBoardErrorCode sampleErrorCode) { + return new DashBoardExceptionResponse(sampleErrorCode); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/dashBoard/common/response/DashBoardResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/dashBoard/common/response/DashBoardResponseMessage.java new file mode 100644 index 00000000..079af1d4 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/dashBoard/common/response/DashBoardResponseMessage.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.dashBoard.common.response; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +public class DashBoardResponseMessage { + private int httpStatus; + private String msg; + private Object result; +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/dashBoard/query/config/MybatisConfiguration.java b/src/main/java/stanl_2/final_backend/domain/dashBoard/query/config/MybatisConfiguration.java new file mode 100644 index 00000000..9ca29118 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/dashBoard/query/config/MybatisConfiguration.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.dashBoard.query.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Configuration; + +@Configuration("dashBoardMybatisConfiguration") +@MapperScan(basePackages = "stanl_2.final_backend.domain.dashBoard.query.repository") +public class MybatisConfiguration { +} diff --git a/src/main/java/stanl_2/final_backend/domain/dashBoard/query/controller/DashBoardController.java b/src/main/java/stanl_2/final_backend/domain/dashBoard/query/controller/DashBoardController.java new file mode 100644 index 00000000..2406b79d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/dashBoard/query/controller/DashBoardController.java @@ -0,0 +1,93 @@ +package stanl_2.final_backend.domain.dashBoard.query.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import stanl_2.final_backend.domain.customer.common.response.CustomerResponseMessage; +import stanl_2.final_backend.domain.dashBoard.common.response.DashBoardResponseMessage; +import stanl_2.final_backend.domain.dashBoard.query.dto.DashBoardAdminDTO; +import stanl_2.final_backend.domain.dashBoard.query.dto.DashBoardDirectorDTO; +import stanl_2.final_backend.domain.dashBoard.query.dto.DashBoardEmployeeDTO; +import stanl_2.final_backend.domain.dashBoard.query.service.DashBoardQueryService; + +import java.security.GeneralSecurityException; +import java.security.Principal; + +@Slf4j +@RestController("queryDashBoardController") +@RequestMapping("/api/v1/dashBoard") +public class DashBoardController { + + private final DashBoardQueryService dashBoardQueryService; + + @Autowired + public DashBoardController(DashBoardQueryService dashBoardQueryService) { + this.dashBoardQueryService = dashBoardQueryService; + } + + @Operation(summary = "대시보드 정보 조회 (사원)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "대시보드 조회 성공(사원)", + content = {@Content(schema = @Schema(implementation = CustomerResponseMessage.class))}) + }) + @GetMapping("/employee") + public ResponseEntity selectDashBoardForEmployee(Principal principal) throws GeneralSecurityException { + + String memberLoginId = principal.getName(); + + DashBoardEmployeeDTO boardResponseDTO = dashBoardQueryService.selectInfoForEmployee(memberLoginId); + + return ResponseEntity.ok(DashBoardResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(boardResponseDTO) + .build()); + } + + @Operation(summary = "대시보드 정보 조회 (관리자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "대시보드 조회 성공(관리자)", + content = {@Content(schema = @Schema(implementation = CustomerResponseMessage.class))}) + }) + @GetMapping("/admin") + public ResponseEntity selectDashBoardForAdmin(Principal principal) throws GeneralSecurityException { + + String memberLoginId = principal.getName(); + + DashBoardAdminDTO boardResponseDTO = dashBoardQueryService.selectInfoForAdmin(memberLoginId); + + return ResponseEntity.ok(DashBoardResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(boardResponseDTO) + .build()); + } + + @Operation(summary = "대시보드 정보 조회 (담당자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "대시보드 조회 성공(담당자)", + content = {@Content(schema = @Schema(implementation = CustomerResponseMessage.class))}) + }) + @GetMapping("/director") + public ResponseEntity selectDashBoardForDirector(Principal principal) throws GeneralSecurityException { + + String memberLoginId = principal.getName(); + + DashBoardDirectorDTO boardResponseDTO = dashBoardQueryService.selectInfoForDirector(memberLoginId); + + return ResponseEntity.ok(DashBoardResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(boardResponseDTO) + .build()); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/dashBoard/query/dto/DashBoardAdminDTO.java b/src/main/java/stanl_2/final_backend/domain/dashBoard/query/dto/DashBoardAdminDTO.java new file mode 100644 index 00000000..5fd37cb2 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/dashBoard/query/dto/DashBoardAdminDTO.java @@ -0,0 +1,28 @@ +package stanl_2.final_backend.domain.dashBoard.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; +import java.util.Map; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class DashBoardAdminDTO { + + private Integer unreadContract; + private Integer unreadOrder; + private Integer unreadPurchaseOrder; + + private Integer totalPrice; + + private List> noticeList; + + private List memberList; + private String memberLoginId; + private String memberId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/dashBoard/query/dto/DashBoardDirectorDTO.java b/src/main/java/stanl_2/final_backend/domain/dashBoard/query/dto/DashBoardDirectorDTO.java new file mode 100644 index 00000000..60f2e33b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/dashBoard/query/dto/DashBoardDirectorDTO.java @@ -0,0 +1,29 @@ +package stanl_2.final_backend.domain.dashBoard.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class DashBoardDirectorDTO { + + private Integer totalContract; + private Integer totalOrder; + private Integer totalPurchaseOrder; + + private BigDecimal totalPrice; + + private List centerList; + private List> noticeList; + + private String memberLoginId; + private String memberId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/dashBoard/query/dto/DashBoardEmployeeDTO.java b/src/main/java/stanl_2/final_backend/domain/dashBoard/query/dto/DashBoardEmployeeDTO.java new file mode 100644 index 00000000..e134c5c7 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/dashBoard/query/dto/DashBoardEmployeeDTO.java @@ -0,0 +1,28 @@ +package stanl_2.final_backend.domain.dashBoard.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; +import java.util.Map; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class DashBoardEmployeeDTO { + + private Integer unreadContract; + private Integer unreadOrder; + + private Integer totalPrice; + + private List scheduleTitle; + private List> noticeList; + + private List memberList; + private String memberLoginId; + private String memberId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/dashBoard/query/repository/DashBoardMapper.java b/src/main/java/stanl_2/final_backend/domain/dashBoard/query/repository/DashBoardMapper.java new file mode 100644 index 00000000..6764a34a --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/dashBoard/query/repository/DashBoardMapper.java @@ -0,0 +1,7 @@ +package stanl_2.final_backend.domain.dashBoard.query.repository; + +import stanl_2.final_backend.domain.dashBoard.query.dto.DashBoardAdminDTO; + +public interface DashBoardMapper { + DashBoardAdminDTO findDashBoardInfoByMemberId(String memberId); +} diff --git a/src/main/java/stanl_2/final_backend/domain/dashBoard/query/service/DashBoardQueryService.java b/src/main/java/stanl_2/final_backend/domain/dashBoard/query/service/DashBoardQueryService.java new file mode 100644 index 00000000..71e36a40 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/dashBoard/query/service/DashBoardQueryService.java @@ -0,0 +1,17 @@ +package stanl_2.final_backend.domain.dashBoard.query.service; + +import stanl_2.final_backend.domain.dashBoard.query.dto.DashBoardAdminDTO; +import stanl_2.final_backend.domain.dashBoard.query.dto.DashBoardDirectorDTO; +import stanl_2.final_backend.domain.dashBoard.query.dto.DashBoardEmployeeDTO; + +import java.security.GeneralSecurityException; + +public interface DashBoardQueryService { +// DashBoardAdminDTO selectInfoForEmployee(String memberLoginId) throws GeneralSecurityException; + + DashBoardAdminDTO selectInfoForAdmin(String memberLoginId) throws GeneralSecurityException; + + DashBoardEmployeeDTO selectInfoForEmployee(String memberLoginId) throws GeneralSecurityException; + + DashBoardDirectorDTO selectInfoForDirector(String memberLoginId) throws GeneralSecurityException; +} diff --git a/src/main/java/stanl_2/final_backend/domain/dashBoard/query/service/DashBoardQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/dashBoard/query/service/DashBoardQueryServiceImpl.java new file mode 100644 index 00000000..174d51ec --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/dashBoard/query/service/DashBoardQueryServiceImpl.java @@ -0,0 +1,359 @@ +package stanl_2.final_backend.domain.dashBoard.query.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.contract.query.dto.ContractSearchDTO; +import stanl_2.final_backend.domain.contract.query.service.ContractQueryService; +import stanl_2.final_backend.domain.dashBoard.query.dto.DashBoardAdminDTO; +import stanl_2.final_backend.domain.dashBoard.query.dto.DashBoardDirectorDTO; +import stanl_2.final_backend.domain.dashBoard.query.dto.DashBoardEmployeeDTO; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.domain.notices.query.dto.NoticeDTO; +import stanl_2.final_backend.domain.notices.query.dto.SearchDTO; +import stanl_2.final_backend.domain.notices.query.service.NoticeQueryService; +import stanl_2.final_backend.domain.order.query.dto.OrderSelectSearchDTO; +import stanl_2.final_backend.domain.order.query.service.OrderQueryService; +import stanl_2.final_backend.domain.purchase_order.query.dto.PurchaseOrderSelectSearchDTO; +import stanl_2.final_backend.domain.purchase_order.query.service.PurchaseOrderQueryService; +import stanl_2.final_backend.domain.sales_history.query.dto.SalesHistoryRankedDataDTO; +import stanl_2.final_backend.domain.sales_history.query.dto.SalesHistorySearchDTO; +import stanl_2.final_backend.domain.sales_history.query.dto.SalesHistoryStatisticsDTO; +import stanl_2.final_backend.domain.sales_history.query.service.SalesHistoryQueryService; +import stanl_2.final_backend.domain.schedule.query.dto.ScheduleDayDTO; +import stanl_2.final_backend.domain.schedule.query.service.ScheduleQueryService; + +import java.math.BigDecimal; +import java.security.GeneralSecurityException; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +@Service("queryDashBoardService") +public class DashBoardQueryServiceImpl implements DashBoardQueryService { + + private final AuthQueryService authQueryService; + private final ContractQueryService contractQueryService; + private final OrderQueryService orderQueryService; + private final PurchaseOrderQueryService purchaseOrderQueryService; + private final SalesHistoryQueryService salesHistoryQueryService; + private final ScheduleQueryService scheduleQueryService; + private final NoticeQueryService noticeQueryService; + private final MemberQueryService memberQueryService; + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + + public DashBoardQueryServiceImpl(AuthQueryService authQueryService, ContractQueryService contractQueryService, + OrderQueryService orderQueryService, PurchaseOrderQueryService purchaseOrderQueryService, + SalesHistoryQueryService salesHistoryQueryService, ScheduleQueryService scheduleQueryService, + NoticeQueryService noticeQueryService, MemberQueryService memberQueryService) { + this.authQueryService = authQueryService; + this.contractQueryService = contractQueryService; + this.orderQueryService = orderQueryService; + this.purchaseOrderQueryService = purchaseOrderQueryService; + this.salesHistoryQueryService = salesHistoryQueryService; + this.scheduleQueryService = scheduleQueryService; + this.noticeQueryService = noticeQueryService; + this.memberQueryService = memberQueryService; + } + + @Override + @Transactional(readOnly = true) + public DashBoardEmployeeDTO selectInfoForEmployee(String memberLoginId) throws GeneralSecurityException { + + DashBoardEmployeeDTO dashBoardEmployeeDTO = new DashBoardEmployeeDTO(); + + String memberId = authQueryService.selectMemberIdByLoginId(memberLoginId); + String centerId = memberQueryService.selectMemberInfo(memberLoginId).getCenterId(); + + // Pageable을 null로 전달하거나 유효한 Pageable 사용 + Pageable pageable = PageRequest.of(0, 100); + + String startAt = getCurrentTime().substring(0, 7) + "-01"; + String endAt = getCurrentTime().substring(0,10); + + LocalDate date = LocalDate.parse(endAt); + LocalDate nextDate = date.plusDays(1); + // 프론트에서 +1 처리했기 떄문에 따로 백에서 처리 + String salesEndAt = nextDate.toString(); + + + // 이번달 Contract 받아오기 + ContractSearchDTO contractSearchDTO = new ContractSearchDTO(); + contractSearchDTO.setMemberId(memberLoginId); + contractSearchDTO.setSearchMemberId(memberId); + contractSearchDTO.setStartDate(startAt); + contractSearchDTO.setEndDate(endAt); + Integer unreadContract = Math.toIntExact(contractQueryService.selectBySearchEmployee(contractSearchDTO, pageable).getTotalElements()); + dashBoardEmployeeDTO.setUnreadContract(unreadContract); + + // 이번달 Order 받아오기 + OrderSelectSearchDTO orderSelectSearchDTO = new OrderSelectSearchDTO(); + orderSelectSearchDTO.setMemberId(memberLoginId); + orderSelectSearchDTO.setStartDate(startAt); + orderSelectSearchDTO.setEndDate(endAt); + Integer unreadOrder = Math.toIntExact(orderQueryService.selectSearchOrdersEmployee(orderSelectSearchDTO, pageable).getTotalElements()); + dashBoardEmployeeDTO.setUnreadOrder(unreadOrder); + + // 이번달 판매내역 받아오기 + SalesHistorySearchDTO salesHistorySearchDTO = new SalesHistorySearchDTO(); + salesHistorySearchDTO.setSearcherName(memberLoginId); + salesHistorySearchDTO.setStartDate(startAt); + salesHistorySearchDTO.setEndDate(salesEndAt); + + SalesHistoryStatisticsDTO resultStatistics = salesHistoryQueryService.selectStatisticsSearchByEmployee(salesHistorySearchDTO); + Integer totalPrice = resultStatistics.getTotalSales(); + dashBoardEmployeeDTO.setTotalPrice(totalPrice); + + // 오늘 일정조회 + ArrayList scheduleList = new ArrayList(); + + List todaySchedules = scheduleQueryService.findSchedulesByDate(endAt); + + for (ScheduleDayDTO schedule : todaySchedules) { + scheduleList.add(schedule.getName()); + } + dashBoardEmployeeDTO.setScheduleTitle(scheduleList); + + // 이번달 판매사원 순위 + ArrayList employeeList = new ArrayList(); + ArrayList centerList = new ArrayList(); + centerList.add(centerId); + + SalesHistoryRankedDataDTO salesHistoryRankedDataDTO = new SalesHistoryRankedDataDTO(); + salesHistoryRankedDataDTO.setCenterList(centerList); + salesHistoryRankedDataDTO.setPeriod("month"); + salesHistoryRankedDataDTO.setStartDate(startAt); + salesHistoryRankedDataDTO.setEndDate(salesEndAt); + salesHistoryRankedDataDTO.setGroupBy("employee"); + salesHistoryRankedDataDTO.setOrderBy("totalSales"); + + Page rankPage = salesHistoryQueryService.selectStatisticsBySearch(salesHistoryRankedDataDTO, pageable); + + List contentList = rankPage.getContent(); + + for (int i = 0; i < Math.min(contentList.size(), 5); i++) { + employeeList.add(contentList.get(i).getMemberId()); + } + dashBoardEmployeeDTO.setMemberList(employeeList); + + // 공지사항 조회 (제목, 내용(redirect)) + ArrayList> noticeList = new ArrayList<>(); + + SearchDTO searchDTO = new SearchDTO(); + searchDTO.setTag("ALL"); + Page noticePage = noticeQueryService.findNotices(pageable, searchDTO); + + for (NoticeDTO notice : noticePage.getContent()) { + Map noticeData = new HashMap<>(); + noticeData.put("title", notice.getTitle()); // NoticeDTO의 title + noticeData.put("content", "/notice/detail?tag=" + notice.getTag() + "&classification=" + notice.getClassification() + + "¬iceTitle=" + notice.getTitle() + "¬iceContent=" + notice.getContent() + + "¬iceId=" + notice.getNoticeId()); // NoticeDTO의 redirectUrl + noticeList.add(noticeData); + } + + dashBoardEmployeeDTO.setNoticeList(noticeList); + + return dashBoardEmployeeDTO; + } + + @Override + @Transactional(readOnly = true) + public DashBoardAdminDTO selectInfoForAdmin(String memberLoginId) throws GeneralSecurityException { + + DashBoardAdminDTO dashBoardAdminDTO = new DashBoardAdminDTO(); + + String memberId = authQueryService.selectMemberIdByLoginId(memberLoginId); + String centerId = memberQueryService.selectMemberInfo(memberLoginId).getCenterId(); + + // Pageable을 null로 전달하거나 유효한 Pageable 사용 + Pageable pageable = PageRequest.of(0, 100); + + String startAt = getCurrentTime().substring(0, 7) + "-01"; + String endAt = getCurrentTime().substring(0,10); + + LocalDate date = LocalDate.parse(endAt); + LocalDate nextDate = date.plusDays(1); + // 프론트에서 +1 처리했기 떄문에 따로 백에서 처리 + String salesEndAt = nextDate.toString(); + + // 이번달 Contract 받아오기 + ContractSearchDTO contractSearchDTO = new ContractSearchDTO(); + contractSearchDTO.setMemberId(memberLoginId); + contractSearchDTO.setSearchMemberId(memberId); + contractSearchDTO.setStartDate(startAt); + contractSearchDTO.setEndDate(endAt); + Integer unreadContract = Math.toIntExact(contractQueryService.selectBySearchEmployee(contractSearchDTO, pageable).getTotalElements()); + dashBoardAdminDTO.setUnreadContract(unreadContract); + + // 이번달 Order 받아오기 + OrderSelectSearchDTO orderSelectSearchDTO = new OrderSelectSearchDTO(); + orderSelectSearchDTO.setMemberId(memberLoginId); + orderSelectSearchDTO.setStartDate(startAt); + orderSelectSearchDTO.setEndDate(endAt); + Integer unreadOrder = Math.toIntExact(orderQueryService.selectSearchOrdersEmployee(orderSelectSearchDTO, pageable).getTotalElements()); + dashBoardAdminDTO.setUnreadOrder(unreadOrder); + + // 이번달 PurchaseOrder 받아오기 + PurchaseOrderSelectSearchDTO purchaseOrderSelectSearchDTO = new PurchaseOrderSelectSearchDTO(); + purchaseOrderSelectSearchDTO.setMemberId(memberLoginId); + purchaseOrderSelectSearchDTO.setSearchMemberId(memberId); + purchaseOrderSelectSearchDTO.setStartDate(startAt); + purchaseOrderSelectSearchDTO.setEndDate(endAt); + Integer unreadPurchaseOrder = Math.toIntExact(purchaseOrderQueryService.selectSearchPurchaseOrderAdmin(purchaseOrderSelectSearchDTO, pageable).getTotalElements()); + dashBoardAdminDTO.setUnreadPurchaseOrder(unreadPurchaseOrder); + + // 이번달 판매내역 받아오기 + SalesHistorySearchDTO salesHistorySearchDTO = new SalesHistorySearchDTO(); + salesHistorySearchDTO.setSearcherName(memberLoginId); + salesHistorySearchDTO.setStartDate(startAt); + salesHistorySearchDTO.setEndDate(salesEndAt); + + SalesHistoryStatisticsDTO resultStatistics = salesHistoryQueryService.selectStatisticsSearchByEmployee(salesHistorySearchDTO); + Integer totalPrice = resultStatistics.getTotalSales(); + dashBoardAdminDTO.setTotalPrice(totalPrice); + + // 이번달 판매사원 순위 + ArrayList employeeList = new ArrayList(); + ArrayList centerList = new ArrayList(); + centerList.add(centerId); + + SalesHistoryRankedDataDTO salesHistoryRankedDataDTO = new SalesHistoryRankedDataDTO(); + salesHistoryRankedDataDTO.setCenterList(centerList); + salesHistoryRankedDataDTO.setPeriod("month"); + salesHistoryRankedDataDTO.setStartDate(startAt); + salesHistoryRankedDataDTO.setEndDate(salesEndAt); + salesHistoryRankedDataDTO.setGroupBy("employee"); + salesHistoryRankedDataDTO.setOrderBy("totalSales"); + + Page rankPage = salesHistoryQueryService.selectStatisticsBySearch(salesHistoryRankedDataDTO, pageable); + + List contentList = rankPage.getContent(); + + for (int i = 0; i < Math.min(contentList.size(), 5); i++) { + employeeList.add(contentList.get(i).getMemberId()); + } + dashBoardAdminDTO.setMemberList(employeeList); + + // 공지사항 조회 (제목, 내용(redirect)) + ArrayList> noticeList = new ArrayList<>(); + + SearchDTO searchDTO = new SearchDTO(); + searchDTO.setTag("ALL"); + Page noticePage = noticeQueryService.findNotices(pageable, searchDTO); + + for (NoticeDTO notice : noticePage.getContent()) { + Map noticeData = new HashMap<>(); + noticeData.put("title", notice.getTitle()); // NoticeDTO의 title + noticeData.put("content", "/notice/detail?tag=" + notice.getTag() + "&classification=" + notice.getClassification() + + "¬iceTitle=" + notice.getTitle() + "¬iceContent=" + notice.getContent() + + "¬iceId=" + notice.getNoticeId()); // NoticeDTO의 redirectUrl + noticeList.add(noticeData); + } + dashBoardAdminDTO.setNoticeList(noticeList); + + return dashBoardAdminDTO; + } + + @Override + @Transactional(readOnly = true) + public DashBoardDirectorDTO selectInfoForDirector(String memberLoginId) throws GeneralSecurityException { + + DashBoardDirectorDTO dashBoardDirectorDTO = new DashBoardDirectorDTO(); + + // Pageable을 null로 전달하거나 유효한 Pageable 사용 + Pageable pageable = PageRequest.of(0, 100); + + String startAt = getCurrentTime().substring(0, 7) + "-01"; + String endAt = getCurrentTime().substring(0,10); + + LocalDate date = LocalDate.parse(endAt); + LocalDate nextDate = date.plusDays(1); + // 프론트에서 +1 처리했기 떄문에 따로 백에서 처리 + String salesEndAt = nextDate.toString(); + + // 이번 달 계약서 전체 갯수 조회 + ContractSearchDTO contractSearchDTO = new ContractSearchDTO(); + contractSearchDTO.setStartDate(startAt); + contractSearchDTO.setEndDate(salesEndAt); + Page result = contractQueryService.selectBySearch(contractSearchDTO, pageable); + Integer totalContract = Math.toIntExact(contractQueryService.selectBySearch(contractSearchDTO, pageable).getTotalElements()); + dashBoardDirectorDTO.setTotalContract(totalContract); + + // 이번 달 수주서 전체 갯수 조회 + OrderSelectSearchDTO orderSelectSearchDTO = new OrderSelectSearchDTO(); + orderSelectSearchDTO.setStartDate(startAt); + orderSelectSearchDTO.setEndDate(endAt); + Integer totalOrder = Math.toIntExact(orderQueryService.selectSearchOrders(orderSelectSearchDTO, pageable).getTotalElements()); + dashBoardDirectorDTO.setTotalOrder(totalOrder); + + // 이번 달 발주서 전체 갯수 조회 + PurchaseOrderSelectSearchDTO purchaseOrderSelectSearchDTO = new PurchaseOrderSelectSearchDTO(); + purchaseOrderSelectSearchDTO.setStartDate(startAt); + purchaseOrderSelectSearchDTO.setEndDate(endAt); + Integer totalPurchaseOrder = Math.toIntExact(purchaseOrderQueryService.selectSearchPurchaseOrder(purchaseOrderSelectSearchDTO, pageable).getTotalElements()); + dashBoardDirectorDTO.setTotalPurchaseOrder(totalPurchaseOrder); + + // 이번 달 전체 판매 내역 조회 + SalesHistoryRankedDataDTO salesHistoryRankedDataDTO = new SalesHistoryRankedDataDTO(); + salesHistoryRankedDataDTO.setStartDate(startAt); + salesHistoryRankedDataDTO.setEndDate(salesEndAt); + Page resultStatistics = salesHistoryQueryService.selectAllStatstics(salesHistoryRankedDataDTO, pageable); + BigDecimal totalPrice = resultStatistics.getContent().get(0).getTotalSales(); + dashBoardDirectorDTO.setTotalPrice(totalPrice); + + // 판매 매장 실적 순위 + ArrayList centerList = new ArrayList(); + + SalesHistoryRankedDataDTO salesHistoryRankedCenterDataDTO = new SalesHistoryRankedDataDTO(); + salesHistoryRankedCenterDataDTO.setPeriod("month"); + salesHistoryRankedCenterDataDTO.setStartDate(startAt); + salesHistoryRankedCenterDataDTO.setEndDate(salesEndAt); + + salesHistoryRankedCenterDataDTO.setGroupBy("center"); + salesHistoryRankedCenterDataDTO.setOrderBy("totalSales"); + + Page rankPage = salesHistoryQueryService.selectStatisticsBySearch(salesHistoryRankedCenterDataDTO, pageable); + + List content = rankPage.getContent(); + + for (int i = 0; i < Math.min(content.size(), 5); i++) { + centerList.add(content.get(i).getCenterId()); + } + + dashBoardDirectorDTO.setCenterList(centerList); + + // 공지사항 조회 (제목, 내용(redirect)) + ArrayList> noticeList = new ArrayList<>(); + + SearchDTO searchDTO = new SearchDTO(); + searchDTO.setTag("ALL"); + Page noticePage = noticeQueryService.findNotices(pageable, searchDTO); + + for (NoticeDTO notice : noticePage.getContent()) { + Map noticeData = new HashMap<>(); + noticeData.put("title", notice.getTitle()); // NoticeDTO의 title + noticeData.put("content", "/notice/detail?tag=" + notice.getTag() + "&classification=" + notice.getClassification() + + "¬iceTitle=" + notice.getTitle() + "¬iceContent=" + notice.getContent() + + "¬iceId=" + notice.getNoticeId()); // NoticeDTO의 redirectUrl + noticeList.add(noticeData); + } + dashBoardDirectorDTO.setNoticeList(noticeList); + + return dashBoardDirectorDTO; + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/education/command/application/controller/EducationController.java b/src/main/java/stanl_2/final_backend/domain/education/command/application/controller/EducationController.java new file mode 100644 index 00000000..10981cd6 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/education/command/application/controller/EducationController.java @@ -0,0 +1,18 @@ +package stanl_2.final_backend.domain.education.command.application.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import stanl_2.final_backend.domain.education.command.application.service.EducationCommandService; + +@RestController("commandEducationController") +@RequestMapping("/api/v1/education") +public class EducationController { + + private final EducationCommandService educationCommandService; + + @Autowired + public EducationController(EducationCommandService educationCommandService) { + this.educationCommandService = educationCommandService; + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/education/command/application/dto/EducationModifyDTO.java b/src/main/java/stanl_2/final_backend/domain/education/command/application/dto/EducationModifyDTO.java new file mode 100644 index 00000000..da10b246 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/education/command/application/dto/EducationModifyDTO.java @@ -0,0 +1,12 @@ +package stanl_2.final_backend.domain.education.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class EducationModifyDTO { + private String graduationDate; + private String note; +} diff --git a/src/main/java/stanl_2/final_backend/domain/education/command/application/dto/EducationRegistDTO.java b/src/main/java/stanl_2/final_backend/domain/education/command/application/dto/EducationRegistDTO.java new file mode 100644 index 00000000..4818a9e9 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/education/command/application/dto/EducationRegistDTO.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.education.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class EducationRegistDTO { + private String entranceDate; + private String graduationDate; + private String name; + private String major; + private String score; + private String note; +} diff --git a/src/main/java/stanl_2/final_backend/domain/education/command/application/service/EducationCommandService.java b/src/main/java/stanl_2/final_backend/domain/education/command/application/service/EducationCommandService.java new file mode 100644 index 00000000..4a7f2538 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/education/command/application/service/EducationCommandService.java @@ -0,0 +1,4 @@ +package stanl_2.final_backend.domain.education.command.application.service; + +public interface EducationCommandService { +} diff --git a/src/main/java/stanl_2/final_backend/domain/education/command/domain/aggregate/entity/Education.java b/src/main/java/stanl_2/final_backend/domain/education/command/domain/aggregate/entity/Education.java new file mode 100644 index 00000000..d936666b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/education/command/domain/aggregate/entity/Education.java @@ -0,0 +1,76 @@ +package stanl_2.final_backend.domain.education.command.domain.aggregate.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "tb_education") +public class Education { + @Id + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name = "prefix", value = "EDU") + ) + @Column(name = "EDU_ID", nullable = false) + private String educationId; + + @Column(name = "EDU_ENTD", nullable = false) + private String entranceDate; + + @Column(name = "EDU_GRAD", nullable = false) + private String graduationDate; + + @Column(name = "EDU_NAME", nullable = false) + private String name; + + @Column(name = "EDU_MJR", nullable = false) + private String major; + + @Column(name = "EDU_SCO") + private String score; + + @Column(name = "EDU_NOTE") + private String note; + + @Column(name = "CREATED_AT", nullable = false, updatable = false) + private String createdAt; + + @Column(name = "UPDATED_AT", nullable = false) + private String updatedAt; + + @Column(name = "MEM_ID", nullable = false) + private String memId; + + /* 설명. updatedAt 자동화 */ + // Insert 되기 전에 실행 + @PrePersist + private void prePersist() { + this.createdAt = getCurrentTime(); + this.updatedAt = this.createdAt; + } + + // Update 되기 전에 실행 + @PreUpdate + private void preUpdate() { + this.updatedAt = getCurrentTime(); + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/education/command/domain/repository/EducationRepository.java b/src/main/java/stanl_2/final_backend/domain/education/command/domain/repository/EducationRepository.java new file mode 100644 index 00000000..9ce9f649 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/education/command/domain/repository/EducationRepository.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.education.command.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import stanl_2.final_backend.domain.education.command.domain.aggregate.entity.Education; + +@Repository +public interface EducationRepository extends JpaRepository { +} diff --git a/src/main/java/stanl_2/final_backend/domain/education/command/domain/service/EducationCommandServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/education/command/domain/service/EducationCommandServiceImpl.java new file mode 100644 index 00000000..3a8679f1 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/education/command/domain/service/EducationCommandServiceImpl.java @@ -0,0 +1,22 @@ +package stanl_2.final_backend.domain.education.command.domain.service; + + +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import stanl_2.final_backend.domain.education.command.application.service.EducationCommandService; +import stanl_2.final_backend.domain.education.command.domain.repository.EducationRepository; + +@Service("commandEducationService") +public class EducationCommandServiceImpl implements EducationCommandService { + + private final EducationRepository educationRepository; + private final ModelMapper modelMapper; + + @Autowired + public EducationCommandServiceImpl(EducationRepository educationRepository, + ModelMapper modelMapper) { + this.educationRepository = educationRepository; + this.modelMapper = modelMapper; + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/education/common/exception/EducationCommonException.java b/src/main/java/stanl_2/final_backend/domain/education/common/exception/EducationCommonException.java new file mode 100644 index 00000000..c6922d4e --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/education/common/exception/EducationCommonException.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.education.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class EducationCommonException extends RuntimeException { + private final EducationErrorCode sampleErrorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.sampleErrorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/education/common/exception/EducationErrorCode.java b/src/main/java/stanl_2/final_backend/domain/education/common/exception/EducationErrorCode.java new file mode 100644 index 00000000..04b614c5 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/education/common/exception/EducationErrorCode.java @@ -0,0 +1,52 @@ +package stanl_2.final_backend.domain.education.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum EducationErrorCode { + + /** + * 400(Bad Request) + * 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. + */ + + + + /** + * 401(Unauthorized) + * 비록 HTTP 표준에서는 "미승인(unauthorized)"를 명확히 하고 있지만, + * 의미상 이 응답은 "비인증(unauthenticated)"을 의미합니다. + * 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로를 인증해야 합니다. + */ + + + /** + * 403(Forbidden) + * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. + * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. + */ + + + + /** + * 404(Not Found) + * 서버는 요청받은 리소스를 찾을 수 없습니다. 브라우저에서는 알려지지 않은 URL을 의미합니다. + * 이것은 API에서 종점은 적절하지만 리소스 자체는 존재하지 않음을 의미할 수도 있습니다. + * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. + * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. + */ + SAMPLE_NOT_FOUND(404001, HttpStatus.NOT_FOUND, "sample 데이터를 찾지 못했습니다"), + CENTER_NOT_FOUND(404002, HttpStatus.NOT_FOUND, "center 데이터를 찾지 못했습니다."), + /** + * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. + */ + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); + + + private final Integer code; + private final HttpStatus httpStatus; + private final String msg; +} diff --git a/src/main/java/stanl_2/final_backend/domain/education/common/exception/EducationExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/education/common/exception/EducationExceptionResponse.java new file mode 100644 index 00000000..b74b7b22 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/education/common/exception/EducationExceptionResponse.java @@ -0,0 +1,22 @@ +package stanl_2.final_backend.domain.education.common.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class EducationExceptionResponse { + private final Integer code; + private final String msg; + private final HttpStatus httpStatus; + + public EducationExceptionResponse(EducationErrorCode sampleErrorCode) { + this.code = sampleErrorCode.getCode(); + this.msg = sampleErrorCode.getMsg(); + this.httpStatus = sampleErrorCode.getHttpStatus(); + } + + public static EducationExceptionResponse of(EducationErrorCode sampleErrorCode) { + return new EducationExceptionResponse(sampleErrorCode); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/education/common/response/EducationResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/education/common/response/EducationResponseMessage.java new file mode 100644 index 00000000..5610fc21 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/education/common/response/EducationResponseMessage.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.education.common.response; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +public class EducationResponseMessage { + private int httpStatus; + private String msg; + private Object result; +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/education/query/config/MybatisConfiguration.java b/src/main/java/stanl_2/final_backend/domain/education/query/config/MybatisConfiguration.java new file mode 100644 index 00000000..06b4ade9 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/education/query/config/MybatisConfiguration.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.education.query.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Configuration; + +@Configuration("educationMybatisConfiguration") +@MapperScan(basePackages = "stanl_2.final_backend.domain.education.query.repository") +public class MybatisConfiguration { +} diff --git a/src/main/java/stanl_2/final_backend/domain/education/query/controller/EducationController.java b/src/main/java/stanl_2/final_backend/domain/education/query/controller/EducationController.java new file mode 100644 index 00000000..1f3fe70a --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/education/query/controller/EducationController.java @@ -0,0 +1,69 @@ +package stanl_2.final_backend.domain.education.query.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import stanl_2.final_backend.domain.education.common.response.EducationResponseMessage; +import stanl_2.final_backend.domain.education.query.dto.EducationDTO; +import stanl_2.final_backend.domain.education.query.service.EducationQueryService; + +import java.security.Principal; +import java.util.List; + +@RestController(value = "queryEducationController") +@RequestMapping("/api/v1/education") +public class EducationController { + + private final EducationQueryService educationQueryService; + + @Autowired + public EducationController(EducationQueryService educationQueryService) { + this.educationQueryService = educationQueryService; + } + + @Operation(summary = "학력 조회(with 사번)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = EducationResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/other/{loginId}") + public ResponseEntity getEducation(@PathVariable String loginId){ + + List educationList = educationQueryService.selectEducationList(loginId); + + return ResponseEntity.ok(EducationResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(educationList) + .build()); + } + + @Operation(summary = "학력 조회(접속중인 사용자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = EducationResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("") + public ResponseEntity getCertification(Principal principal){ + + List educationList = educationQueryService.selectEducationList(principal.getName()); + + return ResponseEntity.ok(EducationResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(educationList) + .build()); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/education/query/dto/EducationDTO.java b/src/main/java/stanl_2/final_backend/domain/education/query/dto/EducationDTO.java new file mode 100644 index 00000000..bc6aa52e --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/education/query/dto/EducationDTO.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.education.query.dto; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class EducationDTO { + private String entranceDate; + private String graduationDate; + private String name; + private String major; + private String score; + private String note; +} diff --git a/src/main/java/stanl_2/final_backend/domain/education/query/repository/EducationMapper.java b/src/main/java/stanl_2/final_backend/domain/education/query/repository/EducationMapper.java new file mode 100644 index 00000000..30d67fcf --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/education/query/repository/EducationMapper.java @@ -0,0 +1,12 @@ +package stanl_2.final_backend.domain.education.query.repository; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import stanl_2.final_backend.domain.education.query.dto.EducationDTO; + +import java.util.List; + +@Mapper +public interface EducationMapper { + List selectEducationInfo(@Param("memberId") String memberId); +} diff --git a/src/main/java/stanl_2/final_backend/domain/education/query/service/EducationQueryService.java b/src/main/java/stanl_2/final_backend/domain/education/query/service/EducationQueryService.java new file mode 100644 index 00000000..57041bbe --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/education/query/service/EducationQueryService.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.education.query.service; + +import stanl_2.final_backend.domain.education.query.dto.EducationDTO; + +import java.util.List; + +public interface EducationQueryService { + List selectEducationList(String loginId); +} diff --git a/src/main/java/stanl_2/final_backend/domain/education/query/service/EducationQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/education/query/service/EducationQueryServiceImpl.java new file mode 100644 index 00000000..8a51b2a1 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/education/query/service/EducationQueryServiceImpl.java @@ -0,0 +1,36 @@ +package stanl_2.final_backend.domain.education.query.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.certification.query.dto.CertificationDTO; +import stanl_2.final_backend.domain.education.query.dto.EducationDTO; +import stanl_2.final_backend.domain.education.query.repository.EducationMapper; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; + +import java.util.List; + +@Service("queryEducationService") +public class EducationQueryServiceImpl implements EducationQueryService{ + + private final EducationMapper educationMapper; + private final AuthQueryService authQueryService; + + @Autowired + public EducationQueryServiceImpl(EducationMapper educationMapper, + AuthQueryService authQueryService) { + this.educationMapper = educationMapper; + this.authQueryService = authQueryService; + } + + @Override + @Transactional(readOnly = true) + public List selectEducationList(String loginId) { + + String memberId = authQueryService.selectMemberIdByLoginId(loginId); + + List educationList = educationMapper.selectEducationInfo(memberId); + + return educationList; + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/evaluation/command/application/controller/EvaluationController.java b/src/main/java/stanl_2/final_backend/domain/evaluation/command/application/controller/EvaluationController.java new file mode 100644 index 00000000..826de7cb --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/evaluation/command/application/controller/EvaluationController.java @@ -0,0 +1,85 @@ +package stanl_2.final_backend.domain.evaluation.command.application.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import stanl_2.final_backend.domain.A_sample.common.response.SampleResponseMessage; +import stanl_2.final_backend.domain.evaluation.command.application.dto.EvaluationModifyDTO; +import stanl_2.final_backend.domain.evaluation.command.application.dto.EvaluationRegistDTO; +import stanl_2.final_backend.domain.evaluation.command.application.service.EvaluationCommandService; +import stanl_2.final_backend.domain.evaluation.common.response.EvaluationResponseMessage; + +@RestController("commandEvaluationController") +@RequestMapping("/api/v1/evaluation") +public class EvaluationController { + + private final EvaluationCommandService evaluationCommandService; + + @Autowired + public EvaluationController(EvaluationCommandService evaluationCommandService) { + this.evaluationCommandService = evaluationCommandService; + } + + @Operation(summary = "평가서 등록") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = EvaluationResponseMessage.class))}) + }) + @PostMapping("") + public ResponseEntity postEvaluation(@RequestPart("dto") EvaluationRegistDTO evaluationRegistRequestDTO, + @RequestPart("file") MultipartFile fileUrl) { + + evaluationCommandService.registerEvaluation(evaluationRegistRequestDTO, fileUrl); + + return ResponseEntity.ok(EvaluationResponseMessage.builder() + .httpStatus(200) + .msg("평가서 등록 성공") + .result(null) + .build()); + } + + @Operation(summary = "평가서 수정") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = SampleResponseMessage.class))}) + }) + @PutMapping("{id}") + public ResponseEntity putEvaluation(@PathVariable String id, + @RequestPart("dto") EvaluationModifyDTO evaluationModifyRequestDTO, + @RequestPart("file") MultipartFile fileUrl) { + + evaluationModifyRequestDTO.setEvaluationId(id); + + evaluationCommandService.modifyEvaluation(evaluationModifyRequestDTO, fileUrl); + + return ResponseEntity.ok(EvaluationResponseMessage.builder() + .httpStatus(200) + .msg("평가서 수정 성공") + .result(null) + .build()); + } + + @Operation(summary = "평가서 삭제") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = EvaluationResponseMessage.class))}) + }) + @DeleteMapping("{id}") + public ResponseEntity deleteEvaluation(@PathVariable String id) { + + evaluationCommandService.deleteEvaluation(id); + + return ResponseEntity.ok(EvaluationResponseMessage.builder() + .httpStatus(200) + .msg("평가서 삭제 성공") + .result(null) + .build()); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/evaluation/command/application/dto/EvaluationModifyDTO.java b/src/main/java/stanl_2/final_backend/domain/evaluation/command/application/dto/EvaluationModifyDTO.java new file mode 100644 index 00000000..055ec1f8 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/evaluation/command/application/dto/EvaluationModifyDTO.java @@ -0,0 +1,17 @@ +package stanl_2.final_backend.domain.evaluation.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class EvaluationModifyDTO { + private String EvaluationId; + private String title; + private String content; + private String centerId; + private String memberId; + private String writerId; + private String fileUrl; +} diff --git a/src/main/java/stanl_2/final_backend/domain/evaluation/command/application/dto/EvaluationRegistDTO.java b/src/main/java/stanl_2/final_backend/domain/evaluation/command/application/dto/EvaluationRegistDTO.java new file mode 100644 index 00000000..707b4a2b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/evaluation/command/application/dto/EvaluationRegistDTO.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.evaluation.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class EvaluationRegistDTO { + private String title; + private String content; + private String memberId; + private String writerId; + private String centerId; + private String fileUrl; +} diff --git a/src/main/java/stanl_2/final_backend/domain/evaluation/command/application/service/EvaluationCommandService.java b/src/main/java/stanl_2/final_backend/domain/evaluation/command/application/service/EvaluationCommandService.java new file mode 100644 index 00000000..bcbec2f7 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/evaluation/command/application/service/EvaluationCommandService.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.evaluation.command.application.service; + +import org.springframework.web.multipart.MultipartFile; +import stanl_2.final_backend.domain.A_sample.command.application.dto.SampleModifyDTO; +import stanl_2.final_backend.domain.evaluation.command.application.dto.EvaluationModifyDTO; +import stanl_2.final_backend.domain.evaluation.command.application.dto.EvaluationRegistDTO; + +public interface EvaluationCommandService { + void registerEvaluation(EvaluationRegistDTO evaluationRegistRequestDTO, MultipartFile fileUrl); + + void modifyEvaluation(EvaluationModifyDTO evaluationModifyRequestDTO, MultipartFile fileUrl); + + void deleteEvaluation(String id); +} diff --git a/src/main/java/stanl_2/final_backend/domain/evaluation/command/domain/aggregate/entity/Evaluation.java b/src/main/java/stanl_2/final_backend/domain/evaluation/command/domain/aggregate/entity/Evaluation.java new file mode 100644 index 00000000..eebf59b7 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/evaluation/command/domain/aggregate/entity/Evaluation.java @@ -0,0 +1,79 @@ +package stanl_2.final_backend.domain.evaluation.command.domain.aggregate.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "TB_EVALUATION") +public class Evaluation { + @Id + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name = "prefix", value = "EVA") + ) + @Column(name = "EVAL_ID") + private String evaluationId; + + @Column(name = "EVAL_TTL", nullable = false) + private String title; + + @Column(name = "EVAL_CONT", nullable = false) + private String content; + + @Column(name = "CREATED_AT", nullable = false, updatable = false) + private String createdAt; + + @Column(name = "UPDATED_AT", nullable = false) + private String updatedAt; + + @Column(name = "DELETED_AT") + private String deletedAt; + + @Column(name = "ACTIVE", nullable = false) + private Boolean active = true; + + @Column(name = "CENT_ID", nullable = false) + private String centerId; + + @Column(name = "MEM_ID", nullable = false) + private String memberId; + + @Column(name = "WRI_ID", nullable = false) + private String writerId; + + @Column(name = "FILE_URL", nullable = false) + private String fileUrl; + + + @PrePersist + private void prePersist() { + this.createdAt = getCurrentTime(); + this.updatedAt = this.createdAt; + } + + @PreUpdate + private void preUpdate() { + this.updatedAt = getCurrentTime(); + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } +} + + diff --git a/src/main/java/stanl_2/final_backend/domain/evaluation/command/domain/repository/EvaluationRepository.java b/src/main/java/stanl_2/final_backend/domain/evaluation/command/domain/repository/EvaluationRepository.java new file mode 100644 index 00000000..140d06ec --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/evaluation/command/domain/repository/EvaluationRepository.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.evaluation.command.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import stanl_2.final_backend.domain.evaluation.command.domain.aggregate.entity.Evaluation; + +@Repository +public interface EvaluationRepository extends JpaRepository { +} diff --git a/src/main/java/stanl_2/final_backend/domain/evaluation/command/domain/service/EvaluationCommandServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/evaluation/command/domain/service/EvaluationCommandServiceImpl.java new file mode 100644 index 00000000..0bfe55b5 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/evaluation/command/domain/service/EvaluationCommandServiceImpl.java @@ -0,0 +1,82 @@ +package stanl_2.final_backend.domain.evaluation.command.domain.service; + +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import stanl_2.final_backend.domain.evaluation.command.application.dto.EvaluationModifyDTO; +import stanl_2.final_backend.domain.evaluation.command.application.dto.EvaluationRegistDTO; +import stanl_2.final_backend.domain.evaluation.command.application.service.EvaluationCommandService; +import stanl_2.final_backend.domain.evaluation.command.domain.aggregate.entity.Evaluation; +import stanl_2.final_backend.domain.evaluation.command.domain.repository.EvaluationRepository; +import stanl_2.final_backend.domain.evaluation.common.exception.EvaluationCommonException; +import stanl_2.final_backend.domain.evaluation.common.exception.EvaluationErrorCode; +import stanl_2.final_backend.domain.s3.command.application.service.S3FileService; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@Service("commandEvaluationService") +public class EvaluationCommandServiceImpl implements EvaluationCommandService { + + private final EvaluationRepository evaluationRepository; + private final ModelMapper modelMapper; + private final S3FileService s3FileService; + + @Autowired + public EvaluationCommandServiceImpl(EvaluationRepository evaluationRepository, ModelMapper modelMapper, S3FileService s3FileService) { + this.evaluationRepository = evaluationRepository; + this.modelMapper = modelMapper; + this.s3FileService = s3FileService; + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + + @Override + @Transactional + public void registerEvaluation(EvaluationRegistDTO evaluationRegistRequestDTO, MultipartFile fileUrl) { + + Evaluation evaluation = modelMapper.map(evaluationRegistRequestDTO, Evaluation.class); + + evaluation.setFileUrl(s3FileService.uploadOneFile(fileUrl)); + + evaluationRepository.save(evaluation); + } + + @Override + @Transactional + public void modifyEvaluation(EvaluationModifyDTO evaluationModifyRequestDTO, MultipartFile fileUrl) { + Evaluation evaluation = evaluationRepository.findById(evaluationModifyRequestDTO.getEvaluationId()) + .orElseThrow(() -> new EvaluationCommonException(EvaluationErrorCode.EVALUATION_NOT_FOUND)); + + s3FileService.deleteFile(evaluation.getFileUrl()); + + Evaluation updateEvaluation = modelMapper.map(evaluationModifyRequestDTO, Evaluation.class); + + updateEvaluation.setFileUrl(s3FileService.uploadOneFile(fileUrl)); + + updateEvaluation.setEvaluationId(evaluation.getEvaluationId()); + updateEvaluation.setCreatedAt(evaluation.getCreatedAt()); + updateEvaluation.setUpdatedAt(getCurrentTime()); + updateEvaluation.setActive(evaluation.getActive()); + + evaluationRepository.save(updateEvaluation); + } + + @Override + @Transactional + public void deleteEvaluation(String id) { + Evaluation evaluation = evaluationRepository.findById(id) + .orElseThrow(() -> new EvaluationCommonException(EvaluationErrorCode.EVALUATION_NOT_FOUND)); + + evaluation.setActive(false); + evaluation.setDeletedAt(getCurrentTime()); + + evaluationRepository.save(evaluation); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/evaluation/common/exception/EvaluationCommonException.java b/src/main/java/stanl_2/final_backend/domain/evaluation/common/exception/EvaluationCommonException.java new file mode 100644 index 00000000..d5ed1115 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/evaluation/common/exception/EvaluationCommonException.java @@ -0,0 +1,17 @@ +package stanl_2.final_backend.domain.evaluation.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import stanl_2.final_backend.domain.A_sample.common.exception.SampleErrorCode; + +@Getter +@RequiredArgsConstructor +public class EvaluationCommonException extends RuntimeException { + private final EvaluationErrorCode evaluationErrorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.evaluationErrorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/evaluation/common/exception/EvaluationErrorCode.java b/src/main/java/stanl_2/final_backend/domain/evaluation/common/exception/EvaluationErrorCode.java new file mode 100644 index 00000000..971dfd3a --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/evaluation/common/exception/EvaluationErrorCode.java @@ -0,0 +1,52 @@ +package stanl_2.final_backend.domain.evaluation.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum EvaluationErrorCode { + + /** + * 400(Bad Request) + * 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. + */ + + + + /** + * 401(Unauthorized) + * 비록 HTTP 표준에서는 "미승인(unauthorized)"를 명확히 하고 있지만, + * 의미상 이 응답은 "비인증(unauthenticated)"을 의미합니다. + * 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로를 인증해야 합니다. + */ + + + /** + * 403(Forbidden) + * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. + * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. + */ + + + + /** + * 404(Not Found) + * 서버는 요청받은 리소스를 찾을 수 없습니다. 브라우저에서는 알려지지 않은 URL을 의미합니다. + * 이것은 API에서 종점은 적절하지만 리소스 자체는 존재하지 않음을 의미할 수도 있습니다. + * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. + * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. + */ + EVALUATION_NOT_FOUND(404002, HttpStatus.NOT_FOUND, "evaluation 데이터를 찾지 못했습니다."), + MEMBER_CENTER_NOT_FOUND(404002, HttpStatus.NOT_FOUND, "member, center 데이터를 찾지 못했습니다."), + /** + * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. + */ + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); + + + private final Integer code; + private final HttpStatus httpStatus; + private final String msg; +} diff --git a/src/main/java/stanl_2/final_backend/domain/evaluation/common/exception/EvaluationExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/evaluation/common/exception/EvaluationExceptionResponse.java new file mode 100644 index 00000000..27890ff2 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/evaluation/common/exception/EvaluationExceptionResponse.java @@ -0,0 +1,23 @@ +package stanl_2.final_backend.domain.evaluation.common.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; +import stanl_2.final_backend.domain.A_sample.common.exception.SampleErrorCode; + +@Getter +public class EvaluationExceptionResponse { + private final Integer code; + private final String msg; + private final HttpStatus httpStatus; + + public EvaluationExceptionResponse(EvaluationErrorCode evaluationErrorCode) { + this.code = evaluationErrorCode.getCode(); + this.msg = evaluationErrorCode.getMsg(); + this.httpStatus = evaluationErrorCode.getHttpStatus(); + } + + public static EvaluationExceptionResponse of(EvaluationErrorCode evaluationErrorCode) { + return new EvaluationExceptionResponse(evaluationErrorCode); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/evaluation/common/response/EvaluationResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/evaluation/common/response/EvaluationResponseMessage.java new file mode 100644 index 00000000..3ae1f6d2 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/evaluation/common/response/EvaluationResponseMessage.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.evaluation.common.response; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +public class EvaluationResponseMessage { + private int httpStatus; + private String msg; + private Object result; +} diff --git a/src/main/java/stanl_2/final_backend/domain/evaluation/common/util/EvaluationRequestList.java b/src/main/java/stanl_2/final_backend/domain/evaluation/common/util/EvaluationRequestList.java new file mode 100644 index 00000000..81af7e45 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/evaluation/common/util/EvaluationRequestList.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.evaluation.common.util; + +import lombok.*; +import org.springframework.data.domain.Pageable; + +@Builder +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class EvaluationRequestList { + private T data; + private Pageable pageable; +} diff --git a/src/main/java/stanl_2/final_backend/domain/evaluation/query/controller/EvaluationController.java b/src/main/java/stanl_2/final_backend/domain/evaluation/query/controller/EvaluationController.java new file mode 100644 index 00000000..ace4bd7c --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/evaluation/query/controller/EvaluationController.java @@ -0,0 +1,207 @@ +package stanl_2.final_backend.domain.evaluation.query.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stanl_2.final_backend.domain.evaluation.common.response.EvaluationResponseMessage; +import stanl_2.final_backend.domain.evaluation.query.dto.EvaluationDTO; +import stanl_2.final_backend.domain.evaluation.query.dto.EvaluationSearchDTO; +import stanl_2.final_backend.domain.evaluation.query.service.EvaluationQueryService; +import stanl_2.final_backend.domain.product.common.response.ProductResponseMessage; + +import java.security.GeneralSecurityException; +import java.security.Principal; +import java.util.Map; + +@RestController(value = "queryEvaluationController") +@RequestMapping("/api/v1/evaluation") +public class EvaluationController { + + private final EvaluationQueryService evaluationQueryService; + + @Autowired + public EvaluationController(EvaluationQueryService evaluationQueryService) { + this.evaluationQueryService = evaluationQueryService; + } + + @Operation(summary = "평가서 관리자 전체 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = EvaluationResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/manager") + public ResponseEntity getAllEvaluationsByManager(Principal principal, + @PageableDefault(size = 20) Pageable pageable, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder) throws GeneralSecurityException { + EvaluationDTO evaluationDTO = new EvaluationDTO(); + + evaluationDTO.setMemberId(principal.getName()); + + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + Page responseEvaluations = evaluationQueryService.selectAllEvaluationsByManager(evaluationDTO, pageable); + + return ResponseEntity.ok(EvaluationResponseMessage.builder() + .httpStatus(200) + .msg("평가서 관리자 전체 조회 성공") + .result(responseEvaluations) + .build()); + } + + @Operation(summary = "평가서 담당자 전체 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = EvaluationResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/representative") + public ResponseEntity getAllEvaluationsByRepresentative(Principal principal, + @PageableDefault(size = 20) Pageable pageable, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder) throws GeneralSecurityException { + EvaluationDTO evaluationDTO = new EvaluationDTO(); + + evaluationDTO.setMemberId(principal.getName()); + + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + Page responseEvaluations = evaluationQueryService.selectAllEvaluationsByRepresentative(evaluationDTO, pageable); + + return ResponseEntity.ok(EvaluationResponseMessage.builder() + .httpStatus(200) + .msg("평가서 담당자 전체 조회 성공") + .result(responseEvaluations) + .build()); + } + + @Operation(summary = "평가서 상세 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = EvaluationResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("{id}") + public ResponseEntity getEvaluationDetail(@PathVariable String id) { + + EvaluationDTO evaluationDTO = evaluationQueryService.selectEvaluationById(id); + + return ResponseEntity.ok(EvaluationResponseMessage.builder() + .httpStatus(200) + .msg("평가서 상세 조회 성공") + .result(evaluationDTO) + .build()); + } + + + @Operation(summary = "평가서 관리자 검색") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = EvaluationResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/manager/search") + public ResponseEntity getEvaluationBySearchByManager(@RequestParam Map params + ,Principal principal + ,@PageableDefault(size = 20) Pageable pageable, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder) throws GeneralSecurityException { + + EvaluationSearchDTO evaluationSearchDTO = new EvaluationSearchDTO(); + evaluationSearchDTO.setEvaluationId(params.get("evaluationId")); + evaluationSearchDTO.setTitle(params.get("title")); + evaluationSearchDTO.setWriterName(params.get("writerName")); + evaluationSearchDTO.setMemberName(params.get("memberName")); + evaluationSearchDTO.setCenterId(params.get("centerId")); + evaluationSearchDTO.setStartDate(params.get("startDate")); + evaluationSearchDTO.setEndDate(params.get("endDate")); + evaluationSearchDTO.setSearcherName(principal.getName()); + + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + Page responseEvaluations = evaluationQueryService.selectEvaluationBySearchByManager(pageable, evaluationSearchDTO); + + return ResponseEntity.ok(EvaluationResponseMessage.builder() + .httpStatus(200) + .msg("관리자 검색 조회 성공") + .result(responseEvaluations) + .build()); + } + + @Operation(summary = "평가서 담당자 검색") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = EvaluationResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/representative/search") + public ResponseEntity getEvaluationBySearchByRepresentative(@RequestParam Map params + ,Principal principal + ,@PageableDefault(size = 20) Pageable pageable, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder) throws GeneralSecurityException { + + EvaluationSearchDTO evaluationSearchDTO = new EvaluationSearchDTO(); + evaluationSearchDTO.setEvaluationId(params.get("evaluationId")); + evaluationSearchDTO.setTitle(params.get("title")); + evaluationSearchDTO.setWriterName(params.get("writerName")); + evaluationSearchDTO.setMemberName(params.get("memberName")); + evaluationSearchDTO.setCenterId(params.get("centerId")); + evaluationSearchDTO.setStartDate(params.get("startDate")); + evaluationSearchDTO.setEndDate(params.get("endDate")); + evaluationSearchDTO.setSearcherName(principal.getName()); + + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + Page responseEvaluations = evaluationQueryService.selectEvaluationBySearchByRepresentative(pageable, evaluationSearchDTO); + + return ResponseEntity.ok(EvaluationResponseMessage.builder() + .httpStatus(200) + .msg("담당자 검색 조회 성공") + .result(responseEvaluations) + .build()); + } + + @Operation(summary = "평가서 엑셀 다운") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "평가서 엑셀 다운 성공", + content = {@Content(schema = @Schema(implementation = ProductResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/excel") + public void exportEvaluation(HttpServletResponse response){ + + evaluationQueryService.exportEvaluationToExcel(response); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/evaluation/query/dto/EvaluationDTO.java b/src/main/java/stanl_2/final_backend/domain/evaluation/query/dto/EvaluationDTO.java new file mode 100644 index 00000000..4696196b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/evaluation/query/dto/EvaluationDTO.java @@ -0,0 +1,22 @@ +package stanl_2.final_backend.domain.evaluation.query.dto; + +import lombok.*; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class EvaluationDTO { + private String evaluationId; + private String title; + private String content; + private String createdAt; + private String updatedAt; + private String centerId; + private String memberId; + private String writerId; + private String fileUrl; +} diff --git a/src/main/java/stanl_2/final_backend/domain/evaluation/query/dto/EvaluationExcelDownload.java b/src/main/java/stanl_2/final_backend/domain/evaluation/query/dto/EvaluationExcelDownload.java new file mode 100644 index 00000000..bf33f131 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/evaluation/query/dto/EvaluationExcelDownload.java @@ -0,0 +1,29 @@ +package stanl_2.final_backend.domain.evaluation.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import stanl_2.final_backend.global.excel.ExcelColumnName; + +@AllArgsConstructor +@Getter +@Setter +public class EvaluationExcelDownload { + @ExcelColumnName(name = "평가서번호") + private String evalId; + + @ExcelColumnName(name = "제목") + private String title; + + @ExcelColumnName(name = "매장이름") + private String centerId; + + @ExcelColumnName(name = "사원이름") + private String memberId; + + @ExcelColumnName(name = "평가작성자") + private String writerId; + + @ExcelColumnName(name = "작성일자") + private String createdAt; +} diff --git a/src/main/java/stanl_2/final_backend/domain/evaluation/query/dto/EvaluationSearchDTO.java b/src/main/java/stanl_2/final_backend/domain/evaluation/query/dto/EvaluationSearchDTO.java new file mode 100644 index 00000000..27fbb350 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/evaluation/query/dto/EvaluationSearchDTO.java @@ -0,0 +1,25 @@ +package stanl_2.final_backend.domain.evaluation.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class EvaluationSearchDTO { + private String evaluationId; + private String title; + private String writerName; + private String memberName; + private String centerId; + private String startDate; + private String endDate; + private String searcherName; + private String fileUrl; +} diff --git a/src/main/java/stanl_2/final_backend/domain/evaluation/query/repository/EvaluationMapper.java b/src/main/java/stanl_2/final_backend/domain/evaluation/query/repository/EvaluationMapper.java new file mode 100644 index 00000000..f08339f7 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/evaluation/query/repository/EvaluationMapper.java @@ -0,0 +1,49 @@ +package stanl_2.final_backend.domain.evaluation.query.repository; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import stanl_2.final_backend.domain.evaluation.query.dto.EvaluationDTO; +import stanl_2.final_backend.domain.evaluation.query.dto.EvaluationExcelDownload; +import stanl_2.final_backend.domain.evaluation.query.dto.EvaluationSearchDTO; + +import java.util.List; +import java.util.Map; + +@Mapper +public interface EvaluationMapper { + + List findEvaluationByCenterId(@Param("size") int size + ,@Param("offset") int offset + ,@Param("centerId") String centerId, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + EvaluationDTO findEvaluationById(@Param("id") String id); + + List findAllEvaluations(@Param("size") int size + ,@Param("offset") int offset, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + int findEvaluationCount(); + + int findEvaluationCountByCenterId(@Param("centerId") String centerId); + + List findEvaluationBySearch(@Param("size") int size + ,@Param("offset") int offset + ,@Param("evaluationSearchDTO") EvaluationSearchDTO evaluationSearchDTO, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + List findEvaluationByCenterIdAndSearch(@Param("size") int size + ,@Param("offset") int offset + ,@Param("evaluationSearchDTO") EvaluationSearchDTO evaluationSearchDTO, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + int findEvaluationBySearchCount(@Param("evaluationSearchDTO") EvaluationSearchDTO evaluationSearchDTO); + + int findEvaluationByCenterIdAndSearchCount(@Param("evaluationSearchDTO") EvaluationSearchDTO evaluationSearchDTO); + + List findEvaluationForExcel(); +} diff --git a/src/main/java/stanl_2/final_backend/domain/evaluation/query/service/EvaluationQueryService.java b/src/main/java/stanl_2/final_backend/domain/evaluation/query/service/EvaluationQueryService.java new file mode 100644 index 00000000..bd08f413 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/evaluation/query/service/EvaluationQueryService.java @@ -0,0 +1,24 @@ +package stanl_2.final_backend.domain.evaluation.query.service; + + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import stanl_2.final_backend.domain.evaluation.query.dto.EvaluationDTO; +import stanl_2.final_backend.domain.evaluation.query.dto.EvaluationSearchDTO; + +import java.security.GeneralSecurityException; +import java.util.Map; + +public interface EvaluationQueryService { + + EvaluationDTO selectEvaluationById(String id); + + Page selectAllEvaluationsByManager(EvaluationDTO evaluationDTO, Pageable pageable) throws GeneralSecurityException; + Page selectAllEvaluationsByRepresentative(EvaluationDTO evaluationDTO, Pageable pageable) throws GeneralSecurityException; + + Page selectEvaluationBySearchByManager(Pageable pageable, EvaluationSearchDTO evaluationSearchDTO) throws GeneralSecurityException; + Page selectEvaluationBySearchByRepresentative(Pageable pageable, EvaluationSearchDTO evaluationSearchDTO) throws GeneralSecurityException; + + void exportEvaluationToExcel(HttpServletResponse response); +} diff --git a/src/main/java/stanl_2/final_backend/domain/evaluation/query/service/EvaluationQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/evaluation/query/service/EvaluationQueryServiceImpl.java new file mode 100644 index 00000000..82d85d42 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/evaluation/query/service/EvaluationQueryServiceImpl.java @@ -0,0 +1,250 @@ +package stanl_2.final_backend.domain.evaluation.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.center.query.service.CenterQueryService; +import stanl_2.final_backend.domain.evaluation.common.exception.EvaluationCommonException; +import stanl_2.final_backend.domain.evaluation.common.exception.EvaluationErrorCode; +import stanl_2.final_backend.domain.evaluation.query.dto.EvaluationDTO; +import stanl_2.final_backend.domain.evaluation.query.dto.EvaluationExcelDownload; +import stanl_2.final_backend.domain.evaluation.query.dto.EvaluationSearchDTO; +import stanl_2.final_backend.domain.evaluation.query.repository.EvaluationMapper; +import stanl_2.final_backend.domain.member.query.dto.MemberDTO; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.global.excel.ExcelUtilsV1; + +import java.security.GeneralSecurityException; +import java.util.List; +import java.util.Optional; + +@Service +public class EvaluationQueryServiceImpl implements EvaluationQueryService { + + private final EvaluationMapper evaluationMapper; + private final MemberQueryService memberQueryService; + private final AuthQueryService authQueryService; + private final CenterQueryService centerQueryService; + private final ExcelUtilsV1 excelUtilsV1; + + @Autowired + public EvaluationQueryServiceImpl(EvaluationMapper evaluationMapper, MemberQueryService memberQueryService, AuthQueryService authQueryService, CenterQueryService centerQueryService, ExcelUtilsV1 excelUtilsV1) { + this.evaluationMapper = evaluationMapper; + this.memberQueryService = memberQueryService; + this.authQueryService = authQueryService; + this.centerQueryService = centerQueryService; + this.excelUtilsV1 = excelUtilsV1; + } + + @Override + @Transactional(readOnly = true) + public Page selectAllEvaluationsByManager(EvaluationDTO evaluationDTO, Pageable pageable) throws GeneralSecurityException { + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + + MemberDTO memberDTO = memberQueryService.selectMemberInfo(evaluationDTO.getMemberId()); + String centerId = memberDTO.getCenterId(); + + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + List evaluationList = evaluationMapper.findEvaluationByCenterId(size,offset, centerId, sortField, sortOrder); + + int total = evaluationMapper.findEvaluationCountByCenterId(centerId); + + if(evaluationList.isEmpty() || total == 0) { + throw new EvaluationCommonException(EvaluationErrorCode.EVALUATION_NOT_FOUND); + } + evaluationList.forEach(evaluation -> { + try { + evaluation.setMemberId(memberQueryService.selectNameById(evaluation.getMemberId())); + evaluation.setWriterId(memberQueryService.selectNameById(evaluation.getWriterId())); + evaluation.setCenterId(centerQueryService.selectNameById(evaluation.getCenterId())); + } catch (Exception e) { + throw new EvaluationCommonException(EvaluationErrorCode.MEMBER_CENTER_NOT_FOUND); + } + }); + return new PageImpl<>(evaluationList, pageable, total); + } + + @Override + @Transactional(readOnly = true) + public Page selectAllEvaluationsByRepresentative(EvaluationDTO evaluationDTO, Pageable pageable) { + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + + List evaluationList = evaluationMapper.findAllEvaluations(size,offset, sortField, sortOrder); + + int total = evaluationMapper.findEvaluationCount(); + + if(evaluationList.isEmpty() || total == 0) { + throw new EvaluationCommonException(EvaluationErrorCode.EVALUATION_NOT_FOUND); + } + + evaluationList.forEach(evaluation -> { + try { + evaluation.setMemberId(memberQueryService.selectNameById(evaluation.getMemberId())); + evaluation.setWriterId(memberQueryService.selectNameById(evaluation.getWriterId())); + evaluation.setCenterId(centerQueryService.selectNameById(evaluation.getCenterId())); + } catch (Exception e) { + throw new EvaluationCommonException(EvaluationErrorCode.MEMBER_CENTER_NOT_FOUND); + } + }); + + return new PageImpl<>(evaluationList, pageable, total); + } + + @Override + @Transactional(readOnly = true) + public EvaluationDTO selectEvaluationById(String id) { + + EvaluationDTO evaluationDTO = evaluationMapper.findEvaluationById(id); + + if(evaluationDTO == null){ + throw new EvaluationCommonException(EvaluationErrorCode.EVALUATION_NOT_FOUND); + } + + try { + evaluationDTO.setMemberId(memberQueryService.selectNameById(evaluationDTO.getMemberId())); + evaluationDTO.setWriterId(memberQueryService.selectNameById(evaluationDTO.getWriterId())); + evaluationDTO.setCenterId(centerQueryService.selectNameById(evaluationDTO.getCenterId())); + } catch (Exception e) { + throw new EvaluationCommonException(EvaluationErrorCode.MEMBER_CENTER_NOT_FOUND); + } + + return evaluationDTO; + } + + @Override + @Transactional(readOnly = true) + public Page selectEvaluationBySearchByManager(Pageable pageable, EvaluationSearchDTO evaluationSearchDTO) throws GeneralSecurityException { + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + String writerName = Optional.ofNullable(evaluationSearchDTO.getWriterName()).orElse(""); + if (!writerName.isEmpty()) { + evaluationSearchDTO.setWriterName(authQueryService.selectMemberIdByLoginId(writerName)); + } + String memberName = Optional.ofNullable(evaluationSearchDTO.getMemberName()).orElse(""); + if (!memberName.isEmpty()) { + evaluationSearchDTO.setMemberName(authQueryService.selectMemberIdByLoginId(memberName)); + } + + MemberDTO memberDTO = memberQueryService.selectMemberInfo(evaluationSearchDTO.getSearcherName()); + evaluationSearchDTO.setCenterId(memberDTO.getCenterId()); + + List evaluationList = evaluationMapper.findEvaluationByCenterIdAndSearch(size,offset, evaluationSearchDTO, sortField, sortOrder); + + int total = evaluationMapper.findEvaluationByCenterIdAndSearchCount(evaluationSearchDTO); + + if(evaluationList.isEmpty() || total == 0){ + throw new EvaluationCommonException(EvaluationErrorCode.EVALUATION_NOT_FOUND); + } + evaluationList.forEach(evaluation -> { + try { + evaluation.setMemberId(memberQueryService.selectNameById(evaluation.getMemberId())); + evaluation.setWriterId(memberQueryService.selectNameById(evaluation.getWriterId())); + evaluation.setCenterId(centerQueryService.selectNameById(evaluation.getCenterId())); + } catch (Exception e) { + throw new EvaluationCommonException(EvaluationErrorCode.MEMBER_CENTER_NOT_FOUND); + } + }); + return new PageImpl<>(evaluationList, pageable, total); + } + @Override + @Transactional(readOnly = true) + public Page selectEvaluationBySearchByRepresentative(Pageable pageable, EvaluationSearchDTO evaluationSearchDTO){ + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + String writerName = Optional.ofNullable(evaluationSearchDTO.getWriterName()).orElse(""); + if (!writerName.isEmpty()) { + evaluationSearchDTO.setWriterName(authQueryService.selectMemberIdByLoginId(writerName)); + } + String memberName = Optional.ofNullable(evaluationSearchDTO.getMemberName()).orElse(""); + if (!memberName.isEmpty()) { + evaluationSearchDTO.setMemberName(authQueryService.selectMemberIdByLoginId(memberName)); + } + + List evaluationList = evaluationMapper.findEvaluationBySearch(size,offset, evaluationSearchDTO, sortField, sortOrder); + + int total = evaluationMapper.findEvaluationBySearchCount(evaluationSearchDTO); + + if(evaluationList.isEmpty() || total == 0) { + throw new EvaluationCommonException(EvaluationErrorCode.EVALUATION_NOT_FOUND); + } + + evaluationList.forEach(evaluation -> { + try { + evaluation.setMemberId(memberQueryService.selectNameById(evaluation.getMemberId())); + evaluation.setWriterId(memberQueryService.selectNameById(evaluation.getWriterId())); + evaluation.setCenterId(centerQueryService.selectNameById(evaluation.getCenterId())); + } catch (Exception e) { + throw new EvaluationCommonException(EvaluationErrorCode.MEMBER_CENTER_NOT_FOUND); + } + }); + + return new PageImpl<>(evaluationList, pageable, total); + } + + @Override + @Transactional + public void exportEvaluationToExcel(HttpServletResponse response) { + List evaluationList = evaluationMapper.findEvaluationForExcel(); + + if(evaluationList == null) { + throw new EvaluationCommonException(EvaluationErrorCode.EVALUATION_NOT_FOUND); + } + + evaluationList.forEach(evaluation -> { + try { + evaluation.setMemberId(memberQueryService.selectNameById(evaluation.getMemberId())); + evaluation.setWriterId(memberQueryService.selectNameById(evaluation.getWriterId())); + evaluation.setCenterId(centerQueryService.selectNameById(evaluation.getCenterId())); + } catch (Exception e) { + throw new EvaluationCommonException(EvaluationErrorCode.MEMBER_CENTER_NOT_FOUND); + } + }); + + excelUtilsV1.download(EvaluationExcelDownload.class, evaluationList, "EvaluationExcel", response); + + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/family/command/application/controller/FamilyController.java b/src/main/java/stanl_2/final_backend/domain/family/command/application/controller/FamilyController.java new file mode 100644 index 00000000..b0b0f2ed --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/family/command/application/controller/FamilyController.java @@ -0,0 +1,18 @@ +package stanl_2.final_backend.domain.family.command.application.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import stanl_2.final_backend.domain.family.command.application.service.FamilyCommandService; + +@RestController("commandFamilyController") +@RequestMapping("/api/v1/family") +public class FamilyController { + + private final FamilyCommandService familyCommandService; + + @Autowired + public FamilyController(FamilyCommandService familyCommandService) { + this.familyCommandService = familyCommandService; + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/family/command/application/dto/FamilyModifyDTO.java b/src/main/java/stanl_2/final_backend/domain/family/command/application/dto/FamilyModifyDTO.java new file mode 100644 index 00000000..12d20579 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/family/command/application/dto/FamilyModifyDTO.java @@ -0,0 +1,15 @@ +package stanl_2.final_backend.domain.family.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class FamilyModifyDTO { + private String relation; + private String phone; + private Boolean disability; + private Boolean die; + private String note; +} diff --git a/src/main/java/stanl_2/final_backend/domain/family/command/application/dto/FamilyRegistDTO.java b/src/main/java/stanl_2/final_backend/domain/family/command/application/dto/FamilyRegistDTO.java new file mode 100644 index 00000000..0e3a340e --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/family/command/application/dto/FamilyRegistDTO.java @@ -0,0 +1,19 @@ +package stanl_2.final_backend.domain.family.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class FamilyRegistDTO { + private String name; + private String relation; + private String birth; + private String identNo; + private String phone; + private String sex; + private Boolean disability; + private Boolean die; + private String note; +} diff --git a/src/main/java/stanl_2/final_backend/domain/family/command/application/service/FamilyCommandService.java b/src/main/java/stanl_2/final_backend/domain/family/command/application/service/FamilyCommandService.java new file mode 100644 index 00000000..d1768acd --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/family/command/application/service/FamilyCommandService.java @@ -0,0 +1,4 @@ +package stanl_2.final_backend.domain.family.command.application.service; + +public interface FamilyCommandService { +} diff --git a/src/main/java/stanl_2/final_backend/domain/family/command/domain/aggregate/entity/Family.java b/src/main/java/stanl_2/final_backend/domain/family/command/domain/aggregate/entity/Family.java new file mode 100644 index 00000000..93d8f85b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/family/command/domain/aggregate/entity/Family.java @@ -0,0 +1,82 @@ +package stanl_2.final_backend.domain.family.command.domain.aggregate.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "tb_family") +public class Family { + @Id + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name = "prefix", value = "FAM") + ) + @Column(name = "FAM_ID", nullable = false) + private String familyId; + + @Column(name = "FAM_NAME", nullable = false) + private String name; + + @Column(name = "FAM_REL", nullable = false) + private String relation; + + @Column(name = "FAM_BIR", nullable = false) + private String birth; + + @Column(name = "FAM_IDEN_NO", nullable = false) + private String identNo; + + @Column(name = "FAM_PHO", nullable = false) + private String phone; + + @Column(name = "FAM_SEX", nullable = false) + private String sex; + + @Column(name = "FAM_DIS", nullable = false) + private Boolean disability; + + @Column(name = "FAM_DIE", nullable = false) + private Boolean die; + + @Column(name = "FAM_NOTE") + private String note; + + @Column(name = "CREATED_AT", nullable = false, updatable = false) + private String createdAt; + + @Column(name = "UPDATED_AT", nullable = false) + private String updatedAt; + + @Column(name = "MEM_ID", nullable = false) + private String memId; + + @PrePersist + private void prePersist() { + this.createdAt = getCurrentTime(); + this.updatedAt = this.createdAt; + } + + @PreUpdate + private void preUpdate() { + this.updatedAt = getCurrentTime(); + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/family/command/domain/repository/FamilyRepository.java b/src/main/java/stanl_2/final_backend/domain/family/command/domain/repository/FamilyRepository.java new file mode 100644 index 00000000..d9f57ab2 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/family/command/domain/repository/FamilyRepository.java @@ -0,0 +1,7 @@ +package stanl_2.final_backend.domain.family.command.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import stanl_2.final_backend.domain.family.command.domain.aggregate.entity.Family; + +public interface FamilyRepository extends JpaRepository { +} diff --git a/src/main/java/stanl_2/final_backend/domain/family/command/domain/service/FamilyCommandServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/family/command/domain/service/FamilyCommandServiceImpl.java new file mode 100644 index 00000000..c5ac5930 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/family/command/domain/service/FamilyCommandServiceImpl.java @@ -0,0 +1,25 @@ +package stanl_2.final_backend.domain.family.command.domain.service; + +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import stanl_2.final_backend.domain.family.command.application.service.FamilyCommandService; +import stanl_2.final_backend.domain.family.command.domain.repository.FamilyRepository; +import stanl_2.final_backend.global.utils.AESUtils; + +@Service("commandFamilyService") +public class FamilyCommandServiceImpl implements FamilyCommandService { + + private final FamilyRepository familyRepository; + private final ModelMapper modelMapper; + private final AESUtils aesUtils; + + @Autowired + public FamilyCommandServiceImpl(FamilyRepository familyRepository, + ModelMapper modelMapper, + AESUtils aesUtils) { + this.familyRepository = familyRepository; + this.modelMapper = modelMapper; + this.aesUtils = aesUtils; + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/family/common/exception/FamilyCommonException.java b/src/main/java/stanl_2/final_backend/domain/family/common/exception/FamilyCommonException.java new file mode 100644 index 00000000..7ab54387 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/family/common/exception/FamilyCommonException.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.family.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class FamilyCommonException extends RuntimeException { + private final FamilyErrorCode sampleErrorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.sampleErrorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/family/common/exception/FamilyErrorCode.java b/src/main/java/stanl_2/final_backend/domain/family/common/exception/FamilyErrorCode.java new file mode 100644 index 00000000..81d35651 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/family/common/exception/FamilyErrorCode.java @@ -0,0 +1,52 @@ +package stanl_2.final_backend.domain.family.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum FamilyErrorCode { + + /** + * 400(Bad Request) + * 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. + */ + + + + /** + * 401(Unauthorized) + * 비록 HTTP 표준에서는 "미승인(unauthorized)"를 명확히 하고 있지만, + * 의미상 이 응답은 "비인증(unauthenticated)"을 의미합니다. + * 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로를 인증해야 합니다. + */ + + + /** + * 403(Forbidden) + * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. + * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. + */ + + + + /** + * 404(Not Found) + * 서버는 요청받은 리소스를 찾을 수 없습니다. 브라우저에서는 알려지지 않은 URL을 의미합니다. + * 이것은 API에서 종점은 적절하지만 리소스 자체는 존재하지 않음을 의미할 수도 있습니다. + * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. + * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. + */ + SAMPLE_NOT_FOUND(404001, HttpStatus.NOT_FOUND, "sample 데이터를 찾지 못했습니다"), + CENTER_NOT_FOUND(404002, HttpStatus.NOT_FOUND, "center 데이터를 찾지 못했습니다."), + /** + * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. + */ + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); + + + private final Integer code; + private final HttpStatus httpStatus; + private final String msg; +} diff --git a/src/main/java/stanl_2/final_backend/domain/family/common/exception/FamilyExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/family/common/exception/FamilyExceptionResponse.java new file mode 100644 index 00000000..b7bf51b2 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/family/common/exception/FamilyExceptionResponse.java @@ -0,0 +1,22 @@ +package stanl_2.final_backend.domain.family.common.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class FamilyExceptionResponse { + private final Integer code; + private final String msg; + private final HttpStatus httpStatus; + + public FamilyExceptionResponse(FamilyErrorCode sampleErrorCode) { + this.code = sampleErrorCode.getCode(); + this.msg = sampleErrorCode.getMsg(); + this.httpStatus = sampleErrorCode.getHttpStatus(); + } + + public static FamilyExceptionResponse of(FamilyErrorCode sampleErrorCode) { + return new FamilyExceptionResponse(sampleErrorCode); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/family/common/response/FamilyResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/family/common/response/FamilyResponseMessage.java new file mode 100644 index 00000000..393076d2 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/family/common/response/FamilyResponseMessage.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.family.common.response; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +public class FamilyResponseMessage { + private int httpStatus; + private String msg; + private Object result; +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/family/query/config/MybatisConfiguration.java b/src/main/java/stanl_2/final_backend/domain/family/query/config/MybatisConfiguration.java new file mode 100644 index 00000000..238e8a1e --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/family/query/config/MybatisConfiguration.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.family.query.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Configuration; + +@Configuration("familyMybatisConfiguration") +@MapperScan(basePackages = "stanl_2.final_backend.domain.family.query.repository") +public class MybatisConfiguration { +} diff --git a/src/main/java/stanl_2/final_backend/domain/family/query/controller/FamilyController.java b/src/main/java/stanl_2/final_backend/domain/family/query/controller/FamilyController.java new file mode 100644 index 00000000..57da897e --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/family/query/controller/FamilyController.java @@ -0,0 +1,69 @@ +package stanl_2.final_backend.domain.family.query.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import stanl_2.final_backend.domain.family.common.response.FamilyResponseMessage; +import stanl_2.final_backend.domain.family.query.dto.FamilyDTO; +import stanl_2.final_backend.domain.family.query.service.FamilyQueryService; + +import java.security.Principal; +import java.util.List; + +@RestController(value = "queryFamilyController") +@RequestMapping("/api/v1/family") +public class FamilyController { + + private final FamilyQueryService familyQueryService; + + @Autowired + public FamilyController(FamilyQueryService familyQueryService) { + this.familyQueryService = familyQueryService; + } + + @Operation(summary = "가족 구성원 조회(with 사번)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = FamilyResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/other/{loginId}") + public ResponseEntity getEducation(@PathVariable String loginId){ + + List familyList = familyQueryService.selectFamilyList(loginId); + + return ResponseEntity.ok(FamilyResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(familyList) + .build()); + } + + @Operation(summary = "가족 구성원 조회(접속중인 사용자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = FamilyResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("") + public ResponseEntity getCertification(Principal principal){ + + List familyList = familyQueryService.selectFamilyList(principal.getName()); + + return ResponseEntity.ok(FamilyResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(familyList) + .build()); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/family/query/dto/FamilyDTO.java b/src/main/java/stanl_2/final_backend/domain/family/query/dto/FamilyDTO.java new file mode 100644 index 00000000..3f86794c --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/family/query/dto/FamilyDTO.java @@ -0,0 +1,19 @@ +package stanl_2.final_backend.domain.family.query.dto; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class FamilyDTO { + private String name; + private String relation; + private String birth; + private String identNo; + private String phone; + private String sex; + private Boolean disability; + private Boolean die; + private String note; +} diff --git a/src/main/java/stanl_2/final_backend/domain/family/query/repository/FamilyMapper.java b/src/main/java/stanl_2/final_backend/domain/family/query/repository/FamilyMapper.java new file mode 100644 index 00000000..0522e373 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/family/query/repository/FamilyMapper.java @@ -0,0 +1,12 @@ +package stanl_2.final_backend.domain.family.query.repository; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import stanl_2.final_backend.domain.family.query.dto.FamilyDTO; + +import java.util.List; + +@Mapper +public interface FamilyMapper { + List selectFamilyInfo(@Param("memberId") String memberId); +} diff --git a/src/main/java/stanl_2/final_backend/domain/family/query/service/FamilyQueryService.java b/src/main/java/stanl_2/final_backend/domain/family/query/service/FamilyQueryService.java new file mode 100644 index 00000000..9c6507e7 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/family/query/service/FamilyQueryService.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.family.query.service; + +import stanl_2.final_backend.domain.family.query.dto.FamilyDTO; + +import java.util.List; + +public interface FamilyQueryService { + List selectFamilyList(String loginId); +} diff --git a/src/main/java/stanl_2/final_backend/domain/family/query/service/FamilyQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/family/query/service/FamilyQueryServiceImpl.java new file mode 100644 index 00000000..9b9be9e9 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/family/query/service/FamilyQueryServiceImpl.java @@ -0,0 +1,39 @@ +package stanl_2.final_backend.domain.family.query.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.family.query.dto.FamilyDTO; +import stanl_2.final_backend.domain.family.query.repository.FamilyMapper; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.global.utils.AESUtils; + +import java.util.List; + +@Service("queryFamilyService") +public class FamilyQueryServiceImpl implements FamilyQueryService { + + private final FamilyMapper familyMapper; + private final AESUtils aesUtils; + private final AuthQueryService authQueryService; + + @Autowired + public FamilyQueryServiceImpl(FamilyMapper familyMapper, + AESUtils aesUtils, + AuthQueryService authQueryService) { + this.familyMapper = familyMapper; + this.aesUtils = aesUtils; + this.authQueryService = authQueryService; + } + + @Override + @Transactional(readOnly = true) + public List selectFamilyList(String loginId) { + + String memberId = authQueryService.selectMemberIdByLoginId(loginId); + + List familyList = familyMapper.selectFamilyInfo(memberId); + + return familyList; + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/log/command/aggregate/Log.java b/src/main/java/stanl_2/final_backend/domain/log/command/aggregate/Log.java new file mode 100644 index 00000000..170f8717 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/log/command/aggregate/Log.java @@ -0,0 +1,82 @@ +package stanl_2.final_backend.domain.log.command.aggregate; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "tb_log") +public class Log { + @Id + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name = "prefix", value = "LOG") + ) + @Column(name = "LOG_ID", nullable = false) + private String logId; + + @Column(name = "LOGIN_ID") + private String loginId; + + @Column(name = "SESSION_ID", nullable = false) + private String sessionId; + + @Column(name = "USER_AGENT", length = 500, nullable = false) + private String userAgent; + + @Column(name = "IP_ADDRESS", nullable = false) + private String ipAddress; + + @Column(name = "HOST_NAME", nullable = false) + private String hostName; + + @Column(name = "REMOTE_PORT", nullable = false) + private Integer remotePort; + + @Column(name = "URI", length = 2048, nullable = false) + private String uri; + + @Column(name = "METHOD", nullable = false) + private String method; + + @Column(name = "QUERY_STRING", length = 2048, nullable = false) + private String queryString; + + @Column(name = "REQUEST_TIME", nullable = false, updatable = false) + private String requestTime; + + @Column(name = "TRANSACTION_ID", nullable = false) + private String transactionId; + + @Column(name = "STATUS", nullable = false) + private String status; + + @Column(name = "ERROR_MESSAGE", columnDefinition = "TEXT") + private String errorMessage; + + + @PrePersist + private void prePersist() { + this.requestTime = getCurrentTime(); + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } +} + + diff --git a/src/main/java/stanl_2/final_backend/domain/log/command/aop/LoggingAspect.java b/src/main/java/stanl_2/final_backend/domain/log/command/aop/LoggingAspect.java new file mode 100644 index 00000000..c7a7c738 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/log/command/aop/LoggingAspect.java @@ -0,0 +1,133 @@ +package stanl_2.final_backend.domain.log.command.aop; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import stanl_2.final_backend.domain.log.command.aggregate.Log; +import stanl_2.final_backend.domain.log.command.repository.LogRepository; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +@Slf4j +@Aspect +@Component +public class LoggingAspect { + + private final LogRepository logRepository; + private static final ThreadLocal currentTransactionId = new ThreadLocal<>(); + + @Autowired + public LoggingAspect(LogRepository logRepository) { + this.logRepository = logRepository; + } + + @AfterReturning("within(@org.springframework.web.bind.annotation.RestController *)") + public void logRequestSuccess(JoinPoint joinPoint) { + + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes == null) return; + + HttpServletRequest request = attributes.getRequest(); + + Map logData = new HashMap<>(); + + // 사용자 정보 + Map userData = new HashMap<>(); + userData.put("session_id", safeValue(request.getRequestedSessionId())); + userData.put("user_agent", safeValue(request.getHeader("User-Agent"))); + logData.put("user", userData); + + // 네트워크 정보 + Map networkData = new HashMap<>(); + networkData.put("ip_address", safeValue(getClientIp(request))); + networkData.put("host_name", safeValue(request.getRemoteHost())); + networkData.put("remote_port", request.getRemotePort()); + logData.put("network", networkData); + + // 요청 정보 + Map requestData = new HashMap<>(); + requestData.put("uri", safeValue(request.getRequestURI())); + requestData.put("method", safeValue(request.getMethod())); + requestData.put("query_string", safeValue(request.getQueryString())); + logData.put("request", requestData); + + + // 추가 정보 + String transactionId = UUID.randomUUID().toString(); + currentTransactionId.set(transactionId); + logData.put("transaction_id", transactionId); + + try { + // 로그 엔티티 저장 + Log logEntry = new Log(); + + // 유저 정보 + logEntry.setSessionId(safeValue(request.getRequestedSessionId())); + logEntry.setUserAgent(safeValue(request.getHeader("User-Agent"))); + + String loginId = "anonymousUser"; + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication != null && authentication.isAuthenticated() && + !"anonymousUser".equals(authentication.getPrincipal()) && + !authentication.getPrincipal().toString().startsWith("stanl_2") + ) { + loginId = authentication.getPrincipal().toString(); + } + + logEntry.setLoginId(loginId); + + // 네트워크 정보 + logEntry.setIpAddress(safeValue(getClientIp(request))); + logEntry.setHostName(safeValue(request.getRemoteHost())); + logEntry.setRemotePort(request.getRemotePort()); + + // 요청 정보 + logEntry.setUri(safeValue(request.getRequestURI())); + logEntry.setMethod(safeValue(request.getMethod())); + logEntry.setQueryString(safeValue(request.getQueryString())); + + // 추가적인 정보 + logEntry.setTransactionId(transactionId); + + logEntry.setStatus("SUCCESS"); + logRepository.save(logEntry); + + } catch (Exception e) { + log.error("Failed to log request info", e); + } + } + + private String getClientIp(HttpServletRequest request) { + String[] headers = { + "X-Forwarded-For", + "Proxy-Client-IP", + "WL-Proxy-Client-IP", + "HTTP_CLIENT_IP", + "HTTP_X_FORWARDED_FOR" + }; + + for (String header : headers) { + String ip = request.getHeader(header); + if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) { + return ip.split(",")[0]; + } + } + return request.getRemoteAddr(); + } + + private String safeValue(String value) { + return value != null ? value : "N/A"; + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/log/command/repository/LogRepository.java b/src/main/java/stanl_2/final_backend/domain/log/command/repository/LogRepository.java new file mode 100644 index 00000000..00a2b826 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/log/command/repository/LogRepository.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.log.command.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import stanl_2.final_backend.domain.log.command.aggregate.Log; + +@Repository +public interface LogRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/log/common/exception/LogCommonException.java b/src/main/java/stanl_2/final_backend/domain/log/common/exception/LogCommonException.java new file mode 100644 index 00000000..aa672853 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/log/common/exception/LogCommonException.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.log.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class LogCommonException extends RuntimeException { + private final LogErrorCode errorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.errorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/log/common/exception/LogErrorCode.java b/src/main/java/stanl_2/final_backend/domain/log/common/exception/LogErrorCode.java new file mode 100644 index 00000000..09ba1a58 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/log/common/exception/LogErrorCode.java @@ -0,0 +1,53 @@ +package stanl_2.final_backend.domain.log.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum LogErrorCode { + + /** + * 400(Bad Request) + * 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. + */ + + + + /** + * 401(Unauthorized) + * 비록 HTTP 표준에서는 "미승인(unauthorized)"를 명확히 하고 있지만, + * 의미상 이 응답은 "비인증(unauthenticated)"을 의미합니다. + * 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로를 인증해야 합니다. + */ + + + /** + * 403(Forbidden) + * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. + * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. + */ + + + + /** + * 404(Not Found) + * 서버는 요청받은 리소스를 찾을 수 없습니다. 브라우저에서는 알려지지 않은 URL을 의미합니다. + * 이것은 API에서 종점은 적절하지만 리소스 자체는 존재하지 않음을 의미할 수도 있습니다. + * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. + * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. + */ + LOG_NOT_FOUND(40401, HttpStatus.NOT_FOUND, "해당 로그를 찾을 수 없습니다."), + + /** + * 500(Internal Server Error) + * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. + */ + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); + + + private final Integer code; + private final HttpStatus httpStatus; + private final String msg; +} diff --git a/src/main/java/stanl_2/final_backend/domain/A_sample/common/exception/ExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/log/common/exception/LogExceptionResponse.java similarity index 54% rename from src/main/java/stanl_2/final_backend/domain/A_sample/common/exception/ExceptionResponse.java rename to src/main/java/stanl_2/final_backend/domain/log/common/exception/LogExceptionResponse.java index bd9c67ad..d3714677 100644 --- a/src/main/java/stanl_2/final_backend/domain/A_sample/common/exception/ExceptionResponse.java +++ b/src/main/java/stanl_2/final_backend/domain/log/common/exception/LogExceptionResponse.java @@ -1,22 +1,22 @@ -package stanl_2.final_backend.domain.A_sample.common.exception; +package stanl_2.final_backend.domain.log.common.exception; import lombok.Getter; import org.springframework.http.HttpStatus; @Getter -public class ExceptionResponse { +public class LogExceptionResponse { private final Integer code; private final String msg; private final HttpStatus httpStatus; - public ExceptionResponse(ErrorCode errorCode) { + public LogExceptionResponse(LogErrorCode errorCode) { this.code = errorCode.getCode(); this.msg = errorCode.getMsg(); this.httpStatus = errorCode.getHttpStatus(); } - public static ExceptionResponse of(ErrorCode errorCode) { - return new ExceptionResponse(errorCode); + public static LogExceptionResponse of(LogErrorCode errorCode) { + return new LogExceptionResponse(errorCode); } } diff --git a/src/main/java/stanl_2/final_backend/domain/log/common/response/LogResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/log/common/response/LogResponseMessage.java new file mode 100644 index 00000000..b1533339 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/log/common/response/LogResponseMessage.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.log.common.response; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +public class LogResponseMessage { + private int httpStatus; + private String msg; + private Object result; +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/log/query/config/MybatisConfiguration.java b/src/main/java/stanl_2/final_backend/domain/log/query/config/MybatisConfiguration.java new file mode 100644 index 00000000..6358f4bc --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/log/query/config/MybatisConfiguration.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.log.query.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Configuration; + +@Configuration("logMybatisConfiguration") +@MapperScan(basePackages = "stanl_2.final_backend.domain.log.query.repository") +public class MybatisConfiguration { +} diff --git a/src/main/java/stanl_2/final_backend/domain/log/query/controller/LogController.java b/src/main/java/stanl_2/final_backend/domain/log/query/controller/LogController.java new file mode 100644 index 00000000..c749ee8f --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/log/query/controller/LogController.java @@ -0,0 +1,89 @@ +package stanl_2.final_backend.domain.log.query.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import stanl_2.final_backend.domain.customer.common.response.CustomerResponseMessage; +import stanl_2.final_backend.domain.log.common.response.LogResponseMessage; +import stanl_2.final_backend.domain.log.query.dto.LogDTO; +import stanl_2.final_backend.domain.log.query.dto.LogSearchDTO; +import stanl_2.final_backend.domain.log.query.service.LogQueryService; + +@Slf4j +@RestController(value = "queryLogController") +@RequestMapping("/api/v1/log") +public class LogController { + + private final LogQueryService logQueryService; + + @Autowired + public LogController(LogQueryService logQueryService) { + this.logQueryService = logQueryService; + } + + @Operation(summary = "로그 검색 조회(시스템 관리자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = LogResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("") + public ResponseEntity getLogs( + @RequestParam(required = false) String logId, + @RequestParam(required = false) String loginId, + @RequestParam(required = false) String ipAddress, + @RequestParam(required = false) String requestTime_start, + @RequestParam(required = false) String requestTime_end, + @RequestParam(required = false) String status, + @RequestParam(required = false) String method, + @RequestParam(required = false) String uri, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder, + @PageableDefault(size = 10) Pageable pageable + ){ + + // 정렬 추가 + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + + LogSearchDTO searchLogDTO = new LogSearchDTO(logId, loginId, ipAddress, requestTime_start, requestTime_end, status, method, uri); + Page logDTOPage = logQueryService.selectLogs(pageable, searchLogDTO); + + return ResponseEntity.ok(LogResponseMessage.builder() + .httpStatus(200) + .result(logDTOPage) + .build()); + } + + @Operation(summary = "엑셀 다운로드") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CustomerResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("excel") + public void exportCustomer(HttpServletResponse response){ + + logQueryService.exportLogToExcel(response); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/log/query/dto/LogDTO.java b/src/main/java/stanl_2/final_backend/domain/log/query/dto/LogDTO.java new file mode 100644 index 00000000..8ae932bd --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/log/query/dto/LogDTO.java @@ -0,0 +1,27 @@ +package stanl_2.final_backend.domain.log.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class LogDTO { + private String logId; + private String loginId; + private String sessionId; + private String userAgent; + private String ipAddress; + private String hostName; + private Integer remotePort; + private String uri; + private String method; + private String queryString; + private String requestTime; + private String transactionId; + private String status; + private String errorMessage; +} diff --git a/src/main/java/stanl_2/final_backend/domain/log/query/dto/LogExcelDTO.java b/src/main/java/stanl_2/final_backend/domain/log/query/dto/LogExcelDTO.java new file mode 100644 index 00000000..75ccc114 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/log/query/dto/LogExcelDTO.java @@ -0,0 +1,48 @@ +package stanl_2.final_backend.domain.log.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import stanl_2.final_backend.global.excel.ExcelColumnName; + +@Getter +@AllArgsConstructor +public class LogExcelDTO { + @ExcelColumnName(name = "로그 번호") + private String logId; + + @ExcelColumnName(name = "접근한 유저") + private String loginId; + + @ExcelColumnName(name = "트랜잭션 번호") + private String transactionId; + + @ExcelColumnName(name = "요청 시간") + private String requestTime; + + @ExcelColumnName(name = "요청 메소드") + private String method; + + @ExcelColumnName(name = "URI") + private String uri; + + @ExcelColumnName(name = "쿼리 스트링") + private String queryString; + + @ExcelColumnName(name = "유저 소프트웨어") + private String userAgent; + + @ExcelColumnName(name = "IP 주소") + private String ipAddress; + + @ExcelColumnName(name = "호스트명") + private String hostName; + + @ExcelColumnName(name = "원격포트") + private Integer remotePort; + + @ExcelColumnName(name = "상태") + private String status; + + @ExcelColumnName(name = "에러 메시지") + private String errorMessage; +} diff --git a/src/main/java/stanl_2/final_backend/domain/log/query/dto/LogSearchDTO.java b/src/main/java/stanl_2/final_backend/domain/log/query/dto/LogSearchDTO.java new file mode 100644 index 00000000..7f2c67ad --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/log/query/dto/LogSearchDTO.java @@ -0,0 +1,46 @@ +package stanl_2.final_backend.domain.log.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class LogSearchDTO { + private String logId; + private String loginId; + private String sessionId; + private String userAgent; + private String ipAddress; + private String hostName; + private Integer remotePort; + private String uri; + private String method; + private String queryString; + private String requestTime_start; + private String requestTime_end; + private String transactionId; + private String status; + private String errorMessage; + + public LogSearchDTO(String logId, + String loginId, + String ipAddress, + String requestTime_start, + String requestTime_end, + String status, + String method, + String uri){ + this.logId = logId; + this.loginId = loginId; + this.ipAddress = ipAddress; + this.requestTime_start = requestTime_start; + this.requestTime_end = requestTime_end; + this.status = status; + this.method = method; + this.uri = uri; + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/log/query/repository/LogMapper.java b/src/main/java/stanl_2/final_backend/domain/log/query/repository/LogMapper.java new file mode 100644 index 00000000..a4d396aa --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/log/query/repository/LogMapper.java @@ -0,0 +1,24 @@ +package stanl_2.final_backend.domain.log.query.repository; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import stanl_2.final_backend.domain.log.query.dto.LogDTO; +import stanl_2.final_backend.domain.log.query.dto.LogExcelDTO; +import stanl_2.final_backend.domain.log.query.dto.LogSearchDTO; + +import java.util.List; + +@Mapper +public interface LogMapper { + List findLogs( + @Param("offset") int offset, + @Param("size") int size, + @Param("searchLogDTO") LogSearchDTO searchLogDTO, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder + ); + + int findLogsCnt(@Param("searchLogDTO") LogSearchDTO searchLogDTO); + + List findLogForExcel(); +} diff --git a/src/main/java/stanl_2/final_backend/domain/log/query/service/LogQueryService.java b/src/main/java/stanl_2/final_backend/domain/log/query/service/LogQueryService.java new file mode 100644 index 00000000..99996d24 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/log/query/service/LogQueryService.java @@ -0,0 +1,15 @@ +package stanl_2.final_backend.domain.log.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import stanl_2.final_backend.domain.log.query.dto.LogDTO; +import stanl_2.final_backend.domain.log.query.dto.LogSearchDTO; + +import java.util.List; + +public interface LogQueryService { + Page selectLogs(Pageable pageable, LogSearchDTO searchLogDTO); + + void exportLogToExcel(HttpServletResponse response); +} diff --git a/src/main/java/stanl_2/final_backend/domain/log/query/service/LogQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/log/query/service/LogQueryServiceImpl.java new file mode 100644 index 00000000..6785be94 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/log/query/service/LogQueryServiceImpl.java @@ -0,0 +1,69 @@ +package stanl_2.final_backend.domain.log.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.log.common.exception.LogCommonException; +import stanl_2.final_backend.domain.log.common.exception.LogErrorCode; +import stanl_2.final_backend.domain.log.query.dto.LogDTO; +import stanl_2.final_backend.domain.log.query.dto.LogExcelDTO; +import stanl_2.final_backend.domain.log.query.dto.LogSearchDTO; +import stanl_2.final_backend.domain.log.query.repository.LogMapper; +import stanl_2.final_backend.global.excel.ExcelUtilsV1; + +import java.util.List; + +@Slf4j +@Service(value = "queryLogService") +public class LogQueryServiceImpl implements LogQueryService { + + private final LogMapper logMapper; + private final ExcelUtilsV1 excelUtilsV1; + + @Autowired + public LogQueryServiceImpl(LogMapper logMapper, + ExcelUtilsV1 excelUtilsV1) { + this.logMapper = logMapper; + this.excelUtilsV1 = excelUtilsV1; + } + + @Override + @Transactional(readOnly = true) + public Page selectLogs(Pageable pageable, LogSearchDTO searchLogDTO) { + + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + + // 정렬 정보 가져오기 + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + List logs = logMapper.findLogs(offset, size, searchLogDTO, sortField, sortOrder); + + int totalElements = logMapper.findLogsCnt(searchLogDTO); + return new PageImpl<>(logs, pageable, totalElements); + } + + @Override + public void exportLogToExcel(HttpServletResponse response) { + + List logExcels = logMapper.findLogForExcel(); + + if(logExcels == null) { + throw new LogCommonException(LogErrorCode.LOG_NOT_FOUND); + } + + excelUtilsV1.download(LogExcelDTO.class, logExcels, "logExcel", response); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/aggregate/Role.java b/src/main/java/stanl_2/final_backend/domain/member/aggregate/Role.java deleted file mode 100644 index 9aaa5511..00000000 --- a/src/main/java/stanl_2/final_backend/domain/member/aggregate/Role.java +++ /dev/null @@ -1,11 +0,0 @@ -package stanl_2.final_backend.domain.member.aggregate; - -public enum Role { - ROLE_CUSTOMER, - ROLE_SALES_PERSON, - ROLE_SALES_MANAGER, - ROLE_INVENTORY_MANAGER, - ROLE_PRODUCT_MANAGER, - ROLE_SALES_ADMIN, - ROLE_SYSTEM_ADMIN; -} diff --git a/src/main/java/stanl_2/final_backend/domain/member/aggregate/dto/SignUpMemberDTO.java b/src/main/java/stanl_2/final_backend/domain/member/aggregate/dto/SignUpMemberDTO.java deleted file mode 100644 index e9ef48ef..00000000 --- a/src/main/java/stanl_2/final_backend/domain/member/aggregate/dto/SignUpMemberDTO.java +++ /dev/null @@ -1,22 +0,0 @@ -package stanl_2.final_backend.domain.member.aggregate.dto; - -import lombok.*; -import stanl_2.final_backend.domain.member.aggregate.Role; - -import java.sql.Timestamp; - -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor -@ToString -public class SignUpMemberDTO { - private String loginId; - private String password; - private String email; - private String name; - private String phone; - private Role role; - private Timestamp createdAt; - private Timestamp updatedAt; -} diff --git a/src/main/java/stanl_2/final_backend/domain/member/aggregate/dto/jwtDTO.java b/src/main/java/stanl_2/final_backend/domain/member/aggregate/dto/jwtDTO.java deleted file mode 100644 index 0fcf6ffe..00000000 --- a/src/main/java/stanl_2/final_backend/domain/member/aggregate/dto/jwtDTO.java +++ /dev/null @@ -1,12 +0,0 @@ -package stanl_2.final_backend.domain.member.aggregate.dto; - -import lombok.*; - -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor -@ToString -public class jwtDTO { - private String jwt; -} diff --git a/src/main/java/stanl_2/final_backend/domain/member/aggregate/entity/Member.java b/src/main/java/stanl_2/final_backend/domain/member/aggregate/entity/Member.java deleted file mode 100644 index 80cad899..00000000 --- a/src/main/java/stanl_2/final_backend/domain/member/aggregate/entity/Member.java +++ /dev/null @@ -1,60 +0,0 @@ -package stanl_2.final_backend.domain.member.aggregate.entity; - -import jakarta.persistence.*; -import jakarta.validation.constraints.NotNull; -import lombok.*; -import stanl_2.final_backend.domain.member.aggregate.Role; - -import java.sql.Timestamp; - -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor -@ToString -@Entity -@Table(name = "MEMBER") -public class Member { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "MEMBER_ID") - private Long id; - - @Column(name = "MEMBER_LOGIN_ID") - @NotNull - private String loginId; - - @Column(name = "MEMBER_PASSWORD") - @NotNull - private String password; - - @Column(name = "MEMBER_EMAIL") - @NotNull - private String email; - - @Column(name = "MEMBER_NAME") - @NotNull - private String name; - - @Column(name = "MEMBER_PHONE") - @NotNull - private String phone; - - @Column(name = "MEMBER_ROLE") - @Enumerated(EnumType.STRING) - @NotNull - private Role role; - - @Column(name = "MEMBER_CREATED_AT") - @NotNull - private Timestamp createdAt; - - @Column(name = "MEMBER_UPDATED_AT") - @NotNull - private Timestamp updatedAt; - - @Column(name = "MEMBER_ACTIVE") - @NotNull - private Boolean active = true; -} diff --git a/src/main/java/stanl_2/final_backend/domain/member/aggregate/vo/request/LoginRequestVO.java b/src/main/java/stanl_2/final_backend/domain/member/aggregate/vo/request/LoginRequestVO.java deleted file mode 100644 index a48fd34f..00000000 --- a/src/main/java/stanl_2/final_backend/domain/member/aggregate/vo/request/LoginRequestVO.java +++ /dev/null @@ -1,18 +0,0 @@ -package stanl_2.final_backend.domain.member.aggregate.vo.request; - -import jakarta.validation.constraints.NotNull; -import lombok.*; - -@Getter -@Setter -@AllArgsConstructor -@NoArgsConstructor -@EqualsAndHashCode -public class LoginRequestVO { - - @NotNull(message = "아이디를 입력해주세요.") - private String loginId; - - @NotNull(message = "비밀번호를 입력해주세요.") - private String password; -} diff --git a/src/main/java/stanl_2/final_backend/domain/member/aggregate/vo/request/SignUpRequestVO.java b/src/main/java/stanl_2/final_backend/domain/member/aggregate/vo/request/SignUpRequestVO.java deleted file mode 100644 index 144ca9c5..00000000 --- a/src/main/java/stanl_2/final_backend/domain/member/aggregate/vo/request/SignUpRequestVO.java +++ /dev/null @@ -1,33 +0,0 @@ -package stanl_2.final_backend.domain.member.aggregate.vo.request; - -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotNull; -import lombok.*; -import stanl_2.final_backend.domain.member.aggregate.Role; - -@Getter -@Setter -@AllArgsConstructor -@NoArgsConstructor -@EqualsAndHashCode -public class SignUpRequestVO { - - @NotNull(message = "아이디를 입력해주세요.") - private String loginId; - - @NotNull(message = "비밀번호를 입력해주세요.") - private String password; - - @NotNull(message = "이메일을 입력해 주세요.") - @Email - private String email; - - @NotNull(message = "성함을 입력해주세요.") - private String name; - - @NotNull(message = "연락처를 입력해주세요.") - private String phone; - - @NotNull(message = "역할을 입력해주세요.") - private Role role; -} diff --git a/src/main/java/stanl_2/final_backend/domain/member/command/application/controller/AuthController.java b/src/main/java/stanl_2/final_backend/domain/member/command/application/controller/AuthController.java new file mode 100644 index 00000000..09b2e40a --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/command/application/controller/AuthController.java @@ -0,0 +1,184 @@ +package stanl_2.final_backend.domain.member.command.application.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.mail.MessagingException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import stanl_2.final_backend.domain.member.command.application.dto.*; +import stanl_2.final_backend.domain.member.command.application.service.AuthCommandService; +import stanl_2.final_backend.domain.member.common.exception.MemberCommonException; +import stanl_2.final_backend.domain.member.common.exception.MemberErrorCode; +import stanl_2.final_backend.domain.member.common.response.MemberResponseMessage; + +import java.security.GeneralSecurityException; + +@Slf4j +@RestController("commandAuthController") +@RequestMapping("/api/v1/auth") +public class AuthController { + + private final AuthCommandService authCommandService; + + @Autowired + public AuthController(AuthCommandService authCommandService) { + this.authCommandService = authCommandService; + } + + /** + * 회원가입 @ResponseBody + * { + * "loginId": "test", + * "password": "test", + * "name": "이름1", + * "email": "test@test.com", + * "age": 30, + * "sex": "MALE", + * "identNo": "12123", + * "phone": "01012345678", + * "emergePhone": "01088888888", + * "address": "서울", + * "note": "비고1", + * "position": "인턴", + * "grade": "고졸", + * "jobType": "영업", + * "military": "미필", + * "bankName": "국민은행", + * "account": "110-2324-131313-12232", + * "centerId": "CEN_000000001", + * "organizationId": "ORG_000000001" + * } + */ + @Operation(summary = "회원가입") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = MemberResponseMessage.class))}) + }) + @PostMapping("signup") + public ResponseEntity signup(@RequestPart("dto") SignupDTO signupDTO, + @RequestPart("file") MultipartFile imageUrl) + throws GeneralSecurityException { + + authCommandService.signup(signupDTO, imageUrl); + + return ResponseEntity.ok(MemberResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(null) + .build()); + } + + /** + * 권한 부여 @ResponseBody + * { + * "loginId": "test", + * "role": "ADMIN" + * } + */ + + @Operation(summary = "권한 부여") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = MemberResponseMessage.class))}) + }) + @PostMapping("") + public ResponseEntity grantAuthority(@RequestBody GrantDTO grantDTO){ + + authCommandService.grantAuthority(grantDTO); + + return ResponseEntity.ok(MemberResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(null) + .build()); + } + + + /** + * 로그인 @ResponseBody + * { + * "loginId": "test", + * "password": "test" + * } + */ + @Operation(summary = "로그인") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = MemberResponseMessage.class))}) + }) + @PostMapping("signin") + public ResponseEntity signin(@RequestBody SigninRequestDTO signinRequestDTO) throws GeneralSecurityException { + + SigninResponseDTO responseDTO = authCommandService.signin(signinRequestDTO); + + return ResponseEntity.ok( + MemberResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(responseDTO) + .build() + ); + } + + @Operation(summary = "토큰 갱신") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = MemberResponseMessage.class))}) + }) + @PostMapping("refresh") + public ResponseEntity refresh(@RequestBody RefreshDTO refreshDTO) { + + RefreshDTO newAccessToken = authCommandService.refreshAccessToken(refreshDTO.getRefreshToken()); + + return ResponseEntity.ok(MemberResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(newAccessToken) + .build()); + } + + + @Operation(summary = "임시 비밀번호 재발급") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = MemberResponseMessage.class))}) + }) + @PostMapping("checkmail") + public ResponseEntity checkMail(@RequestBody CheckMailDTO checkMailDTO) throws GeneralSecurityException, MessagingException { + + authCommandService.sendEmail(checkMailDTO); + + return ResponseEntity.ok(MemberResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(null) + .build()); + } + + + @Operation(summary = "임시 비밀번호 재발급") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = MemberResponseMessage.class))}) + }) + @PostMapping("checknum") + public ResponseEntity checkMail(@RequestBody CheckNumDTO checkNumDTO) throws GeneralSecurityException, MessagingException { + + authCommandService.checkNum(checkNumDTO); + + authCommandService.sendNewPwd(checkNumDTO.getLoginId()); + + return ResponseEntity.ok(MemberResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(null) + .build()); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/command/application/controller/MemberController.java b/src/main/java/stanl_2/final_backend/domain/member/command/application/controller/MemberController.java new file mode 100644 index 00000000..b3867f0b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/command/application/controller/MemberController.java @@ -0,0 +1,51 @@ +package stanl_2.final_backend.domain.member.command.application.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stanl_2.final_backend.domain.member.command.application.service.MemberCommandService; +import stanl_2.final_backend.domain.member.common.response.MemberResponseMessage; + +import java.security.Principal; + +@Slf4j +@RestController("commandMemberController") +@RequestMapping("/api/v1/member") +public class MemberController { + + private final MemberCommandService memberCommandService; + + @Autowired + public MemberController(MemberCommandService memberCommandService) { + this.memberCommandService = memberCommandService; + } + + + @Operation(summary = "본인정보(Principal) 출력 테스트") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = MemberResponseMessage.class))}) + }) + @GetMapping("/authorities") + public ResponseEntity check(Principal principal) { + + // 인증된 사용자 정보 출력 + log.info("인증된 사용자: {}", principal.getName()); + + return ResponseEntity.ok(MemberResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result("인증된 사용자: " + principal.getName()) + .build()); + } + + + + +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/command/application/dto/CheckMailDTO.java b/src/main/java/stanl_2/final_backend/domain/member/command/application/dto/CheckMailDTO.java new file mode 100644 index 00000000..2e47357c --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/command/application/dto/CheckMailDTO.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.member.command.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class CheckMailDTO { + private String loginId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/command/application/dto/CheckNumDTO.java b/src/main/java/stanl_2/final_backend/domain/member/command/application/dto/CheckNumDTO.java new file mode 100644 index 00000000..369caf0f --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/command/application/dto/CheckNumDTO.java @@ -0,0 +1,15 @@ +package stanl_2.final_backend.domain.member.command.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class CheckNumDTO { + private String loginId; + private String number; +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/command/application/dto/GrantDTO.java b/src/main/java/stanl_2/final_backend/domain/member/command/application/dto/GrantDTO.java new file mode 100644 index 00000000..80f9767d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/command/application/dto/GrantDTO.java @@ -0,0 +1,13 @@ +package stanl_2.final_backend.domain.member.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class GrantDTO { + private String memberId; + private String loginId; + private String role; +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/command/application/dto/RefreshDTO.java b/src/main/java/stanl_2/final_backend/domain/member/command/application/dto/RefreshDTO.java new file mode 100644 index 00000000..b3a55f69 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/command/application/dto/RefreshDTO.java @@ -0,0 +1,13 @@ +package stanl_2.final_backend.domain.member.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +@Builder +public class RefreshDTO { + private String refreshToken; + private String newAccessToken; +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/aggregate/dto/LoginMemberDTO.java b/src/main/java/stanl_2/final_backend/domain/member/command/application/dto/SigninRequestDTO.java similarity index 56% rename from src/main/java/stanl_2/final_backend/domain/member/aggregate/dto/LoginMemberDTO.java rename to src/main/java/stanl_2/final_backend/domain/member/command/application/dto/SigninRequestDTO.java index 41634840..f83b52f7 100644 --- a/src/main/java/stanl_2/final_backend/domain/member/aggregate/dto/LoginMemberDTO.java +++ b/src/main/java/stanl_2/final_backend/domain/member/command/application/dto/SigninRequestDTO.java @@ -1,13 +1,12 @@ -package stanl_2.final_backend.domain.member.aggregate.dto; +package stanl_2.final_backend.domain.member.command.application.dto; import lombok.*; -@Getter -@Setter -@NoArgsConstructor @AllArgsConstructor -@ToString -public class LoginMemberDTO { +@NoArgsConstructor +@Setter +@Getter +public class SigninRequestDTO { private String loginId; private String password; } diff --git a/src/main/java/stanl_2/final_backend/domain/member/command/application/dto/SigninResponseDTO.java b/src/main/java/stanl_2/final_backend/domain/member/command/application/dto/SigninResponseDTO.java new file mode 100644 index 00000000..032e1178 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/command/application/dto/SigninResponseDTO.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.member.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class SigninResponseDTO { + private String accessToken; + private String refreshToken; + private String name; + private String role; + private String auth; + private String imageUrl; +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/command/application/dto/SignupDTO.java b/src/main/java/stanl_2/final_backend/domain/member/command/application/dto/SignupDTO.java new file mode 100644 index 00000000..3ce0ac21 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/command/application/dto/SignupDTO.java @@ -0,0 +1,30 @@ +package stanl_2.final_backend.domain.member.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class SignupDTO { + private String loginId; + private String password; + private String name; + private String imageUrl; + private String email; + private Integer age; + private String sex; + private String identNo; + private String phone; + private String emergePhone; + private String address; + private String note; + private String position; + private String grade; + private String jobType; + private String military; + private String bankName; + private String account; + private String centerId; + private String organizationId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/command/application/service/AuthCommandService.java b/src/main/java/stanl_2/final_backend/domain/member/command/application/service/AuthCommandService.java new file mode 100644 index 00000000..74558de9 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/command/application/service/AuthCommandService.java @@ -0,0 +1,23 @@ +package stanl_2.final_backend.domain.member.command.application.service; + +import jakarta.mail.MessagingException; +import org.springframework.web.multipart.MultipartFile; +import stanl_2.final_backend.domain.member.command.application.dto.*; + +import java.security.GeneralSecurityException; + +public interface AuthCommandService { + void signup(SignupDTO signupDTO, MultipartFile imageUrl) throws GeneralSecurityException; + + RefreshDTO refreshAccessToken(String refreshToken); + + void grantAuthority(GrantDTO grantDTO); + + SigninResponseDTO signin(SigninRequestDTO signinRequestDTO) throws GeneralSecurityException; + + void sendEmail(CheckMailDTO checkMailDTO) throws GeneralSecurityException, MessagingException; + + void checkNum(CheckNumDTO checkNumDTO) throws GeneralSecurityException; + + void sendNewPwd(String loginId) throws MessagingException, GeneralSecurityException; +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/command/application/service/MemberCommandService.java b/src/main/java/stanl_2/final_backend/domain/member/command/application/service/MemberCommandService.java new file mode 100644 index 00000000..e1e93f57 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/command/application/service/MemberCommandService.java @@ -0,0 +1,4 @@ +package stanl_2.final_backend.domain.member.command.application.service; + +public interface MemberCommandService { +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/command/domain/aggregate/entity/Member.java b/src/main/java/stanl_2/final_backend/domain/member/command/domain/aggregate/entity/Member.java new file mode 100644 index 00000000..b61508f6 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/command/domain/aggregate/entity/Member.java @@ -0,0 +1,126 @@ +package stanl_2.final_backend.domain.member.command.domain.aggregate.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "TB_MEMBER") +public class Member { + + @Id + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name = "prefix", value = "MEM") + ) + @Column(name = "MEM_ID", nullable = false) + private String memberId; + + @Column(name = "MEM_LOGIN_ID", nullable = false, unique = true) + private String loginId; + + @Column(name = "MEM_PWD", nullable = false) + private String password; + + @Column(name = "MEM_NAME", nullable = false) + private String name; + + @Column(name = "MEM_IMAGEURL", nullable = false) + private String imageUrl; + + @Column(name = "MEM_EMA", nullable = false) + private String email; + + @Column(name = "MEM_AGE", nullable = false) + private Integer age; + + @Column(name = "MEM_SEX", nullable = false) + private String sex; + + @Column(name = "MEM_IDEN_NO", nullable = false) + private String identNo; + + @Column(name = "MEM_PHO", nullable = false) + private String phone; + + @Column(name = "MEM_EMER_PHO") + private String emergePhone; + + @Column(name = "MEM_ADR", nullable = false) + private String address; + + @Column(name = "MEM_NOTE") + private String note; + + @Column(name = "MEM_POS", nullable = false) + private String position; + + @Column(name = "MEM_GRD", nullable = false) + private String grade; + + @Column(name = "MEM_JOB_TYPE", nullable = false) + private String jobType; + + @Column(name = "MEM_MIL", nullable = false) + private String military; + + @Column(name = "MEM_BANK_NAME") + private String bankName; + + @Column(name = "MEM_ACC") + private String account; + + @Column(name = "CREATED_AT", nullable = false, updatable = false) + private String createdAt; + + @Column(name = "UPDATED_AT", nullable = false) + private String updatedAt; + + @Column(name = "DELETED_AT") + private String deletedAt; + + @Column(name = "ACTIVE", nullable = false) + private Boolean active = true; + + @Column(name = "CENTER_ID", nullable = false) + private String centerId; + + @Column(name = "ORG_CHA_ID", nullable = false) + private String organizationId; + + @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) + @JoinColumn(name = "MEM_ID") + private List roles = new ArrayList<>(); + + + @PrePersist + private void prePersist() { + this.createdAt = getCurrentTime(); + this.updatedAt = this.createdAt; + } + + @PreUpdate + private void preUpdate() { + this.updatedAt = getCurrentTime(); + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/command/domain/aggregate/entity/MemberRole.java b/src/main/java/stanl_2/final_backend/domain/member/command/domain/aggregate/entity/MemberRole.java new file mode 100644 index 00000000..2a758910 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/command/domain/aggregate/entity/MemberRole.java @@ -0,0 +1,35 @@ +package stanl_2.final_backend.domain.member.command.domain.aggregate.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "TB_MEMBER_ROLE") +public class MemberRole { + + @Id + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name = "prefix", value = "MEM_ROL") + ) + @Column(name = "MEM_ROL_ID", nullable = false) + private String memberRoleId; + + @Column(name = "MEM_ROL_NAME", nullable = false) + private String role; + + @Column(name = "MEM_ID", nullable = false, updatable = false) + private String memberId; + +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/command/domain/repository/MemberRepository.java b/src/main/java/stanl_2/final_backend/domain/member/command/domain/repository/MemberRepository.java new file mode 100644 index 00000000..4f0d0086 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/command/domain/repository/MemberRepository.java @@ -0,0 +1,10 @@ +package stanl_2.final_backend.domain.member.command.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import stanl_2.final_backend.domain.member.command.domain.aggregate.entity.Member; + +@Repository +public interface MemberRepository extends JpaRepository { + Member findByLoginId(String loginId); +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/command/domain/repository/MemberRoleRepository.java b/src/main/java/stanl_2/final_backend/domain/member/command/domain/repository/MemberRoleRepository.java new file mode 100644 index 00000000..2710db13 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/command/domain/repository/MemberRoleRepository.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.member.command.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import stanl_2.final_backend.domain.member.command.domain.aggregate.entity.MemberRole; + +@Repository +public interface MemberRoleRepository extends JpaRepository { +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/command/domain/service/AuthCommandServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/member/command/domain/service/AuthCommandServiceImpl.java new file mode 100644 index 00000000..6cf6793d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/command/domain/service/AuthCommandServiceImpl.java @@ -0,0 +1,281 @@ +package stanl_2.final_backend.domain.member.command.domain.service; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import jakarta.mail.MessagingException; +import lombok.extern.slf4j.Slf4j; +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import stanl_2.final_backend.domain.member.command.application.dto.*; +import stanl_2.final_backend.domain.member.command.application.service.AuthCommandService; +import stanl_2.final_backend.domain.member.command.domain.aggregate.entity.Member; +import stanl_2.final_backend.domain.member.command.domain.aggregate.entity.MemberRole; +import stanl_2.final_backend.domain.member.command.domain.repository.MemberRepository; +import stanl_2.final_backend.domain.member.command.domain.repository.MemberRoleRepository; +import stanl_2.final_backend.domain.member.common.exception.MemberCommonException; +import stanl_2.final_backend.domain.member.common.exception.MemberErrorCode; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.s3.command.application.service.S3FileService; +import stanl_2.final_backend.global.exception.GlobalCommonException; +import stanl_2.final_backend.global.exception.GlobalErrorCode; +import stanl_2.final_backend.global.mail.MailService; +import stanl_2.final_backend.global.redis.RedisService; +import stanl_2.final_backend.global.security.service.MemberDetails; +import stanl_2.final_backend.global.utils.AESUtils; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.SecureRandom; +import java.util.Date; +import java.util.stream.Collectors; + +@Slf4j +@Service("commandAuthService") +public class AuthCommandServiceImpl implements AuthCommandService { + + private final RedisService redisService; + @Value("${jwt.secret-key}") + private String jwtSecretKey; + + private static final String LOWERCASE = "abcdefghijklmnopqrstuvwxyz"; + private static final String UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static final String DIGITS = "0123456789"; + private static final String SPECIAL_CHARACTERS = "!@#$%^&*()-_=+<>?"; + + // 임시 비밀번호의 최소 길이 + private static final int MIN_LENGTH = 12; + + private SecureRandom secureRandom = new SecureRandom(); + + private final MemberRepository memberRepository; + private final MemberRoleRepository memberRoleRepository; + private final PasswordEncoder passwordEncoder; + private final ModelMapper modelMapper; + private final AuthenticationManager authenticationManager; + private final AuthQueryService authQueryService; + private final AESUtils aesUtils; + private final S3FileService s3FileService; + private final MailService mailService; + + @Autowired + public AuthCommandServiceImpl(MemberRepository memberRepository, + MemberRoleRepository memberRoleRepository, + PasswordEncoder passwordEncoder, + ModelMapper modelMapper, + AuthenticationManager authenticationManager, + AuthQueryService authQueryService, + AESUtils aesUtils, + S3FileService s3FileService, + MailService mailService, RedisService redisService) { + this.memberRepository = memberRepository; + this.memberRoleRepository = memberRoleRepository; + this.passwordEncoder = passwordEncoder; + this.modelMapper = modelMapper; + this.authenticationManager = authenticationManager; + this.authQueryService = authQueryService; + this.aesUtils = aesUtils; + this.s3FileService = s3FileService; + this.mailService = mailService; + this.redisService = redisService; + } + + @Override + @Transactional + public void signup(SignupDTO signupDTO, MultipartFile imageUrl) throws GeneralSecurityException { + + // 이미지 업로드 및 암호화 + signupDTO.setImageUrl(aesUtils.encrypt(s3FileService.uploadOneFile(imageUrl))); + + String hashPwd = passwordEncoder.encode(signupDTO.getPassword()); + signupDTO.setPassword(hashPwd); + signupDTO.setName(aesUtils.encrypt(signupDTO.getName())); + signupDTO.setEmail(aesUtils.encrypt(signupDTO.getEmail())); + signupDTO.setIdentNo(aesUtils.encrypt(signupDTO.getIdentNo())); + signupDTO.setPhone(aesUtils.encrypt(signupDTO.getPhone())); + signupDTO.setEmergePhone(aesUtils.encrypt(signupDTO.getEmergePhone())); + signupDTO.setAddress(aesUtils.encrypt(signupDTO.getAddress())); + signupDTO.setBankName(aesUtils.encrypt(signupDTO.getBankName())); + signupDTO.setAccount(aesUtils.encrypt(signupDTO.getAccount())); + + Member registerMember = modelMapper.map(signupDTO, Member.class); + + memberRepository.save(registerMember); + } + + @Override + @Transactional + public SigninResponseDTO signin(SigninRequestDTO signinRequestDTO) throws GeneralSecurityException { + // 사용자 인증 + Authentication authentication = new UsernamePasswordAuthenticationToken( + signinRequestDTO.getLoginId(), signinRequestDTO.getPassword()); + Authentication authenticationResponse = authenticationManager.authenticate(authentication); + + if (authenticationResponse == null || !authenticationResponse.isAuthenticated()) { + throw new GlobalCommonException(GlobalErrorCode.LOGIN_FAILURE); + } + + // 인증된 사용자 정보를 SecurityContext에 저장 + SecurityContextHolder.getContext().setAuthentication(authenticationResponse); + + // 권한 정보 추출 + String authorities = authenticationResponse.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.joining(",")); + + // JWT 토큰 생성 + SecretKey secretKey = Keys.hmacShaKeyFor(jwtSecretKey.getBytes(StandardCharsets.UTF_8)); + String accessToken = generateAccessToken(authenticationResponse.getName(), authorities, secretKey); + String refreshToken = generateRefreshToken(authenticationResponse.getName(), authorities,secretKey); + + MemberDetails memberDetails = (MemberDetails) authenticationResponse.getPrincipal(); + + String memberAuthorities = memberDetails.getAuthorities().toString(); + memberAuthorities = memberAuthorities.substring(1, memberAuthorities.length() - 1); + String[] roleArray = memberAuthorities.split(",\\s*"); + String firstRole = ""; + if (roleArray.length > 0) { + firstRole = roleArray[0].replace("ROLE_", ""); // ROLE_ 제거 + } else { + throw new GlobalCommonException(GlobalErrorCode.AUTHORITIES_NOT_FOUND); + } + return new SigninResponseDTO( + accessToken, refreshToken, + aesUtils.decrypt(memberDetails.getMember().getName()), + memberDetails.getMember().getPosition(), + firstRole, + aesUtils.decrypt(memberDetails.getMember().getImageUrl()) + ); + } + + @Override + @Transactional + public void sendEmail(CheckMailDTO checkMailDTO) throws GeneralSecurityException, MessagingException { + String email = authQueryService.findEmail(checkMailDTO.getLoginId()); + + mailService.sendEmail(email); + } + + @Override + @Transactional + public void checkNum(CheckNumDTO checkNumDTO) throws GeneralSecurityException { + String email = authQueryService.findEmail(checkNumDTO.getLoginId()); + + if (checkNumDTO.getNumber() != null && !checkNumDTO.getNumber().equals(redisService.getKey(email))) { + throw new MemberCommonException(MemberErrorCode.NUMBER_NOT_FOUND); + } + } + + @Override + @Transactional + public void sendNewPwd(String loginId) throws MessagingException, GeneralSecurityException { + StringBuilder password = new StringBuilder(); + + // 반드시 포함할 문자들 + password.append(randomChar(LOWERCASE)); // 소문자 + password.append(randomChar(UPPERCASE)); // 대문자 + password.append(randomChar(DIGITS)); // 숫자 + password.append(randomChar(SPECIAL_CHARACTERS)); // 특수문자 + + // 나머지 비밀번호 길이 채우기 (MIN_LENGTH 까지) + String allChars = LOWERCASE + UPPERCASE + DIGITS + SPECIAL_CHARACTERS; + for (int i = password.length(); i < MIN_LENGTH; i++) { + password.append(randomChar(allChars)); + } + + String hashPwd = passwordEncoder.encode(password); + + Member newPwdMem = memberRepository.findByLoginId(loginId); + + mailService.sendPwdEmail(aesUtils.decrypt(newPwdMem.getEmail()), password); + + newPwdMem.setPassword(hashPwd); + + memberRepository.save(newPwdMem); + } + + private char randomChar(String charSet) { + int randomIndex = secureRandom.nextInt(charSet.length()); + return charSet.charAt(randomIndex); + } + + private String generateAccessToken(String username, String authorities, SecretKey secretKey) { + return Jwts.builder() + .setIssuer("STANL2") + .setSubject("Access Token") + .claim("username", username) + .claim("authorities", authorities) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + 1800000)) // 30분 유효 + .signWith(secretKey) + .compact(); + } + + private String generateRefreshToken(String username, String authorities, SecretKey secretKey) { + return Jwts.builder() + .setIssuer("STANL2") + .setSubject("Refresh Token") + .claim("username", username) + .claim("authorities", authorities) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 24시간 유효 + .signWith(secretKey) + .compact(); + } + + @Override + public RefreshDTO refreshAccessToken(String refreshToken) { + SecretKey secretKey = Keys.hmacShaKeyFor(jwtSecretKey.getBytes(StandardCharsets.UTF_8)); + + + // Refresh Token을 검증하고 클레임을 추출 + Claims claims = Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(refreshToken) + .getBody(); + + // 토큰의 주제가 "Refresh Token"인지 확인 + if (!"Refresh Token".equals(claims.getSubject())) { + throw new GlobalCommonException(GlobalErrorCode.INVALID_TOKEN_ERROR); + } + + // 사용자 정보 추출 + String username = claims.get("username", String.class); + String authorities = claims.get("authorities", String.class); + + if (username == null || authorities == null) { + throw new GlobalCommonException(GlobalErrorCode.INVALID_TOKEN_ERROR); + } + + // 새로운 Access Token 생성 + return RefreshDTO.builder() + .newAccessToken(generateAccessToken(username, authorities, secretKey)) + .build(); + } + + @Override + @Transactional + public void grantAuthority(GrantDTO grantDTO) { + + String loginId = authQueryService.selectMemberIdByLoginId(grantDTO.getLoginId()); + + MemberRole newMemberRole = modelMapper.map(grantDTO, MemberRole.class); + + // fk 값 설정 + newMemberRole.setMemberId(loginId); + + memberRoleRepository.save(newMemberRole); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/command/domain/service/MemberCommandServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/member/command/domain/service/MemberCommandServiceImpl.java new file mode 100644 index 00000000..cd042289 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/command/domain/service/MemberCommandServiceImpl.java @@ -0,0 +1,29 @@ +package stanl_2.final_backend.domain.member.command.domain.service; + +import lombok.extern.slf4j.Slf4j; +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import stanl_2.final_backend.domain.member.command.application.service.MemberCommandService; +import stanl_2.final_backend.domain.member.command.domain.repository.MemberRepository; +import stanl_2.final_backend.global.utils.AESUtils; + + +@Slf4j +@Service("commandMemberService") +public class MemberCommandServiceImpl implements MemberCommandService { + + private final MemberRepository memberRepository; + private final ModelMapper modelMapper; + private final AESUtils aesUtils; + + @Autowired + public MemberCommandServiceImpl(MemberRepository memberRepository, + ModelMapper modelMapper, + AESUtils aesUtils) { + this.memberRepository = memberRepository; + this.modelMapper = modelMapper; + this.aesUtils = aesUtils; + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/common/exception/MemberCommonException.java b/src/main/java/stanl_2/final_backend/domain/member/common/exception/MemberCommonException.java new file mode 100644 index 00000000..5e775d30 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/common/exception/MemberCommonException.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.member.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class MemberCommonException extends RuntimeException { + private final MemberErrorCode errorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.errorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/common/exception/MemberErrorCode.java b/src/main/java/stanl_2/final_backend/domain/member/common/exception/MemberErrorCode.java new file mode 100644 index 00000000..4c2e71ae --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/common/exception/MemberErrorCode.java @@ -0,0 +1,56 @@ +package stanl_2.final_backend.domain.member.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum MemberErrorCode { + + /** + * 400(Bad Request) + * 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. + */ + + + + /** + * 401(Unauthorized) + * 비록 HTTP 표준에서는 "미승인(unauthorized)"를 명확히 하고 있지만, + * 의미상 이 응답은 "비인증(unauthenticated)"을 의미합니다. + * 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로를 인증해야 합니다. + */ + + + /** + * 403(Forbidden) + * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. + * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. + */ + + + + /** + * 404(Not Found) + * 서버는 요청받은 리소스를 찾을 수 없습니다. 브라우저에서는 알려지지 않은 URL을 의미합니다. + * 이것은 API에서 종점은 적절하지만 리소스 자체는 존재하지 않음을 의미할 수도 있습니다. + * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. + * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. + */ + MEMBER_NOT_FOUND(404001, HttpStatus.NOT_FOUND, "회원 데이터를 찾지 못했습니다"), + MEMBER_ID_NOT_FOUND(404002, HttpStatus.NOT_FOUND, "회원 pk값을 찾지 못했습니다."), + NUMBER_NOT_FOUND(40403, HttpStatus.NOT_FOUND, "인증번호가 맞지 않습니다."), + + + /** + * 500(Internal Server Error) + * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. + */ + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); + + + private final Integer code; + private final HttpStatus httpStatus; + private final String msg; +} diff --git a/src/main/java/stanl_2/final_backend/domain/center/common/exception/ExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/member/common/exception/MemberExceptionResponse.java similarity index 52% rename from src/main/java/stanl_2/final_backend/domain/center/common/exception/ExceptionResponse.java rename to src/main/java/stanl_2/final_backend/domain/member/common/exception/MemberExceptionResponse.java index d7ebb1c1..2a36eedb 100644 --- a/src/main/java/stanl_2/final_backend/domain/center/common/exception/ExceptionResponse.java +++ b/src/main/java/stanl_2/final_backend/domain/member/common/exception/MemberExceptionResponse.java @@ -1,22 +1,22 @@ -package stanl_2.final_backend.domain.center.common.exception; +package stanl_2.final_backend.domain.member.common.exception; import lombok.Getter; import org.springframework.http.HttpStatus; @Getter -public class ExceptionResponse { +public class MemberExceptionResponse { private final Integer code; private final String msg; private final HttpStatus httpStatus; - public ExceptionResponse(ErrorCode errorCode) { + public MemberExceptionResponse(MemberErrorCode errorCode) { this.code = errorCode.getCode(); this.msg = errorCode.getMsg(); this.httpStatus = errorCode.getHttpStatus(); } - public static ExceptionResponse of(ErrorCode errorCode) { - return new ExceptionResponse(errorCode); + public static MemberExceptionResponse of(MemberErrorCode errorCode) { + return new MemberExceptionResponse(errorCode); } } diff --git a/src/main/java/stanl_2/final_backend/domain/member/common/response/MemberResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/member/common/response/MemberResponseMessage.java new file mode 100644 index 00000000..1f9a9e25 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/common/response/MemberResponseMessage.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.member.common.response; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +public class MemberResponseMessage { + private int httpStatus; + private String msg; + private Object result; +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/member/controller/AuthController.java b/src/main/java/stanl_2/final_backend/domain/member/controller/AuthController.java deleted file mode 100644 index 598c8c9e..00000000 --- a/src/main/java/stanl_2/final_backend/domain/member/controller/AuthController.java +++ /dev/null @@ -1,85 +0,0 @@ -package stanl_2.final_backend.domain.member.controller; - -import io.swagger.v3.oas.annotations.Operation; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.modelmapper.ModelMapper; -import org.springframework.http.ResponseEntity; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import stanl_2.final_backend.domain.member.aggregate.dto.SignUpMemberDTO; -import stanl_2.final_backend.domain.member.aggregate.dto.jwtDTO; -import stanl_2.final_backend.domain.member.aggregate.vo.request.LoginRequestVO; -import stanl_2.final_backend.domain.member.aggregate.vo.request.SignUpRequestVO; -import stanl_2.final_backend.domain.member.service.MemberService; -import stanl_2.final_backend.global.common.response.ResponseMessage; -import stanl_2.final_backend.global.exception.CommonException; -import stanl_2.final_backend.global.exception.ErrorCode; -import stanl_2.final_backend.global.utils.RequestUtils; - -@Slf4j -@RequiredArgsConstructor -@RestController("value = authController") -@RequestMapping("/api/v1/auth") -public class AuthController { - - private final MemberService memberService; - private final ModelMapper modelMapper; - private final AuthenticationManager authenticationManager; - - @PostMapping("/signup") - @Operation(summary = "회원가입 API") - public ResponseEntity signUp(@RequestBody SignUpRequestVO signUpRequestVO) { - - SignUpMemberDTO memberRequestDTO = modelMapper.map(signUpRequestVO, SignUpMemberDTO.class); - - if (memberService.signUp(memberRequestDTO)) { - new CommonException(ErrorCode.REGISTER_FAIL); - } - -// return ResponseEntity.ok("회원가입 성공!"); - return ResponseEntity.ok(new ResponseMessage(200, "회원가입 성공", null)); - } - - @PostMapping("/signin") - @Operation(summary = "로그인 API") - public ResponseEntity signin(@RequestBody LoginRequestVO loginRequestVO, - HttpServletRequest request, - HttpServletResponse response) { - - log.info("c1"); - // 인증 생성 - Authentication authentication = UsernamePasswordAuthenticationToken - .unauthenticated(loginRequestVO.getLoginId(), loginRequestVO.getPassword()); - - log.info("c2"); - // 인증 처리 - Authentication authenticationResponse = authenticationManager.authenticate(authentication); - log.info("c3"); - - // JWT 생성 - String jwt = memberService.signin(authenticationResponse); - log.info("c4"); - - if("".equals(jwt)){ - throw new CommonException(ErrorCode.LOGIN_FAILURE); - } - log.info("c5"); - - // 로그인 이력 저장(ip, local 컴퓨터) - log.info("{}", RequestUtils.getClientIp(request)); - log.info("{}", request.getHeader("User-Agent")); - jwtDTO jwt1 = new jwtDTO(); - jwt1.setJwt(jwt); -// return ResponseEntity.ok(jwt1); - return ResponseEntity.ok(new ResponseMessage(200, "로그인 성공", jwt)); - } - -} diff --git a/src/main/java/stanl_2/final_backend/domain/member/query/config/MybatisConfiguration.java b/src/main/java/stanl_2/final_backend/domain/member/query/config/MybatisConfiguration.java new file mode 100644 index 00000000..f9a031c9 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/query/config/MybatisConfiguration.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.member.query.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Configuration; + +@Configuration("memberMybatisConfiguration") +@MapperScan(basePackages = "stanl_2.final_backend.domain.member.query.repository") +public class MybatisConfiguration { +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/query/controller/AuthController.java b/src/main/java/stanl_2/final_backend/domain/member/query/controller/AuthController.java new file mode 100644 index 00000000..5882ec68 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/query/controller/AuthController.java @@ -0,0 +1,20 @@ +package stanl_2.final_backend.domain.member.query.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; + +@RestController(value = "queryAuthController") +@RequestMapping("/api/v1/auth") +public class AuthController { + + private final AuthQueryService authQueryService; + + @Autowired + public AuthController(AuthQueryService authQueryService) { + this.authQueryService = authQueryService; + } + + +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/query/controller/MemberController.java b/src/main/java/stanl_2/final_backend/domain/member/query/controller/MemberController.java new file mode 100644 index 00000000..14442ac9 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/query/controller/MemberController.java @@ -0,0 +1,228 @@ +package stanl_2.final_backend.domain.member.query.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stanl_2.final_backend.domain.customer.common.response.CustomerResponseMessage; +import stanl_2.final_backend.domain.member.common.response.MemberResponseMessage; +import stanl_2.final_backend.domain.member.query.dto.MemberCenterListDTO; +import stanl_2.final_backend.domain.member.query.dto.MemberDTO; +import stanl_2.final_backend.domain.member.query.dto.MemberSearchDTO; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; + +import java.security.GeneralSecurityException; +import java.security.Principal; +import java.util.List; + +@Slf4j +@RestController(value = "queryMemberController") +@RequestMapping("/api/v1/member") +public class MemberController { + + private final MemberQueryService memberQueryService; + + @Autowired + public MemberController(MemberQueryService memberQueryService) { + this.memberQueryService = memberQueryService; + } + + @Operation(summary = "회원 정보 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = MemberResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("") + public ResponseEntity getMemberInfo(Principal principal) throws GeneralSecurityException { + + MemberDTO memberInfo = memberQueryService.selectMemberInfo(principal.getName()); + + return ResponseEntity.ok(MemberResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(memberInfo) + .build()); + } + + @Operation(summary = "회원 정보 조회(with 사번)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = MemberResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("info/{loginId}") + public ResponseEntity getMemberInfoBymemberId(@PathVariable("loginId") String loginId) throws GeneralSecurityException { + + MemberDTO memberInfo = memberQueryService.selectMemberInfo(loginId); + + return ResponseEntity.ok(MemberResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(memberInfo) + .build()); + } + + @Operation(summary = "회원 정보 조건(매장) 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = MemberResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("{centerId}") + public ResponseEntity getMemberByCenterId(@PathVariable("centerId") String centerId) throws GeneralSecurityException { + + List memberList = memberQueryService.selectMemberByCenterId(centerId); + + return ResponseEntity.ok(MemberResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(memberList) + .build()); + } + + @Operation(summary = "회원 정보 조건(매장리스트) 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = MemberResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("centerList") + public ResponseEntity getMemberByCenterList(@RequestBody MemberCenterListDTO memberCenterListDTO) throws GeneralSecurityException { + + List memberList = memberQueryService.selectMemberByCenterList(memberCenterListDTO.getCenterList()); + + return ResponseEntity.ok(MemberResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(memberList) + .build()); + } + + + @Operation(summary = "회원 정보 조건(부서) 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = MemberResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/organization/{organizationId}") + public ResponseEntity getMemberByOrganizationId(@PathVariable("organizationId") String organizationId) throws GeneralSecurityException { + + List memberList = memberQueryService.selectMemberByOrganizationId(organizationId); + + return ResponseEntity.ok(MemberResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(memberList) + .build()); + } + + + @Operation(summary = "회원 id로 정보 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = MemberResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/memberInfo/{memberId}") + public ResponseEntity getMemberInfoById(@PathVariable("memberId") String memberId) throws GeneralSecurityException { + + MemberDTO memberInfo = memberQueryService.selectMemberInfoById(memberId); + + return ResponseEntity.ok(MemberResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(memberInfo) + .build()); + } + + @Operation(summary = "회원 정보 조건(사원이름) 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = MemberResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("search") + public ResponseEntity getMemberByName(@RequestParam(required = true) String name) throws GeneralSecurityException { + + List memberList = memberQueryService.selectMemberByName(name); + + return ResponseEntity.ok(MemberResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(memberList) + .build()); + } + + + @Operation(summary = "회원 정보 검색 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = MemberResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("search/list") + public ResponseEntity getMemberByName( + @RequestParam(required = false) String loginId, + @RequestParam(required = false) String memberName, + @RequestParam(required = false) String phone, + @RequestParam(required = false) String email, + @RequestParam(required = false) String centerName, + @RequestParam(required = false) String organizationName, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder, + @PageableDefault(size = 10) Pageable pageable + ) throws GeneralSecurityException { + + // 정렬 추가 + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + MemberSearchDTO memberSearchDTO = new MemberSearchDTO(loginId, memberName, phone, email, centerName, organizationName); + Page memberDTOPage = memberQueryService.selectMemberBySearch(pageable, memberSearchDTO); + + return ResponseEntity.ok(MemberResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(memberDTOPage) + .build()); + } + + + @Operation(summary = "엑셀 다운로드") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = CustomerResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("excel") + public void exportCustomer(HttpServletResponse response) throws GeneralSecurityException { + + memberQueryService.exportCustomerToExcel(response); + } + + +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/query/dto/MemberCenterListDTO.java b/src/main/java/stanl_2/final_backend/domain/member/query/dto/MemberCenterListDTO.java new file mode 100644 index 00000000..809388f4 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/query/dto/MemberCenterListDTO.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.member.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class MemberCenterListDTO { + private List centerList; +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/query/dto/MemberDTO.java b/src/main/java/stanl_2/final_backend/domain/member/query/dto/MemberDTO.java new file mode 100644 index 00000000..5ff361dd --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/query/dto/MemberDTO.java @@ -0,0 +1,35 @@ +package stanl_2.final_backend.domain.member.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class MemberDTO { + private String memberId; + private String loginId; + private String name; + private String imageUrl; + private String email; + private Integer age; + private String sex; + private String identNo; + private String phone; + private String emergePhone; + private String address; + private String note; + private String position; + private String grade; + private String jobType; + private String military; + private String bankName; + private String account; + private String centerId; + private String createdAt; + private String updatedAt; + private String centerName; +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/query/dto/MemberExcelDTO.java b/src/main/java/stanl_2/final_backend/domain/member/query/dto/MemberExcelDTO.java new file mode 100644 index 00000000..1b3bdcc0 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/query/dto/MemberExcelDTO.java @@ -0,0 +1,57 @@ +package stanl_2.final_backend.domain.member.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import stanl_2.final_backend.global.excel.ExcelColumnName; + +@Getter +@Setter +@AllArgsConstructor +public class MemberExcelDTO { + + @ExcelColumnName(name = "사원 번호") + private String loginId; + + @ExcelColumnName(name = "사원명") + private String name; + + @ExcelColumnName(name = "사원 이메일") + private String email; + + @ExcelColumnName(name = "사원 나이") + private Integer age; + + @ExcelColumnName(name = "사원 성별") + private String sex; + + @ExcelColumnName(name = "사원 연락처") + private String phone; + + @ExcelColumnName(name = "사원 비상연락처") + private String emergePhone; + + @ExcelColumnName(name = "사원 주소") + private String address; + + @ExcelColumnName(name = "비고") + private String note; + + @ExcelColumnName(name = "직급") + private String position; + + @ExcelColumnName(name = "학력") + private String grade; + + @ExcelColumnName(name = "고용형태") + private String jobType; + + @ExcelColumnName(name = "병역구분") + private String military; + + @ExcelColumnName(name = "은행명") + private String bankName; + + @ExcelColumnName(name = "계좌 번호") + private String account; +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/query/dto/MemberSearchDTO.java b/src/main/java/stanl_2/final_backend/domain/member/query/dto/MemberSearchDTO.java new file mode 100644 index 00000000..dc6bba4c --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/query/dto/MemberSearchDTO.java @@ -0,0 +1,19 @@ +package stanl_2.final_backend.domain.member.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class MemberSearchDTO { + private String loginId; + private String memberName; + private String phone; + private String email; + private String centerName; + private String organizationName; +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/query/repository/AuthMapper.java b/src/main/java/stanl_2/final_backend/domain/member/query/repository/AuthMapper.java new file mode 100644 index 00000000..3f844b0f --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/query/repository/AuthMapper.java @@ -0,0 +1,11 @@ +package stanl_2.final_backend.domain.member.query.repository; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +@Mapper +public interface AuthMapper { + String selectIdByMemberName(@Param("loginId") String loginId); + + String findEmailByLoginId(@Param("loginId") String loginId); +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/query/repository/MemberMapper.java b/src/main/java/stanl_2/final_backend/domain/member/query/repository/MemberMapper.java new file mode 100644 index 00000000..a22d5c90 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/query/repository/MemberMapper.java @@ -0,0 +1,34 @@ +package stanl_2.final_backend.domain.member.query.repository; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import stanl_2.final_backend.domain.center.query.dto.CenterSelectAllDTO; +import stanl_2.final_backend.domain.member.query.dto.MemberDTO; +import stanl_2.final_backend.domain.member.query.dto.MemberExcelDTO; +import stanl_2.final_backend.domain.member.query.dto.MemberSearchDTO; + +import java.util.List; +import java.util.Map; + +@Mapper +public interface MemberMapper { + MemberDTO findMemberInfoById(@Param("loginId") String loginId); + + String findNameById(@Param("memberId") String memberId); + + List findMembersByCenterId(@Param("centerId") String centerId); + + List findMembersByCenterList(@Param("centerList") List centerList); + + List findMembersByOrganizationId(@Param("organizationId") String organizationId); + + MemberDTO findMemberInfoBymemberId(String memberId); + + List findMemberByName(String memberName); + + List findMemberByConditions(Map params); + + Integer findMemberCnt(Map params); + + List findMemberForExcel(); +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/query/repository/MemberRoleMapper.java b/src/main/java/stanl_2/final_backend/domain/member/query/repository/MemberRoleMapper.java new file mode 100644 index 00000000..008b5e4b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/query/repository/MemberRoleMapper.java @@ -0,0 +1,12 @@ +package stanl_2.final_backend.domain.member.query.repository; + +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface MemberRoleMapper { + List findMembersbyRole(String role); + + String findMembersRolebyId(String memberId); +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/query/service/AuthQueryService.java b/src/main/java/stanl_2/final_backend/domain/member/query/service/AuthQueryService.java new file mode 100644 index 00000000..4a06509a --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/query/service/AuthQueryService.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.member.query.service; + +import java.security.GeneralSecurityException; + +public interface AuthQueryService { + String selectMemberIdByLoginId(String name); + + String findEmail(String loginId) throws GeneralSecurityException; +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/query/service/AuthQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/member/query/service/AuthQueryServiceImpl.java new file mode 100644 index 00000000..b3d74f1a --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/query/service/AuthQueryServiceImpl.java @@ -0,0 +1,51 @@ +package stanl_2.final_backend.domain.member.query.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.member.command.application.dto.CheckMailDTO; +import stanl_2.final_backend.domain.member.common.exception.MemberCommonException; +import stanl_2.final_backend.domain.member.common.exception.MemberErrorCode; +import stanl_2.final_backend.domain.member.query.repository.AuthMapper; +import stanl_2.final_backend.global.utils.AESUtils; + +import java.security.GeneralSecurityException; + +@Slf4j +@Service(value = "queryAuthService") +public class AuthQueryServiceImpl implements AuthQueryService { + + private final AuthMapper authMapper; + private final AESUtils aesUtils; + + @Autowired + public AuthQueryServiceImpl(AuthMapper authMapper, + AESUtils aesUtils) { + this.authMapper = authMapper; + this.aesUtils = aesUtils; + } + + @Override + public String selectMemberIdByLoginId(String loginId){ + + String id = authMapper.selectIdByMemberName(loginId); + + if(id == null){ + throw new MemberCommonException(MemberErrorCode.MEMBER_ID_NOT_FOUND); + } + + return id; + } + + @Override + @Transactional(readOnly = true) + public String findEmail(String loginId) throws GeneralSecurityException { + + String email = authMapper.findEmailByLoginId(loginId); + + email = aesUtils.decrypt(email); + + return email; + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/query/service/MemberQueryService.java b/src/main/java/stanl_2/final_backend/domain/member/query/service/MemberQueryService.java new file mode 100644 index 00000000..17f7953b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/query/service/MemberQueryService.java @@ -0,0 +1,40 @@ +package stanl_2.final_backend.domain.member.query.service; + +import jakarta.mail.MessagingException; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import stanl_2.final_backend.domain.log.command.aggregate.Log; +import stanl_2.final_backend.domain.member.query.dto.MemberDTO; +import stanl_2.final_backend.domain.member.query.dto.MemberSearchDTO; + +import java.security.GeneralSecurityException; +import java.util.List; + +public interface MemberQueryService { + MemberDTO selectMemberInfo(String name) throws GeneralSecurityException; + + List selectMemberByRole(String role); + + List selectMemberByCenterId(String centerId); + + List selectMemberByCenterList(List centerList); + + String selectNameById(String memberId) throws GeneralSecurityException; + + List selectMemberByOrganizationId(String organizationId) throws GeneralSecurityException; + + MemberDTO selectMemberInfoById(String memberId) throws GeneralSecurityException; + + List selectMemberByName(String name) throws GeneralSecurityException; + + Page selectMemberBySearch(Pageable pageable, MemberSearchDTO memberSearchDTO) throws GeneralSecurityException; + + void exportCustomerToExcel(HttpServletResponse response) throws GeneralSecurityException; + + void sendErrorMail(String loginId, Log logEntry) throws GeneralSecurityException, MessagingException; + + String selectMemberRoleById(String memberId); + +// MemberDetailDTO selectMemberDetail(String name) throws GeneralSecurityException; +} diff --git a/src/main/java/stanl_2/final_backend/domain/member/query/service/MemberQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/member/query/service/MemberQueryServiceImpl.java new file mode 100644 index 00000000..6b7865d8 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/member/query/service/MemberQueryServiceImpl.java @@ -0,0 +1,275 @@ +package stanl_2.final_backend.domain.member.query.service; + +import jakarta.mail.MessagingException; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.center.query.service.CenterQueryService; +import stanl_2.final_backend.domain.log.command.aggregate.Log; +import stanl_2.final_backend.domain.member.common.exception.MemberCommonException; +import stanl_2.final_backend.domain.member.common.exception.MemberErrorCode; +import stanl_2.final_backend.domain.member.query.dto.MemberDTO; +import stanl_2.final_backend.domain.member.query.dto.MemberExcelDTO; +import stanl_2.final_backend.domain.member.query.dto.MemberSearchDTO; +import stanl_2.final_backend.domain.member.query.repository.MemberMapper; +import stanl_2.final_backend.domain.member.query.repository.MemberRoleMapper; +import stanl_2.final_backend.global.excel.ExcelUtilsV1; +import stanl_2.final_backend.global.mail.MailService; +import stanl_2.final_backend.global.utils.AESUtils; + +import java.security.GeneralSecurityException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +@Service(value = "queryMemberService") +public class MemberQueryServiceImpl implements MemberQueryService { + + private final MemberMapper memberMapper; + private final CenterQueryService centerQueryService; + private final AESUtils aesUtils; + private final MemberRoleMapper memberRoleMapper; + private final ExcelUtilsV1 excelUtilsV1; + private final MailService mailService; + + @Autowired + public MemberQueryServiceImpl(MemberMapper memberMapper, + CenterQueryService centerQueryService, + AESUtils aesUtils, + MemberRoleMapper memberRoleMapper, + ExcelUtilsV1 excelUtilsV1, + MailService mailService) { + this.memberMapper = memberMapper; + this.centerQueryService = centerQueryService; + this.aesUtils = aesUtils; + this.memberRoleMapper = memberRoleMapper; + this.excelUtilsV1 = excelUtilsV1; + this.mailService = mailService; + } + + @Override + @Transactional + public MemberDTO selectMemberInfo(String name) throws GeneralSecurityException { + + MemberDTO memberInfo = memberMapper.findMemberInfoById(name); + + if(memberInfo == null){ + throw new MemberCommonException(MemberErrorCode.MEMBER_NOT_FOUND); + } + + memberInfo.setName(aesUtils.decrypt(memberInfo.getName())); + memberInfo.setEmail(aesUtils.decrypt(memberInfo.getEmail())); + memberInfo.setIdentNo(aesUtils.decrypt(memberInfo.getIdentNo())); + memberInfo.setPhone(aesUtils.decrypt(memberInfo.getPhone())); + memberInfo.setEmergePhone(aesUtils.decrypt(memberInfo.getEmergePhone())); + memberInfo.setAddress(aesUtils.decrypt(memberInfo.getAddress())); + memberInfo.setBankName(aesUtils.decrypt(memberInfo.getBankName())); + memberInfo.setAccount(aesUtils.decrypt(memberInfo.getAccount())); + memberInfo.setImageUrl(aesUtils.decrypt(memberInfo.getImageUrl())); + memberInfo.setCenterId(memberInfo.getCenterId()); + + return memberInfo; + } + + @Override + @Transactional + public List selectMemberByRole(String role){ + + List memberList = memberRoleMapper.findMembersbyRole(role); + + return memberList; + } + + @Override + @Transactional + public List selectMemberByCenterId(String centerId){ + + List memberList = memberMapper.findMembersByCenterId(centerId); + + memberList.forEach(dto -> { + try { + dto.setName(selectNameById(dto.getMemberId())); + + dto.setEmail(aesUtils.decrypt(dto.getEmail())); + } catch (Exception e) { + throw new MemberCommonException(MemberErrorCode.MEMBER_NOT_FOUND); + } + }); + + return memberList; + } + + @Override + @Transactional + public List selectMemberByCenterList(List centerList) { + List memberList = memberMapper.findMembersByCenterList(centerList); + + memberList.forEach(dto -> { + try { + dto.setName(selectNameById(dto.getMemberId())); + } catch (Exception e) { + throw new MemberCommonException(MemberErrorCode.MEMBER_NOT_FOUND); + } + }); + + return memberList; + } + + @Override + @Transactional + public String selectNameById(String memberId) throws GeneralSecurityException { + + String name = memberMapper.findNameById(memberId); + name = aesUtils.decrypt(name); + + return name; + } + + @Override + @Transactional(readOnly = true) + public List selectMemberByOrganizationId(String organizationId) throws GeneralSecurityException { + + List memberList = memberMapper.findMembersByOrganizationId(organizationId); + + for(int i=0;i selectMemberByName(String name) throws GeneralSecurityException { + + String memberName = aesUtils.encrypt(name); + List memberList = memberMapper.findMemberByName(memberName); + + if(memberList == null){ + throw new MemberCommonException(MemberErrorCode.MEMBER_NOT_FOUND); + } + + for(int i=0;i selectMemberBySearch(Pageable pageable, MemberSearchDTO memberSearchDTO) throws GeneralSecurityException { + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + + // 정렬 정보 가져오기 + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + Map params = new HashMap<>(); + params.put("offset", offset); + params.put("size", size); + params.put("sortField", sortField); + params.put("sortOrder", sortOrder); + + // 암호화 시켜서 검색 + memberSearchDTO.setMemberName(aesUtils.encrypt(memberSearchDTO.getMemberName())); + memberSearchDTO.setPhone(aesUtils.encrypt(memberSearchDTO.getPhone())); + memberSearchDTO.setEmail(aesUtils.encrypt(memberSearchDTO.getEmail())); + + params.put("loginId", memberSearchDTO.getLoginId()); + params.put("memberName", memberSearchDTO.getMemberName()); + params.put("phone", memberSearchDTO.getPhone()); + params.put("email", memberSearchDTO.getEmail()); + params.put("centerName", memberSearchDTO.getCenterName()); + params.put("organizationName", memberSearchDTO.getOrganizationName()); + + List memberList = memberMapper.findMemberByConditions(params); + + Integer count = memberMapper.findMemberCnt(params); + + // 암호화 풀기 + for(int i=0;i(memberList, pageable, count); + } + + @Override + public void exportCustomerToExcel(HttpServletResponse response) throws GeneralSecurityException { + List memberExcels = memberMapper.findMemberForExcel(); + + if(memberExcels == null){ + throw new MemberCommonException(MemberErrorCode.MEMBER_NOT_FOUND); + } + + for(int i=0;i { - Member findByloginId(String loginId); -} diff --git a/src/main/java/stanl_2/final_backend/domain/member/service/MemberService.java b/src/main/java/stanl_2/final_backend/domain/member/service/MemberService.java deleted file mode 100644 index 6f4d1d59..00000000 --- a/src/main/java/stanl_2/final_backend/domain/member/service/MemberService.java +++ /dev/null @@ -1,10 +0,0 @@ -package stanl_2.final_backend.domain.member.service; - -import org.springframework.security.core.Authentication; -import stanl_2.final_backend.domain.member.aggregate.dto.SignUpMemberDTO; - -public interface MemberService { - Boolean signUp(SignUpMemberDTO memberRequestDTO); - - String signin(Authentication authenticationResponse); -} diff --git a/src/main/java/stanl_2/final_backend/domain/member/service/MemberServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/member/service/MemberServiceImpl.java deleted file mode 100644 index d04b9d49..00000000 --- a/src/main/java/stanl_2/final_backend/domain/member/service/MemberServiceImpl.java +++ /dev/null @@ -1,87 +0,0 @@ -package stanl_2.final_backend.domain.member.service; - -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.security.Keys; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.modelmapper.ModelMapper; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import stanl_2.final_backend.domain.member.aggregate.dto.SignUpMemberDTO; -import stanl_2.final_backend.domain.member.aggregate.entity.Member; -import stanl_2.final_backend.domain.member.repository.MemberRepository; -import stanl_2.final_backend.global.security.constants.ApplicationConstants; -import stanl_2.final_backend.global.security.service.MemberPrincipal; - -import javax.crypto.SecretKey; -import java.sql.Timestamp; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.stream.Collectors; - -@Slf4j -@Service("memberServiceImpl") -@RequiredArgsConstructor -public class MemberServiceImpl implements MemberService { - - private final MemberRepository memberRepository; - private final ModelMapper modelMapper; - private final PasswordEncoder passwordEncoder; - private final ApplicationConstants applicationConstants; - private static final long JWT_EXPIRATION_TIME = 30000000L; - - private Timestamp getCurrentTimestamp() { - ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); - return Timestamp.from(nowKst.toInstant()); - } - - @Override - public Boolean signUp(SignUpMemberDTO memberRequestDTO) { - Timestamp currentTimestamp = getCurrentTimestamp(); - - String hashPwd = passwordEncoder.encode(memberRequestDTO.getPassword()); - memberRequestDTO.setPassword(hashPwd); - - Member signUpMember = modelMapper.map(memberRequestDTO, Member.class); - signUpMember.setCreatedAt(currentTimestamp); - signUpMember.setUpdatedAt(currentTimestamp); - - Member newMember = memberRepository.save(signUpMember); - - if(newMember != null) { - return false; - } - - return true; - } - - @Override - public String signin(Authentication authenticationResponse) { - - String jwt = ""; - - if(authenticationResponse != null && authenticationResponse.isAuthenticated()){ - - // Base64로 디코딩된 SecretKey 사용 - byte[] decodedKey = applicationConstants.getJWT_SECRET_KEY(); - SecretKey secretKey = Keys.hmacShaKeyFor(decodedKey); - - MemberPrincipal memberDetails = (MemberPrincipal) authenticationResponse.getPrincipal(); - Member member = memberDetails.getMember(); // MemberPrincipal에서 Member를 얻어옴 - - jwt = Jwts.builder() - .setIssuer("STANL2") - .setSubject("JWT Token") - .claim("loginId", authenticationResponse.getName()) - .claim("authorities", authenticationResponse.getAuthorities() - .stream().map( - GrantedAuthority::getAuthority).collect(Collectors.joining(","))) - .setIssuedAt(new java.util.Date()) - .setExpiration(new java.util.Date((new java.util.Date()).getTime() + 30000000)) - .signWith(secretKey).compact(); - } - return jwt; - } -} diff --git a/src/main/java/stanl_2/final_backend/domain/news/common/exception/NewsCommonException.java b/src/main/java/stanl_2/final_backend/domain/news/common/exception/NewsCommonException.java new file mode 100644 index 00000000..7fdad446 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/news/common/exception/NewsCommonException.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.news.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class NewsCommonException extends RuntimeException { + private final NewsErrorCode errorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.errorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/center/common/exception/ErrorCode.java b/src/main/java/stanl_2/final_backend/domain/news/common/exception/NewsErrorCode.java similarity index 95% rename from src/main/java/stanl_2/final_backend/domain/center/common/exception/ErrorCode.java rename to src/main/java/stanl_2/final_backend/domain/news/common/exception/NewsErrorCode.java index 8353f359..dfae328c 100644 --- a/src/main/java/stanl_2/final_backend/domain/center/common/exception/ErrorCode.java +++ b/src/main/java/stanl_2/final_backend/domain/news/common/exception/NewsErrorCode.java @@ -1,4 +1,4 @@ -package stanl_2.final_backend.domain.center.common.exception; +package stanl_2.final_backend.domain.news.common.exception; import lombok.AllArgsConstructor; import lombok.Getter; @@ -6,7 +6,7 @@ @Getter @AllArgsConstructor -public enum ErrorCode { +public enum NewsErrorCode { /** * 400(Bad Request) @@ -40,7 +40,6 @@ public enum ErrorCode { */ - /** * 500(Internal Server Error) * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/common/exception/ExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/news/common/exception/NewsExceptionResponse.java similarity index 54% rename from src/main/java/stanl_2/final_backend/domain/schedule/common/exception/ExceptionResponse.java rename to src/main/java/stanl_2/final_backend/domain/news/common/exception/NewsExceptionResponse.java index c5ee4b35..771eb688 100644 --- a/src/main/java/stanl_2/final_backend/domain/schedule/common/exception/ExceptionResponse.java +++ b/src/main/java/stanl_2/final_backend/domain/news/common/exception/NewsExceptionResponse.java @@ -1,22 +1,22 @@ -package stanl_2.final_backend.domain.schedule.common.exception; +package stanl_2.final_backend.domain.news.common.exception; import lombok.Getter; import org.springframework.http.HttpStatus; @Getter -public class ExceptionResponse { +public class NewsExceptionResponse { private final Integer code; private final String msg; private final HttpStatus httpStatus; - public ExceptionResponse(ErrorCode errorCode) { + public NewsExceptionResponse(NewsErrorCode errorCode) { this.code = errorCode.getCode(); this.msg = errorCode.getMsg(); this.httpStatus = errorCode.getHttpStatus(); } - public static ExceptionResponse of(ErrorCode errorCode) { - return new ExceptionResponse(errorCode); + public static NewsExceptionResponse of(NewsErrorCode errorCode) { + return new NewsExceptionResponse(errorCode); } } diff --git a/src/main/java/stanl_2/final_backend/domain/news/common/response/NewsResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/news/common/response/NewsResponseMessage.java new file mode 100644 index 00000000..bc5f894c --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/news/common/response/NewsResponseMessage.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.news.common.response; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +public class NewsResponseMessage { + private int httpStatus; + private String msg; + private Object result; +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/news/controller/NaverNewsController.java b/src/main/java/stanl_2/final_backend/domain/news/controller/NaverNewsController.java new file mode 100644 index 00000000..6b467c5b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/news/controller/NaverNewsController.java @@ -0,0 +1,51 @@ +package stanl_2.final_backend.domain.news.controller; + +import com.fasterxml.jackson.core.JsonProcessingException; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; +import stanl_2.final_backend.domain.member.common.response.MemberResponseMessage; +import stanl_2.final_backend.domain.news.common.response.NewsResponseMessage; +import stanl_2.final_backend.domain.news.dto.NaverNewsDTO; +import stanl_2.final_backend.domain.news.service.NaverNewsService; + +import java.util.List; + +@RestController +@RequestMapping("api/v1/news") +public class NaverNewsController { + + private final NaverNewsService naverNewsService; + + @Autowired + public NaverNewsController(NaverNewsService naverNewsService) { + this.naverNewsService = naverNewsService; + } + + @Operation(summary = "자동차 뉴스 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = MemberResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/car") + public ResponseEntity getCarNews() throws JsonProcessingException { + + List result = naverNewsService.getNews(); + + return ResponseEntity.ok(NewsResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(result) + .build()); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/news/dto/NaverNewsDTO.java b/src/main/java/stanl_2/final_backend/domain/news/dto/NaverNewsDTO.java new file mode 100644 index 00000000..c109b2db --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/news/dto/NaverNewsDTO.java @@ -0,0 +1,18 @@ +package stanl_2.final_backend.domain.news.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class NaverNewsDTO { + private String title; + private String originallink; + private String link; + private String description; + private String pubDate; +} diff --git a/src/main/java/stanl_2/final_backend/domain/news/service/NaverNewsService.java b/src/main/java/stanl_2/final_backend/domain/news/service/NaverNewsService.java new file mode 100644 index 00000000..55f0948c --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/news/service/NaverNewsService.java @@ -0,0 +1,10 @@ +package stanl_2.final_backend.domain.news.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import stanl_2.final_backend.domain.news.dto.NaverNewsDTO; + +import java.util.List; + +public interface NaverNewsService { + List getNews() throws JsonProcessingException; +} diff --git a/src/main/java/stanl_2/final_backend/domain/news/service/NaverNewsServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/news/service/NaverNewsServiceImpl.java new file mode 100644 index 00000000..267eb5a3 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/news/service/NaverNewsServiceImpl.java @@ -0,0 +1,63 @@ +package stanl_2.final_backend.domain.news.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import stanl_2.final_backend.domain.news.dto.NaverNewsDTO; + +import java.util.ArrayList; +import java.util.List; + +@Service +public class NaverNewsServiceImpl implements NaverNewsService { + + @Value("${naver.client-id}") + private String clientId; + + @Value("${naver.client-secret}") + private String clientSecret; + + private final RestTemplate restTemplate; + + @Autowired + public NaverNewsServiceImpl(RestTemplate restTemplate){ + this.restTemplate = restTemplate; + } + + @Override + public List getNews() throws JsonProcessingException { + // 네이버 뉴스 검색 URL + String query = "자동차"; + String apiUrl = "https://openapi.naver.com/v1/search/news.json?query=" + query + "&display=10"; + + HttpHeaders headers = new HttpHeaders(); + headers.set("X-Naver-Client-Id", clientId); + headers.set("X-Naver-Client-Secret", clientSecret); + + HttpEntity entity = new HttpEntity<>(headers); + + // API 호출 + String response = restTemplate.exchange(apiUrl, HttpMethod.GET, entity, String.class).getBody(); + + // ObjectMapper를 사용하여 JSON을 파싱 + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode jsonNode = objectMapper.readTree(response); + + // "items" 배열을 NaverNewsDTO 리스트로 변환 + List result = new ArrayList<>(); + for (JsonNode itemNode : jsonNode.path("items")) { + NaverNewsDTO naverNewsDTO = objectMapper.treeToValue(itemNode, NaverNewsDTO.class); + result.add(naverNewsDTO); + } + + return result; + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/notices/command/application/controller/NoticeController.java b/src/main/java/stanl_2/final_backend/domain/notices/command/application/controller/NoticeController.java new file mode 100644 index 00000000..64c1cfd5 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/command/application/controller/NoticeController.java @@ -0,0 +1,136 @@ +package stanl_2.final_backend.domain.notices.command.application.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.domain.notices.command.application.dto.NoticeDeleteDTO; +import stanl_2.final_backend.domain.notices.command.application.dto.NoticeModifyDTO; +import stanl_2.final_backend.domain.notices.command.application.dto.NoticeRegistDTO; +import stanl_2.final_backend.domain.notices.command.application.service.NoticeCommandService; +import stanl_2.final_backend.domain.notices.command.domain.aggragate.entity.Notice; +import stanl_2.final_backend.domain.notices.common.response.NoticeResponseMessage; +import stanl_2.final_backend.domain.promotion.command.domain.aggregate.entity.Promotion; +import stanl_2.final_backend.domain.s3.command.domain.service.S3FileServiceImpl; + +import java.security.GeneralSecurityException; +import java.security.Principal; + +@RestController("commandNoticeController") +@RequestMapping("/api/v1/notice") +public class NoticeController { + + private final NoticeCommandService noticeCommandService; + private final AuthQueryService authQueryService; + + private final S3FileServiceImpl s3FileService; + private final NoticeModifyDTO noticeModifyDTO; + private final MemberQueryService memberQueryService; + private final ModelMapper modelMapper; + + @Autowired + public NoticeController(NoticeCommandService noticeCommandService, AuthQueryService authQueryService, S3FileServiceImpl s3FileService, NoticeModifyDTO noticeModifyDTO, + MemberQueryService memberQueryService, ModelMapper modelMapper){ + this.noticeModifyDTO = noticeModifyDTO; + this.noticeCommandService = noticeCommandService; + this.authQueryService =authQueryService; + this.s3FileService = s3FileService; + this.memberQueryService = memberQueryService; + this.modelMapper = modelMapper; + } + + @Operation(summary = "공지사항 작성") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = NoticeResponseMessage.class))}) + }) + @PostMapping(value = "") + public ResponseEntity postNotice(@RequestPart("dto") NoticeRegistDTO noticeRegistDTO, // JSON 데이터 + @RequestPart(value = "file", required = false) MultipartFile file, + Principal principal) throws GeneralSecurityException { + String memberLoginId = principal.getName(); + noticeRegistDTO.setMemberLoginId(memberLoginId); + if (file != null && !file.isEmpty()) { + noticeRegistDTO.setFileUrl(s3FileService.uploadOneFile(file)); + }else if(file==null || file.isEmpty()){ + noticeRegistDTO.setFileUrl(null); + } else { + noticeRegistDTO.setFileUrl(null); + } + noticeCommandService.registerNotice(noticeRegistDTO, principal); + return ResponseEntity.ok(NoticeResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(null) + .build()); + + } + @Operation(summary = "공지사항 수정") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = NoticeResponseMessage.class))}) + }) + @PutMapping("{noticeId}") + public ResponseEntity modifyNotice( + @PathVariable String noticeId, + @RequestPart("dto") NoticeModifyDTO noticeModifyDTO, // JSON 데이터 + @RequestPart(value = "file", required = false) MultipartFile file, + Principal principal) throws GeneralSecurityException { + String memberLoginId = principal.getName(); + String memberId = authQueryService.selectMemberIdByLoginId(memberLoginId); + memberId=memberQueryService.selectNameById(memberId); + noticeModifyDTO.setMemberId(memberId); + noticeModifyDTO.setMemberLoginId(memberLoginId); + noticeModifyDTO.setContent(noticeModifyDTO.getContent()); + Notice updateNotice = modelMapper.map(noticeModifyDTO, Notice.class); + if(noticeModifyDTO.getFileUrl()==null){ + noticeModifyDTO.setFileUrl(updateNotice.getFileUrl()); + } + if (file != null && !file.isEmpty()) { + noticeModifyDTO.setFileUrl(s3FileService.uploadOneFile(file)); + }else if(file==null || file.isEmpty()) { + noticeModifyDTO.setFileUrl(updateNotice.getFileUrl()); + } else { + noticeModifyDTO.setFileUrl(s3FileService.uploadOneFile(file)); + } + noticeCommandService.modifyNotice(noticeId,noticeModifyDTO, principal); + + return ResponseEntity.ok(NoticeResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(noticeModifyDTO) + .build()); + } + + @Operation(summary = "공지사항 삭제") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = NoticeResponseMessage.class))}) + }) + @DeleteMapping("{noticeId}") + public ResponseEntity deleteNotice(Principal principal, + @PathVariable String noticeId) { + + String memberLoginId = principal.getName(); + NoticeDeleteDTO noticeDeleteDTO = new NoticeDeleteDTO(); + noticeDeleteDTO.setMemberLoginId(memberLoginId); + noticeDeleteDTO.setNoticeId(noticeId); + + noticeCommandService.deleteNotice(noticeDeleteDTO,principal); + + return ResponseEntity.ok(NoticeResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(null) + .build()); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/notices/command/application/dto/NoticeAlarmDTO.java b/src/main/java/stanl_2/final_backend/domain/notices/command/application/dto/NoticeAlarmDTO.java new file mode 100644 index 00000000..202926ee --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/command/application/dto/NoticeAlarmDTO.java @@ -0,0 +1,26 @@ +package stanl_2.final_backend.domain.notices.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class NoticeAlarmDTO { + + private String noticeId; + + private String title; + + private String tag; + + private String classification; + + private String content; + + private String memberLoginId; + + private String memberId; + + private String adminId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/notices/command/application/dto/NoticeDeleteDTO.java b/src/main/java/stanl_2/final_backend/domain/notices/command/application/dto/NoticeDeleteDTO.java new file mode 100644 index 00000000..6ad98a1d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/command/application/dto/NoticeDeleteDTO.java @@ -0,0 +1,15 @@ +package stanl_2.final_backend.domain.notices.command.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class NoticeDeleteDTO { + private String NoticeId; + private String memberLoginId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/notices/command/application/dto/NoticeModifyDTO.java b/src/main/java/stanl_2/final_backend/domain/notices/command/application/dto/NoticeModifyDTO.java new file mode 100644 index 00000000..e91cc24e --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/command/application/dto/NoticeModifyDTO.java @@ -0,0 +1,23 @@ +package stanl_2.final_backend.domain.notices.command.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.stereotype.Component; + +@Component +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class NoticeModifyDTO { + private String noticeId; + private String title; + private String tag; + private String memberId; + private String classification; + private String content; + private String memberLoginId; + private String fileUrl; +} diff --git a/src/main/java/stanl_2/final_backend/domain/notices/command/application/dto/NoticeRegistDTO.java b/src/main/java/stanl_2/final_backend/domain/notices/command/application/dto/NoticeRegistDTO.java new file mode 100644 index 00000000..b6f42509 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/command/application/dto/NoticeRegistDTO.java @@ -0,0 +1,26 @@ +package stanl_2.final_backend.domain.notices.command.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class NoticeRegistDTO { + private String title; + + private String tag; + + private String classification; + + private String content; + + private String memberId; + + private String memberLoginId; + + private String fileUrl; +} diff --git a/src/main/java/stanl_2/final_backend/domain/notices/command/application/service/NoticeCommandService.java b/src/main/java/stanl_2/final_backend/domain/notices/command/application/service/NoticeCommandService.java new file mode 100644 index 00000000..7024d114 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/command/application/service/NoticeCommandService.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.notices.command.application.service; + +import stanl_2.final_backend.domain.notices.command.application.dto.NoticeDeleteDTO; +import stanl_2.final_backend.domain.notices.command.application.dto.NoticeModifyDTO; +import stanl_2.final_backend.domain.notices.command.application.dto.NoticeRegistDTO; + +import java.security.GeneralSecurityException; +import java.security.Principal; + +public interface NoticeCommandService { + void registerNotice(NoticeRegistDTO noticeRegistDTO, Principal principal) throws GeneralSecurityException; + + NoticeModifyDTO modifyNotice(String id, NoticeModifyDTO noticeModifyDTO,Principal principal) throws GeneralSecurityException; + + void deleteNotice(NoticeDeleteDTO noticeDeleteDTO,Principal principal); +} diff --git a/src/main/java/stanl_2/final_backend/domain/notices/command/domain/aggragate/entity/Notice.java b/src/main/java/stanl_2/final_backend/domain/notices/command/domain/aggragate/entity/Notice.java new file mode 100644 index 00000000..92943ae3 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/command/domain/aggragate/entity/Notice.java @@ -0,0 +1,79 @@ +package stanl_2.final_backend.domain.notices.command.domain.aggragate.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@Entity +@Table(name="TB_NOTICE") +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class Notice { + @Id + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name="prefix", value = "NOT") + ) + @Column(name = "NOT_ID") + private String noticeId; + + @Column(name = "NOT_TTL") + private String title; + + @Column(name = "NOT_TAG") + private String tag; + + @Column(name = "NOT_CLA") + private String classification; + + @Column(columnDefinition = "TEXT", name = "NOT_CONT") + private String content; + + @Column(name = "CREATED_AT", nullable = false, updatable = false) + private String createdAt; + + @Column(name = "UPDATED_AT", nullable = false) + private String updatedAt; + + @Column(name = "DELETED_AT") + private String deletedAt; + + @Column(name = "ACTIVE", nullable = false) + private Boolean active = true; + + @Column(name = "MEM_ID", nullable = false) + private String memberId; + + @Column(name = "FILE_URL") + private String fileUrl; + + @PrePersist + private void prePersist() { + this.createdAt = getCurrentTime(); + this.updatedAt = this.createdAt; + } + + // Update 되기 전에 실행 + @PreUpdate + private void preUpdate() { + this.updatedAt = getCurrentTime(); + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + + +} diff --git a/src/main/java/stanl_2/final_backend/domain/notices/command/domain/repository/NoticeRepository.java b/src/main/java/stanl_2/final_backend/domain/notices/command/domain/repository/NoticeRepository.java new file mode 100644 index 00000000..a2c2856d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/command/domain/repository/NoticeRepository.java @@ -0,0 +1,17 @@ +package stanl_2.final_backend.domain.notices.command.domain.repository; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import stanl_2.final_backend.domain.notices.command.domain.aggragate.entity.Notice; + +import java.util.Optional; + +@Repository +public interface NoticeRepository extends JpaRepository { + Page findAll(Pageable pageable); + + Optional findByNoticeId(String noticeId); + +} diff --git a/src/main/java/stanl_2/final_backend/domain/notices/command/domain/service/NoticeCommandServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/notices/command/domain/service/NoticeCommandServiceImpl.java new file mode 100644 index 00000000..c7e4de97 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/command/domain/service/NoticeCommandServiceImpl.java @@ -0,0 +1,143 @@ +package stanl_2.final_backend.domain.notices.command.domain.service; + +import lombok.extern.slf4j.Slf4j; +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.alarm.command.application.service.AlarmCommandService; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.domain.notices.command.application.dto.NoticeAlarmDTO; +import stanl_2.final_backend.domain.notices.command.application.dto.NoticeDeleteDTO; +import stanl_2.final_backend.domain.notices.command.application.dto.NoticeModifyDTO; +import stanl_2.final_backend.domain.notices.command.application.dto.NoticeRegistDTO; +import stanl_2.final_backend.domain.notices.command.application.service.NoticeCommandService; +import stanl_2.final_backend.domain.notices.command.domain.aggragate.entity.Notice; +import stanl_2.final_backend.domain.notices.command.domain.repository.NoticeRepository; +import stanl_2.final_backend.domain.notices.common.exception.NoticeCommonException; +import stanl_2.final_backend.domain.notices.common.exception.NoticeErrorCode; +import stanl_2.final_backend.global.redis.RedisService; + +import java.security.GeneralSecurityException; +import java.security.Principal; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +import static org.springframework.transaction.support.TransactionSynchronizationManager.isCurrentTransactionReadOnly; +@Slf4j +@Service("commandNoticeService") +public class NoticeCommandServiceImpl implements NoticeCommandService { + + private final NoticeRepository noticeRepository; + + private final RedisService redisService; + private final AuthQueryService authQueryService; + + private final ModelMapper modelMapper; + private final AlarmCommandService alarmCommandService; + private final MemberQueryService memberQueryService; + + @Autowired + public NoticeCommandServiceImpl(NoticeRepository noticeRepository, ModelMapper modelMapper, + AuthQueryService authQueryService, AlarmCommandService alarmCommandService, + RedisService redisService, MemberQueryService memberQueryService) { + this.noticeRepository = noticeRepository; + this.modelMapper = modelMapper; + this.authQueryService =authQueryService; + this.alarmCommandService = alarmCommandService; + this.redisService = redisService; + this.memberQueryService = memberQueryService; + } + + private String getCurrentTimestamp() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + + @Override + @Transactional(readOnly = false) + public void registerNotice(NoticeRegistDTO noticeRegistDTO, + Principal principal) throws GeneralSecurityException { + System.out.println("[Before Cache Clear] Transaction ReadOnly: " + isCurrentTransactionReadOnly()); + redisService.clearNoticeCache(); + String memberId = authQueryService.selectMemberIdByLoginId(noticeRegistDTO.getMemberLoginId()); + memberId=memberQueryService.selectNameById(memberId); + noticeRegistDTO.setMemberId(memberId); + try { + Notice notice = modelMapper.map(noticeRegistDTO, Notice.class); + Notice newNotice = noticeRepository.save(notice); + NoticeAlarmDTO noticeAlarmDTO = modelMapper.map(newNotice, NoticeAlarmDTO.class); + alarmCommandService.sendNoticeAlarm(noticeAlarmDTO); + } catch (DataIntegrityViolationException e){ + // DB 무결정 제약 조건 (NOT NULL, UNIQUE) 위반 + throw new NoticeCommonException(NoticeErrorCode.DATA_INTEGRITY_VIOLATION); + } catch (Exception e) { + // 서버 오류 + throw new NoticeCommonException(NoticeErrorCode.INTERNAL_SERVER_ERROR); + } + } + @Override + public NoticeModifyDTO modifyNotice(String id, NoticeModifyDTO noticeModifyDTO,Principal principal) throws GeneralSecurityException { + + redisService.clearNoticeCache(); + String memberId = authQueryService.selectMemberIdByLoginId(noticeModifyDTO.getMemberLoginId()); + memberId=memberQueryService.selectNameById(memberId); + + Notice notice = noticeRepository.findById(id) + .orElseThrow(() -> new NoticeCommonException(NoticeErrorCode.NOTICE_NOT_FOUND)); + if(!notice.getMemberId().equals(memberId)){ + // 권한 오류 + throw new NoticeCommonException(NoticeErrorCode.AUTHORIZATION_VIOLATION); + } + try { + Notice updateNotice = modelMapper.map(noticeModifyDTO, Notice.class); + updateNotice.setNoticeId(notice.getNoticeId()); + updateNotice.setMemberId(notice.getMemberId()); + updateNotice.setCreatedAt(notice.getCreatedAt()); + updateNotice.setActive(notice.getActive()); + + noticeRepository.save(updateNotice); + + NoticeModifyDTO noticeModify = modelMapper.map(updateNotice,NoticeModifyDTO.class); + + return noticeModify; + } catch (DataIntegrityViolationException e) { + // 데이터 무결성 위반 예외 처리 + throw new NoticeCommonException(NoticeErrorCode.DATA_INTEGRITY_VIOLATION); + } catch (Exception e) { + // 서버 오류 + throw new NoticeCommonException(NoticeErrorCode.INTERNAL_SERVER_ERROR); + } + } + + + @Override + public void deleteNotice(NoticeDeleteDTO noticeDeleteDTO, Principal principal) { + redisService.clearNoticeCache(); + String memberId = principal.getName(); + + Notice notice = noticeRepository.findByNoticeId(noticeDeleteDTO.getNoticeId()) + .orElseThrow(() -> new NoticeCommonException(NoticeErrorCode.NOTICE_NOT_FOUND)); + + if(!memberId.equals(memberId)){ + // 권한 오류 + throw new NoticeCommonException(NoticeErrorCode.AUTHORIZATION_VIOLATION); + } + + notice.setActive(false); + notice.setDeletedAt(getCurrentTimestamp()); + + try { + noticeRepository.save(notice); + } catch (DataIntegrityViolationException e) { + // 데이터 무결성 위반 예외 처리 + throw new NoticeCommonException(NoticeErrorCode.DATA_INTEGRITY_VIOLATION); + } catch (Exception e) { + // 서버 오류 + throw new NoticeCommonException(NoticeErrorCode.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/notices/common/exception/NoticeCommonException.java b/src/main/java/stanl_2/final_backend/domain/notices/common/exception/NoticeCommonException.java new file mode 100644 index 00000000..9c384472 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/common/exception/NoticeCommonException.java @@ -0,0 +1,17 @@ +package stanl_2.final_backend.domain.notices.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import stanl_2.final_backend.domain.A_sample.common.exception.SampleErrorCode; + +@Getter +@RequiredArgsConstructor +public class NoticeCommonException extends RuntimeException { + private final NoticeErrorCode noticeErrorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.noticeErrorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/notices/common/exception/NoticeErrorCode.java b/src/main/java/stanl_2/final_backend/domain/notices/common/exception/NoticeErrorCode.java new file mode 100644 index 00000000..0f833657 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/common/exception/NoticeErrorCode.java @@ -0,0 +1,56 @@ +package stanl_2.final_backend.domain.notices.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum NoticeErrorCode { + + /** + * 400(Bad Request) + * 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. + */ + + + + /** + * 401(Unauthorized) + * 비록 HTTP 표준에서는 "미승인(unauthorized)"를 명확히 하고 있지만, + * 의미상 이 응답은 "비인증(unauthenticated)"을 의미합니다. + * 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로를 인증해야 합니다. + */ + + + /** + * 403(Forbidden) + * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. + * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. + */ + + + + /** + * 404(Not Found) + * 서버는 요청받은 리소스를 찾을 수 없습니다. 브라우저에서는 알려지지 않은 URL을 의미합니다. + * 이것은 API에서 종점은 적절하지만 리소스 자체는 존재하지 않음을 의미할 수도 있습니다. + * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. + * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. + */ + NOTICE_NOT_FOUND(404001, HttpStatus.NOT_FOUND, "notice 데이터를 찾지 못했습니다"), + + DATA_INTEGRITY_VIOLATION(40402, HttpStatus.BAD_REQUEST, "데이터 무결 위반하였습니다."), + + AUTHORIZATION_VIOLATION(40403, HttpStatus.BAD_REQUEST, "접근 권한이 없습니다."), + + /** + * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. + */ + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); + + + private final Integer code; + private final HttpStatus httpStatus; + private final String msg; +} diff --git a/src/main/java/stanl_2/final_backend/domain/notices/common/exception/NoticeExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/notices/common/exception/NoticeExceptionResponse.java new file mode 100644 index 00000000..30ac7c4e --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/common/exception/NoticeExceptionResponse.java @@ -0,0 +1,4 @@ +package stanl_2.final_backend.domain.notices.common.exception; + +public class NoticeExceptionResponse { +} diff --git a/src/main/java/stanl_2/final_backend/domain/notices/common/response/NoticeResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/notices/common/response/NoticeResponseMessage.java new file mode 100644 index 00000000..ae17eed4 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/common/response/NoticeResponseMessage.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.notices.common.response; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +public class NoticeResponseMessage { + private int httpStatus; + private String msg; + private Object result; +} diff --git a/src/main/java/stanl_2/final_backend/domain/notices/common/util/RequestList.java b/src/main/java/stanl_2/final_backend/domain/notices/common/util/RequestList.java new file mode 100644 index 00000000..b4030203 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/common/util/RequestList.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.notices.common.util; + +import lombok.*; +import org.springframework.data.domain.Pageable; + +@Builder +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class RequestList { + private T data; + private Pageable pageable; +} diff --git a/src/main/java/stanl_2/final_backend/domain/notices/query/config/MyBatisConfiguration.java b/src/main/java/stanl_2/final_backend/domain/notices/query/config/MyBatisConfiguration.java new file mode 100644 index 00000000..f420b5cc --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/query/config/MyBatisConfiguration.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.notices.query.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Configuration; + +@Configuration("noticeMybatisConfiguration") +@MapperScan(basePackages = "stanl_2.final_backend.domain.notices.query.repository") +public class MyBatisConfiguration { +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/notices/query/controller/NoticeController.java b/src/main/java/stanl_2/final_backend/domain/notices/query/controller/NoticeController.java new file mode 100644 index 00000000..0ede6fd5 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/query/controller/NoticeController.java @@ -0,0 +1,87 @@ +package stanl_2.final_backend.domain.notices.query.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.domain.notices.common.response.NoticeResponseMessage; +import stanl_2.final_backend.domain.notices.query.dto.NoticeDTO; +import stanl_2.final_backend.domain.notices.query.dto.SearchDTO; +import stanl_2.final_backend.domain.notices.query.service.NoticeQueryService; + +import java.security.GeneralSecurityException; +import java.security.Principal; + + +@RestController("queryNoticeController") +@RequestMapping("/api/v1/notice") +public class NoticeController { + private final NoticeQueryService noticeQueryService; + private final AuthQueryService authQueryService; + private final MemberQueryService memberQueryService; + @Autowired + public NoticeController(NoticeQueryService noticeQueryService, + AuthQueryService authQueryService, + MemberQueryService memberQueryService) { + this.noticeQueryService = noticeQueryService; + this.authQueryService = authQueryService; + this.memberQueryService = memberQueryService; + } + + @Operation(summary = "공지사항 조건별 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = NoticeResponseMessage.class))}) + }) + @GetMapping() + public ResponseEntity> getNotices( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(required = false) String title, + @RequestParam(required = false) String tag, + @RequestParam(required = false) String memberId, + @RequestParam(required = false) String classification, + @RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate, + Principal principal + ) throws GeneralSecurityException { + Pageable pageable = PageRequest.of(page, size); + SearchDTO searchDTO = new SearchDTO(title, tag, memberId, classification, startDate, endDate); + + Page noticeDTOPage = noticeQueryService.findNotices(pageable, searchDTO); + + return ResponseEntity.ok(noticeDTOPage); + } + + @Operation(summary = "공지사항 Id로 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = NoticeResponseMessage.class))}) + }) + @GetMapping("{noticeId}") + public ResponseEntity getNotice(@PathVariable String noticeId){ + NoticeDTO noticeDTO = noticeQueryService.findNotice(noticeId); + return ResponseEntity.ok(noticeDTO); + } + @Operation(summary = "공지사항 엑셀 다운 테스트") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "공지사항 엑셀 다운 테스트 성공", + content = {@Content(schema = @Schema(implementation = NoticeResponseMessage.class))}), + }) + @GetMapping("/excel") + public void exportNotice(HttpServletResponse response){ + + noticeQueryService.exportNoticesToExcel(response); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/notices/query/dto/NoticeDTO.java b/src/main/java/stanl_2/final_backend/domain/notices/query/dto/NoticeDTO.java new file mode 100644 index 00000000..ea657c5d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/query/dto/NoticeDTO.java @@ -0,0 +1,36 @@ +package stanl_2.final_backend.domain.notices.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import stanl_2.final_backend.domain.notices.command.domain.aggragate.entity.Notice; +import stanl_2.final_backend.domain.notices.query.repository.NoticeMapper; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class NoticeDTO { + private String noticeId; + + private String title; + + private String tag; + + private String classification; + + private String content; + + private String createdAt; + + private String updatedAt; + + private String deletedAt; + + private Boolean active; + + private String memberId; + + private String fileUrl; +} diff --git a/src/main/java/stanl_2/final_backend/domain/notices/query/dto/NoticeExcelDownload.java b/src/main/java/stanl_2/final_backend/domain/notices/query/dto/NoticeExcelDownload.java new file mode 100644 index 00000000..1d9b2028 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/query/dto/NoticeExcelDownload.java @@ -0,0 +1,30 @@ +package stanl_2.final_backend.domain.notices.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import stanl_2.final_backend.global.excel.ExcelColumnName; + +@Getter +@AllArgsConstructor +public class NoticeExcelDownload { + @ExcelColumnName(name = "제목") + private String title; + + @ExcelColumnName(name = "태그") + private String tag; + + @ExcelColumnName(name = "분류") + private String classification; + + @ExcelColumnName(name = "내용") + private String content; + + @ExcelColumnName(name = "생성일자") + private String createdAt; + + @ExcelColumnName(name = "수정일자") + private String updatedAt; + + @ExcelColumnName(name = "작성자") + private String memberId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/notices/query/dto/PaginationDTO.java b/src/main/java/stanl_2/final_backend/domain/notices/query/dto/PaginationDTO.java new file mode 100644 index 00000000..1d4a6708 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/query/dto/PaginationDTO.java @@ -0,0 +1,15 @@ +package stanl_2.final_backend.domain.notices.query.dto; + +import lombok.*; + +import java.util.List; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class PaginationDTO { + private List data; + private int totalPages; + private long totalElements; +} diff --git a/src/main/java/stanl_2/final_backend/domain/notices/query/dto/SearchDTO.java b/src/main/java/stanl_2/final_backend/domain/notices/query/dto/SearchDTO.java new file mode 100644 index 00000000..d6384cc5 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/query/dto/SearchDTO.java @@ -0,0 +1,32 @@ +package stanl_2.final_backend.domain.notices.query.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@ToString +public class SearchDTO { + private String noticeId; + private String title; + private String tag; + private String content; + private String active; + private String createdAt; + private String updatedAt; + private String deletedAt; + private String memberId; + private String classification; + private String startDate; + private String endDate; + + public SearchDTO(String title, String tag, String memberId, String classification, String startDate, String endDate) { + this.title = title; + this.tag = tag; + this.memberId = memberId; + this.classification = classification; + this.startDate = startDate; + this.endDate = endDate; + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/notices/query/repository/NoticeMapper.java b/src/main/java/stanl_2/final_backend/domain/notices/query/repository/NoticeMapper.java new file mode 100644 index 00000000..7da0713d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/query/repository/NoticeMapper.java @@ -0,0 +1,32 @@ +package stanl_2.final_backend.domain.notices.query.repository; + +import org.apache.ibatis.annotations.Mapper; +import org.springframework.data.repository.query.Param; +import stanl_2.final_backend.domain.A_sample.query.dto.SampleExcelDownload; +import stanl_2.final_backend.domain.notices.query.dto.NoticeDTO; +import stanl_2.final_backend.domain.notices.query.dto.NoticeExcelDownload; +import stanl_2.final_backend.domain.notices.query.dto.SearchDTO; + +import java.util.List; + + +@Mapper +public interface NoticeMapper { + List findAllNotices(@Param("offset") int offset, @Param("size") int size); + + int findNoticeCount(); + + List findNotices( + @Param("offset") int offset, + @Param("size") int size, + @Param("searchDTO") SearchDTO searchDTO + ); + +// int findNoticesCount(@Param("searchDTO") SearchDTO searchDTO); + NoticeDTO findNotice(@Param("noticeId") String noticeId); + + List findNoticesForExcel(); +} + + + diff --git a/src/main/java/stanl_2/final_backend/domain/notices/query/service/NoticeQueryService.java b/src/main/java/stanl_2/final_backend/domain/notices/query/service/NoticeQueryService.java new file mode 100644 index 00000000..85a7357d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/query/service/NoticeQueryService.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.notices.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import stanl_2.final_backend.domain.notices.query.dto.NoticeDTO; +import stanl_2.final_backend.domain.notices.query.dto.SearchDTO; + + +public interface NoticeQueryService { + + Page findNotices(Pageable pageable, SearchDTO searchDTO); + NoticeDTO findNotice(String noticeId); + void exportNoticesToExcel(HttpServletResponse response); + +} diff --git a/src/main/java/stanl_2/final_backend/domain/notices/query/service/NoticeQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/notices/query/service/NoticeQueryServiceImpl.java new file mode 100644 index 00000000..4ba21318 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/notices/query/service/NoticeQueryServiceImpl.java @@ -0,0 +1,112 @@ +package stanl_2.final_backend.domain.notices.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.domain.notices.query.dto.NoticeDTO; +import stanl_2.final_backend.domain.notices.query.dto.NoticeExcelDownload; +import stanl_2.final_backend.domain.notices.query.dto.SearchDTO; +import stanl_2.final_backend.domain.notices.query.repository.NoticeMapper; +import stanl_2.final_backend.domain.sales_history.common.exception.SalesHistoryCommonException; +import stanl_2.final_backend.domain.sales_history.common.exception.SalesHistoryErrorCode; +import stanl_2.final_backend.global.excel.ExcelUtilsV1; +import stanl_2.final_backend.global.redis.RedisService; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.util.List; + +import static org.springframework.transaction.support.TransactionSynchronizationManager.isCurrentTransactionReadOnly; +/* 설명. 조회의 경우 readOnly= true 를 설정해 readOnlyDB에 접근을 하도록 만든다. + "Transaction ReadOnly: " + isCurrentTransactionReadOnly();를 통해 + 현재의 트랜젝션이 ReadOnly 상태인지 확인할 수 있다. + (readOnly=false)나 default로 설정된 메소드를 호출할 경우 readOnly설정이 풀릴 수도 있음 + 이런 경우, Controller의 api에 (readOnly=true)로 설정해 본다. +*/ + +@Transactional(readOnly = true) +@Slf4j +@Service("queryNoticeServiceImpl") +public class NoticeQueryServiceImpl implements NoticeQueryService { + private final NoticeMapper noticeMapper; + private final RedisTemplate redisTemplate; + private final RedisService redisService; + private final ExcelUtilsV1 excelUtilsV1; + private final MemberQueryService memberQueryService; + + private final DataSource dataSource; + @Autowired + public NoticeQueryServiceImpl(NoticeMapper noticeMapper, RedisTemplate redisTemplate, RedisService redisService, + ExcelUtilsV1 excelUtilsV1, MemberQueryService memberQueryService, DataSource dataSource) { + this.noticeMapper = noticeMapper; + this.redisTemplate = redisTemplate; + this.redisService =redisService; + this.excelUtilsV1 =excelUtilsV1; + this.memberQueryService =memberQueryService; + this.dataSource = dataSource; + } + + private String getDatabaseUrl() { + try (Connection connection = dataSource.getConnection()) { + DatabaseMetaData metaData = connection.getMetaData(); + return metaData.getURL(); + } catch (Exception e) { + throw new RuntimeException("DB URL 조회 실패", e); + } + } + + @Transactional(readOnly = true) + @Override + public Page findNotices(Pageable pageable, SearchDTO searchDTO) { + log.info("readOnly = true 적용 시 DB URL: " + getDatabaseUrl()); + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + System.out.println("3.Transaction ReadOnly: " + isCurrentTransactionReadOnly()); + String cacheKey = "NoticeCache::notices::offset=" + offset + "::size=" + size + + "::title=" + searchDTO.getTitle()+ "::tag=" + searchDTO.getTag() + +"::memberid=" + searchDTO.getMemberId()+ "::classification=" + searchDTO.getClassification() + + "::startDate=" + searchDTO.getStartDate()+ "::endDate=" + searchDTO.getEndDate(); + List notices = (List) redisTemplate.opsForValue().get(cacheKey); + if (notices == null) { + log.info("데이터베이스에서 데이터 조회 중..."); + notices = noticeMapper.findNotices(offset, size, searchDTO); + if (notices != null && !notices.isEmpty()) { // 데이터가 있을 때만 캐싱 + redisService.setKeyWithTTL(cacheKey, notices, 30 * 60); // 캐싱 시 동일 키 사용 + } + } else { + log.info("캐시에서 데이터 조회 중..."); + } + notices.forEach(notice -> { + try { + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.MEMBER_NOT_FOUND); + } + }); + int totalElements = noticeMapper.findNoticeCount(); // 총 개수 조회 + return new PageImpl<>(notices, pageable, totalElements); + } + @Transactional + @Override + public NoticeDTO findNotice(String noticeId) { + NoticeDTO notice = noticeMapper.findNotice(noticeId); + log.info("Default DB URL: " + getDatabaseUrl()); + return notice; + } + @Transactional(readOnly = true) + @Override + public void exportNoticesToExcel(HttpServletResponse response) { + List noticeList = noticeMapper.findNoticesForExcel(); + + excelUtilsV1.download(NoticeExcelDownload.class, noticeList, "noticeExcel", response); + } + + +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/operation_test.java b/src/main/java/stanl_2/final_backend/domain/operation_test.java deleted file mode 100644 index 704a8918..00000000 --- a/src/main/java/stanl_2/final_backend/domain/operation_test.java +++ /dev/null @@ -1,13 +0,0 @@ -package stanl_2.final_backend.domain; - - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class operation_test { - @GetMapping("health") - public String healthCheck() { - return "CI/CD 배포 성공!!"; - } -} diff --git a/src/main/java/stanl_2/final_backend/domain/order/command/application/controller/OrderController.java b/src/main/java/stanl_2/final_backend/domain/order/command/application/controller/OrderController.java new file mode 100644 index 00000000..636afeb7 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/order/command/application/controller/OrderController.java @@ -0,0 +1,112 @@ +package stanl_2.final_backend.domain.order.command.application.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stanl_2.final_backend.domain.order.command.application.dto.OrderModifyDTO; +import stanl_2.final_backend.domain.order.command.application.dto.OrderRegistDTO; +import stanl_2.final_backend.domain.order.command.application.dto.OrderStatusModifyDTO; +import stanl_2.final_backend.domain.order.command.application.service.OrderCommandService; +import stanl_2.final_backend.domain.order.common.response.OrderResponseMessage; + +import java.security.GeneralSecurityException; +import java.security.Principal; + +@RestController("OrderCommandController") +@RequestMapping("/api/v1/order") +public class OrderController { + + private final OrderCommandService orderCommandService; + + @Autowired + public OrderController(OrderCommandService orderCommandService) { + this.orderCommandService = orderCommandService; + } + + @Operation(summary = "수주서 등록(영업사원)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "수주서 등록 성공", + content = {@Content(schema = @Schema(implementation = OrderResponseMessage.class))}) + }) + @PostMapping("") + public ResponseEntity postOrder(@RequestBody OrderRegistDTO orderRegistDTO, + Principal principal) throws GeneralSecurityException { + + orderRegistDTO.setMemberId(principal.getName()); + orderCommandService.registerOrder(orderRegistDTO); + + return ResponseEntity.ok(OrderResponseMessage.builder() + .httpStatus(200) + .msg("수주서가 성공적으로 등록되었습니다.") + .result(null) + .build()); + } + + @Operation(summary = "수주서 수정(영업사원)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "수주서 수정 성공", + content = {@Content(schema = @Schema(implementation = OrderResponseMessage.class))}) + }) + @PutMapping("{orderId}") + public ResponseEntity putOrder(@PathVariable String orderId, + @RequestBody OrderModifyDTO orderModifyDTO, + Principal principal) throws GeneralSecurityException { + + orderModifyDTO.setOrderId(orderId); + orderModifyDTO.setMemberId(principal.getName()); + OrderModifyDTO orderModifyResponseDTO = orderCommandService.modifyOrder(orderModifyDTO); + + return ResponseEntity.ok(OrderResponseMessage.builder() + .httpStatus(200) + .msg("수주서가 성공적으로 수정되었습니다.") + .result(orderModifyResponseDTO) + .build()); + } + + @Operation(summary = "수주서 삭제(영업사원)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "수주서 삭제 성공", + content = {@Content(schema = @Schema(implementation = OrderResponseMessage.class))}) + }) + @DeleteMapping("{orderId}") + public ResponseEntity deleteTest(@PathVariable("orderId") String orderId, + Principal principal) { + + String loginId = principal.getName(); + orderCommandService.deleteOrder(orderId, loginId); + + return ResponseEntity.ok(OrderResponseMessage.builder() + .httpStatus(200) + .msg("수주서를 성공적으로 삭제하였습니다.") + .result(null) + .build()); + } + + @Operation(summary = "수주서 승인상태 변경(영업관리자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "수주서 승인상태 변경 성공", + content = {@Content(schema = @Schema(implementation = OrderResponseMessage.class))}) + }) + @PutMapping("status/{orderId}") + public ResponseEntity putOrderStatus (@PathVariable String orderId, + @RequestBody OrderStatusModifyDTO orderStatusModifyDTO, + Principal principal) throws GeneralSecurityException { + + String adminLoginId = principal.getName(); + orderStatusModifyDTO.setOrderId(orderId); + orderStatusModifyDTO.setAdminId(adminLoginId); + + orderCommandService.modifyOrderStatus(orderStatusModifyDTO); + + return ResponseEntity.ok(OrderResponseMessage.builder() + .httpStatus(200) + .msg("수주서 승인 상태가 성공적으로 변경되었습니다.") + .result(null) + .build()); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/order/command/application/dto/OrderAlarmDTO.java b/src/main/java/stanl_2/final_backend/domain/order/command/application/dto/OrderAlarmDTO.java new file mode 100644 index 00000000..4797c8a6 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/order/command/application/dto/OrderAlarmDTO.java @@ -0,0 +1,18 @@ +package stanl_2.final_backend.domain.order.command.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class OrderAlarmDTO { + + private String orderId; + private String title; + private String memberId; + private String adminId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/order/command/application/dto/OrderModifyDTO.java b/src/main/java/stanl_2/final_backend/domain/order/command/application/dto/OrderModifyDTO.java new file mode 100644 index 00000000..038cdd82 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/order/command/application/dto/OrderModifyDTO.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.order.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class OrderModifyDTO { + private String orderId; + private String title; + private String content; + private String contractId; + private String memberId; + private String centerId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/order/command/application/dto/OrderRegistDTO.java b/src/main/java/stanl_2/final_backend/domain/order/command/application/dto/OrderRegistDTO.java new file mode 100644 index 00000000..986ea09d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/order/command/application/dto/OrderRegistDTO.java @@ -0,0 +1,15 @@ +package stanl_2.final_backend.domain.order.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class OrderRegistDTO { + private String title; + private String content; + private String contractId; + private String memberId; + private String centerId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/order/command/application/dto/OrderStatusModifyDTO.java b/src/main/java/stanl_2/final_backend/domain/order/command/application/dto/OrderStatusModifyDTO.java new file mode 100644 index 00000000..f3311c09 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/order/command/application/dto/OrderStatusModifyDTO.java @@ -0,0 +1,13 @@ +package stanl_2.final_backend.domain.order.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class OrderStatusModifyDTO { + private String orderId; + private String status; + private String adminId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/order/command/application/service/OrderCommandService.java b/src/main/java/stanl_2/final_backend/domain/order/command/application/service/OrderCommandService.java new file mode 100644 index 00000000..39b6dd01 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/order/command/application/service/OrderCommandService.java @@ -0,0 +1,17 @@ +package stanl_2.final_backend.domain.order.command.application.service; + +import stanl_2.final_backend.domain.order.command.application.dto.OrderModifyDTO; +import stanl_2.final_backend.domain.order.command.application.dto.OrderRegistDTO; +import stanl_2.final_backend.domain.order.command.application.dto.OrderStatusModifyDTO; + +import java.security.GeneralSecurityException; + +public interface OrderCommandService { + void registerOrder(OrderRegistDTO orderRegistDTO) throws GeneralSecurityException; + + OrderModifyDTO modifyOrder(OrderModifyDTO orderModifyDTO) throws GeneralSecurityException; + + void deleteOrder(String orderId, String loginId); + + void modifyOrderStatus(OrderStatusModifyDTO orderStatusModifyDTO) throws GeneralSecurityException; +} diff --git a/src/main/java/stanl_2/final_backend/domain/order/command/domain/aggregate/entity/Order.java b/src/main/java/stanl_2/final_backend/domain/order/command/domain/aggregate/entity/Order.java new file mode 100644 index 00000000..089fab83 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/order/command/domain/aggregate/entity/Order.java @@ -0,0 +1,83 @@ +package stanl_2.final_backend.domain.order.command.domain.aggregate.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "TB_ORDER") +public class Order { + + @Id + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name = "prefix", value = "ORD") + ) + @Column(name = "ORD_ID") + private String orderId; + + @Column(name = "ORD_TTL", nullable = false) + private String title; + + @Lob + @Column(name = "ORD_CONT", nullable = false, columnDefinition = "TEXT") + private String content; + + @Column(name = "ACTIVE", nullable = false) + private Boolean active = true; + + @Column(name = "CREATED_AT", nullable = false, updatable = false) + private String createdAt; + + @Column(name = "UPDATED_AT", nullable = false) + private String updatedAt; + + @Column(name = "DELETED_AT") + private String deletedAt; + + @Column(name = "ORD_STAT", nullable = false) + private String status = "WAIT"; + + @Column(name = "CONR_ID", nullable = false) + private String contractId; + + @Column(name = "ADMIN_ID") + private String adminId; + + @Column(name = "MEM_ID", nullable = false) + private String memberId; + + @Column(name = "CENT_ID", nullable = false) + private String centerId; + + // Insert 되기 전에 실행 + @PrePersist + private void prePersist() { + this.createdAt = getCurrentTime(); + this.updatedAt = this.createdAt; + } + + // Update 되기 전에 실행 + @PreUpdate + private void preUpdate() { + this.updatedAt = getCurrentTime(); + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/order/command/domain/repository/OrderRepository.java b/src/main/java/stanl_2/final_backend/domain/order/command/domain/repository/OrderRepository.java new file mode 100644 index 00000000..4430164d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/order/command/domain/repository/OrderRepository.java @@ -0,0 +1,10 @@ +package stanl_2.final_backend.domain.order.command.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import stanl_2.final_backend.domain.order.command.domain.aggregate.entity.Order; + +public interface OrderRepository extends JpaRepository { + Order findByOrderIdAndMemberId(String orderId, String memberId); + + Order findByOrderId(String orderId); +} diff --git a/src/main/java/stanl_2/final_backend/domain/order/command/domain/service/OrderCommandServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/order/command/domain/service/OrderCommandServiceImpl.java new file mode 100644 index 00000000..b34735e5 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/order/command/domain/service/OrderCommandServiceImpl.java @@ -0,0 +1,141 @@ +package stanl_2.final_backend.domain.order.command.domain.service; + +import org.apache.commons.lang3.StringEscapeUtils; +import org.modelmapper.ModelMapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.alarm.command.application.service.AlarmCommandService; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.domain.order.command.application.dto.OrderAlarmDTO; +import stanl_2.final_backend.domain.order.command.application.dto.OrderModifyDTO; +import stanl_2.final_backend.domain.order.command.application.dto.OrderRegistDTO; +import stanl_2.final_backend.domain.order.command.application.dto.OrderStatusModifyDTO; +import stanl_2.final_backend.domain.order.command.application.service.OrderCommandService; +import stanl_2.final_backend.domain.order.command.domain.aggregate.entity.Order; +import stanl_2.final_backend.domain.order.command.domain.repository.OrderRepository; +import stanl_2.final_backend.domain.order.common.exception.OrderCommonException; +import stanl_2.final_backend.domain.order.common.exception.OrderErrorCode; +import stanl_2.final_backend.domain.s3.command.application.service.S3FileService; + +import java.security.GeneralSecurityException; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@Service +public class OrderCommandServiceImpl implements OrderCommandService { + + private final OrderRepository orderRepository; + private final AuthQueryService authQueryService; + private final ModelMapper modelMapper; + private final S3FileService s3FileService; + private final AlarmCommandService alarmCommandService; + private final MemberQueryService memberQueryService; + + public OrderCommandServiceImpl(OrderRepository orderRepository, AuthQueryService authQueryService, ModelMapper modelMapper, + S3FileService s3FileService , AlarmCommandService alarmCommandService, MemberQueryService memberQueryService) { + this.orderRepository = orderRepository; + this.authQueryService = authQueryService; + this.modelMapper = modelMapper; + this.s3FileService = s3FileService; + this.alarmCommandService = alarmCommandService; + this.memberQueryService = memberQueryService; + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + + @Override + @Transactional + public void registerOrder(OrderRegistDTO orderRegistDTO) throws GeneralSecurityException { + + String memberId = authQueryService.selectMemberIdByLoginId(orderRegistDTO.getMemberId()); + String centerId = memberQueryService.selectMemberInfo(orderRegistDTO.getMemberId()).getCenterId(); + + String unescapedHtml = StringEscapeUtils.unescapeJson(orderRegistDTO.getContent()); + String updatedS3Url = s3FileService.uploadHtml(unescapedHtml, orderRegistDTO.getTitle()); + + orderRegistDTO.setMemberId(memberId); + orderRegistDTO.setContent(updatedS3Url); + orderRegistDTO.setCenterId(centerId); + + Order order = modelMapper.map(orderRegistDTO, Order.class); + + orderRepository.save(order); + } + + @Override + @Transactional + public OrderModifyDTO modifyOrder(OrderModifyDTO orderModifyDTO) throws GeneralSecurityException { + + String memberId = authQueryService.selectMemberIdByLoginId(orderModifyDTO.getMemberId()); + String centerId = memberQueryService.selectMemberInfo(orderModifyDTO.getMemberId()).getCenterId(); + orderModifyDTO.setMemberId(memberId); + orderModifyDTO.setCenterId(centerId); + + Order order = orderRepository.findByOrderIdAndMemberId(orderModifyDTO.getOrderId(), memberId); + + if (order == null) { + throw new OrderCommonException(OrderErrorCode.ORDER_NOT_FOUND); + } + + String unescapedHtml = StringEscapeUtils.unescapeJson(orderModifyDTO.getContent()); + String updatedS3Url = s3FileService.uploadHtml(unescapedHtml, orderModifyDTO.getTitle()); + + orderModifyDTO.setContent(updatedS3Url); + + Order updateOrder = modelMapper.map(orderModifyDTO, Order.class); + updateOrder.setCreatedAt(order.getCreatedAt()); + updateOrder.setUpdatedAt(order.getUpdatedAt()); + updateOrder.setStatus(order.getStatus()); + updateOrder.setActive(order.getActive()); + + orderRepository.save(updateOrder); + + OrderModifyDTO orderModifyResponse = modelMapper.map(updateOrder, OrderModifyDTO.class); + + return orderModifyResponse; + } + + @Override + @Transactional + public void deleteOrder(String id, String loginId) { + + String memberId = authQueryService.selectMemberIdByLoginId(loginId); + + Order order = orderRepository.findByOrderIdAndMemberId(id, memberId); + + if (order == null) { + throw new OrderCommonException(OrderErrorCode.ORDER_NOT_FOUND); + } + + order.setActive(false); + order.setDeletedAt(getCurrentTime()); + + orderRepository.save(order); + } + + @Override + @Transactional + public void modifyOrderStatus(OrderStatusModifyDTO orderStatusModifyDTO) throws GeneralSecurityException { + + String adminId = authQueryService.selectMemberIdByLoginId(orderStatusModifyDTO.getAdminId()); + Order order = orderRepository.findByOrderId(orderStatusModifyDTO.getOrderId()); + if (order == null) { + throw new OrderCommonException(OrderErrorCode.ORDER_NOT_FOUND); + } + + order.setStatus(orderStatusModifyDTO.getStatus()); + order.setAdminId(adminId); + + orderRepository.save(order); + + OrderAlarmDTO orderAlarmDTO = new OrderAlarmDTO(order.getOrderId(), order.getTitle(), order.getMemberId(), + order.getAdminId()); + + alarmCommandService.sendOrderAlarm(orderAlarmDTO); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/order/common/exception/OrderCommonException.java b/src/main/java/stanl_2/final_backend/domain/order/common/exception/OrderCommonException.java new file mode 100644 index 00000000..6e81a006 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/order/common/exception/OrderCommonException.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.order.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class OrderCommonException extends RuntimeException { + private final OrderErrorCode sampleErrorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.sampleErrorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/order/common/exception/OrderErrorCode.java b/src/main/java/stanl_2/final_backend/domain/order/common/exception/OrderErrorCode.java new file mode 100644 index 00000000..05350a0c --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/order/common/exception/OrderErrorCode.java @@ -0,0 +1,52 @@ +package stanl_2.final_backend.domain.order.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum OrderErrorCode { + + /** + * 400(Bad Request) + * 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. + */ + + + + /** + * 401(Unauthorized) + * 비록 HTTP 표준에서는 "미승인(unauthorized)"를 명확히 하고 있지만, + * 의미상 이 응답은 "비인증(unauthenticated)"을 의미합니다. + * 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로를 인증해야 합니다. + */ + + + /** + * 403(Forbidden) + * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. + * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. + */ + + + + /** + * 404(Not Found) + * 서버는 요청받은 리소스를 찾을 수 없습니다. 브라우저에서는 알려지지 않은 URL을 의미합니다. + * 이것은 API에서 종점은 적절하지만 리소스 자체는 존재하지 않음을 의미할 수도 있습니다. + * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. + * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. + */ + ORDER_NOT_FOUND(404001, HttpStatus.NOT_FOUND, "수주서를 찾지 못했습니다"), + ORDER_STATUS_NOT_APPROVED(404002, HttpStatus.NOT_FOUND, "승인되지 않은 수주서입니다."), + /** + * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. + */ + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); + + + private final Integer code; + private final HttpStatus httpStatus; + private final String msg; +} diff --git a/src/main/java/stanl_2/final_backend/domain/order/common/exception/OrderExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/order/common/exception/OrderExceptionResponse.java new file mode 100644 index 00000000..cac1fb2e --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/order/common/exception/OrderExceptionResponse.java @@ -0,0 +1,22 @@ +package stanl_2.final_backend.domain.order.common.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class OrderExceptionResponse { + private final Integer code; + private final String msg; + private final HttpStatus httpStatus; + + public OrderExceptionResponse(OrderErrorCode sampleErrorCode) { + this.code = sampleErrorCode.getCode(); + this.msg = sampleErrorCode.getMsg(); + this.httpStatus = sampleErrorCode.getHttpStatus(); + } + + public static OrderExceptionResponse of(OrderErrorCode sampleErrorCode) { + return new OrderExceptionResponse(sampleErrorCode); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/order/common/response/OrderResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/order/common/response/OrderResponseMessage.java new file mode 100644 index 00000000..00c7e773 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/order/common/response/OrderResponseMessage.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.order.common.response; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +public class OrderResponseMessage { + private int httpStatus; + private String msg; + private Object result; +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/order/query/controller/OrderController.java b/src/main/java/stanl_2/final_backend/domain/order/query/controller/OrderController.java new file mode 100644 index 00000000..058b47f0 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/order/query/controller/OrderController.java @@ -0,0 +1,286 @@ +package stanl_2.final_backend.domain.order.query.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stanl_2.final_backend.domain.order.common.response.OrderResponseMessage; +import stanl_2.final_backend.domain.order.query.dto.OrderSelectAllDTO; +import stanl_2.final_backend.domain.order.query.dto.OrderSelectIdDTO; +import stanl_2.final_backend.domain.order.query.dto.OrderSelectSearchDTO; +import stanl_2.final_backend.domain.order.query.service.OrderQueryService; + +import java.security.GeneralSecurityException; +import java.security.Principal; + +@Slf4j +@RestController("queryOrderController") +@RequestMapping("/api/v1/order") +public class OrderController { + + private final OrderQueryService orderQueryService; + + @Autowired + public OrderController(OrderQueryService orderQueryService) { + this.orderQueryService = orderQueryService; + } + + @Operation(summary = "수주서 전체 조회(영업사원)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "수주서 전체 조회 성공", + content = {@Content(schema = @Schema(implementation = OrderResponseMessage.class))}) + }) + @GetMapping("employee") + public ResponseEntity getAllOrderEmployee(Principal principal, + @PageableDefault(size = 10)Pageable pageable, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder) { + + String loginId = principal.getName(); + + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + Page responseOrders = orderQueryService.selectAllEmployee(loginId, pageable); + + return ResponseEntity.ok(OrderResponseMessage.builder() + .httpStatus(200) + .msg("수주서 전체 조회 성공") + .result(responseOrders) + .build()); + } + + @Operation(summary = "수주서 상세 조회(영업사원)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "수주서 상세 조회 성공", + content = {@Content(schema = @Schema(implementation = OrderResponseMessage.class))}) + }) + @GetMapping("employee/{orderId}") + public ResponseEntity getDetailOrderEmployee(@PathVariable("orderId") String orderId, + Principal principal) { + + String memberId = principal.getName(); + OrderSelectIdDTO orderSelectIdDTO = new OrderSelectIdDTO(); + orderSelectIdDTO.setOrderId(orderId); + orderSelectIdDTO.setMemberId(memberId); + + OrderSelectIdDTO responseOrder = orderQueryService.selectDetailOrderEmployee(orderSelectIdDTO); + + return ResponseEntity.ok(OrderResponseMessage.builder() + .httpStatus(200) + .msg("수주서 상세 조회 성공") + .result(responseOrder) + .build()); + } + + @Operation(summary = "수주서 검색 조회(영업사원)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "수주서 검색 조회 성공", + content = {@Content(schema = @Schema(implementation = OrderResponseMessage.class))}) + }) + @GetMapping("employee/search") + public ResponseEntity getSearchOrderEmployee(@RequestParam(required = false) String title, + @RequestParam(required = false) String status, + @RequestParam(required = false) String adminId, + @RequestParam(required = false) String searchMemberId, + @RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate, + @RequestParam(required = false) String productName, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder, + Principal principal, + @PageableDefault(size = 10) Pageable pageable) throws GeneralSecurityException { + + OrderSelectSearchDTO orderSelectSearchDTO = new OrderSelectSearchDTO(); + orderSelectSearchDTO.setTitle(title); + orderSelectSearchDTO.setStatus(status); + orderSelectSearchDTO.setAdminId(adminId); + orderSelectSearchDTO.setSearchMemberId(searchMemberId); + orderSelectSearchDTO.setStartDate(startDate); + orderSelectSearchDTO.setEndDate(endDate); + orderSelectSearchDTO.setMemberId(principal.getName()); + orderSelectSearchDTO.setProductName(productName); + + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + Page responseOrders = orderQueryService.selectSearchOrdersEmployee(orderSelectSearchDTO, pageable); + + return ResponseEntity.ok(OrderResponseMessage.builder() + .httpStatus(200) + .msg("수주서 검색 조회 성공") + .result(responseOrders) + .build()); + } + + // 영업담당자 조회 + @Operation(summary = "수주서 전체 조회(영업담당자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "수주서 전체 조회 성공", + content = {@Content(schema = @Schema(implementation = OrderResponseMessage.class))}) + }) + @GetMapping("") + public ResponseEntity getAllOrder(@PageableDefault(size = 10)Pageable pageable) { + + Page responseOrders = orderQueryService.selectAll(pageable); + + return ResponseEntity.ok(OrderResponseMessage.builder() + .httpStatus(200) + .msg("수주서 전체 조회 성공") + .result(responseOrders) + .build()); + } + + @Operation(summary = "수주서 상세 조회(영업담당자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "수주서 상세 조회 성공", + content = {@Content(schema = @Schema(implementation = OrderResponseMessage.class))}) + }) + @GetMapping("{orderId}") + public ResponseEntity getDetailOrder(@PathVariable("orderId") String orderId) { + + OrderSelectIdDTO orderSelectIdDTO = new OrderSelectIdDTO(); + orderSelectIdDTO.setOrderId(orderId); + + OrderSelectIdDTO responseOrder = orderQueryService.selectDetailOrder(orderSelectIdDTO); + + return ResponseEntity.ok(OrderResponseMessage.builder() + .httpStatus(200) + .msg("수주서 상세 조회 성공") + .result(responseOrder) + .build()); + } + + @Operation(summary = "수주서 검색 조회(영업담당자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "수주서 검색 조회 성공", + content = {@Content(schema = @Schema(implementation = OrderResponseMessage.class))}) + }) + @GetMapping("search") + public ResponseEntity getSearchOrder(@RequestParam(required = false) String title, + @RequestParam(required = false) String orderId, + @RequestParam(required = false) String status, + @RequestParam(required = false) String adminId, + @RequestParam(required = false) String searchMemberId, + @RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder, + @RequestParam(required = false) String productName, + @PageableDefault(size = 10) Pageable pageable) throws GeneralSecurityException { + + OrderSelectSearchDTO orderSelectSearchDTO = new OrderSelectSearchDTO(); + orderSelectSearchDTO.setTitle(title); + orderSelectSearchDTO.setStatus(status); + orderSelectSearchDTO.setAdminId(adminId); + orderSelectSearchDTO.setSearchMemberId(searchMemberId); + orderSelectSearchDTO.setStartDate(startDate); + orderSelectSearchDTO.setEndDate(endDate); + orderSelectSearchDTO.setProductName(productName); + orderSelectSearchDTO.setOrderId(orderId); + + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + Page responseOrders = orderQueryService.selectSearchOrders(orderSelectSearchDTO, pageable); + + return ResponseEntity.ok(OrderResponseMessage.builder() + .httpStatus(200) + .msg("수주서 검색 조회 성공") + .result(responseOrders) + .build()); + } + + // 영업관리자 + @Operation(summary = "수주서 전체 조회(영업담당자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "수주서 전체 조회 성공", + content = {@Content(schema = @Schema(implementation = OrderResponseMessage.class))}) + }) + @GetMapping("center") + public ResponseEntity getAllOrderCenter(@PageableDefault(size = 10)Pageable pageable, + Principal principal) throws GeneralSecurityException { + + String memberId = principal.getName(); + Page responseOrders = orderQueryService.selectAllCenter(pageable, memberId); + + return ResponseEntity.ok(OrderResponseMessage.builder() + .httpStatus(200) + .msg("수주서 전체 조회 성공") + .result(responseOrders) + .build()); + } + + @Operation(summary = "수주서 검색 조회(영업담당자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "수주서 검색 조회 성공", + content = {@Content(schema = @Schema(implementation = OrderResponseMessage.class))}) + }) + @GetMapping("center/search") + public ResponseEntity getSearchOrderCenter( + Principal principal, + @RequestParam(required = false) String title, + @RequestParam(required = false) String orderId, + @RequestParam(required = false) String status, + @RequestParam(required = false) String adminId, + @RequestParam(required = false) String searchMemberId, + @RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder, + @RequestParam(required = false) String productName, + @PageableDefault(size = 10) Pageable pageable) throws GeneralSecurityException { + + OrderSelectSearchDTO orderSelectSearchDTO = new OrderSelectSearchDTO(); + orderSelectSearchDTO.setTitle(title); + orderSelectSearchDTO.setStatus(status); + orderSelectSearchDTO.setAdminId(adminId); + orderSelectSearchDTO.setSearchMemberId(searchMemberId); + orderSelectSearchDTO.setStartDate(startDate); + orderSelectSearchDTO.setEndDate(endDate); + orderSelectSearchDTO.setProductName(productName); + orderSelectSearchDTO.setOrderId(orderId); + orderSelectSearchDTO.setMemberId(principal.getName()); + + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + Page responseOrders = orderQueryService.selectSearchOrdersCenter(orderSelectSearchDTO, pageable); + + return ResponseEntity.ok(OrderResponseMessage.builder() + .httpStatus(200) + .msg("수주서 검색 조회 성공") + .result(responseOrders) + .build()); + } + + @Operation(summary = "수주서 엑셀 다운로드") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "수주서 엑셀 다운로드 성공", + content = {@Content(schema = @Schema(implementation = OrderResponseMessage.class))}) + }) + @GetMapping("excel") + public void exportOrder(HttpServletResponse response) { + + orderQueryService.exportOrder(response); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/order/query/dto/OrderExcelDTO.java b/src/main/java/stanl_2/final_backend/domain/order/query/dto/OrderExcelDTO.java new file mode 100644 index 00000000..c7d5191e --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/order/query/dto/OrderExcelDTO.java @@ -0,0 +1,28 @@ +package stanl_2.final_backend.domain.order.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import stanl_2.final_backend.global.excel.ExcelColumnName; + +@Getter +@AllArgsConstructor +public class OrderExcelDTO { + + @ExcelColumnName(name = "수주서 번호") + private String orderId; + + @ExcelColumnName(name = "수주서명") + private String title; + + @ExcelColumnName(name = "승인 상태") + private String status; + + @ExcelColumnName(name = "제품명") + private String productName; + + @ExcelColumnName(name = "수주자") + private String memberId; + + @ExcelColumnName(name = "수주일") + private String createdAt; +} diff --git a/src/main/java/stanl_2/final_backend/domain/order/query/dto/OrderSelectAllDTO.java b/src/main/java/stanl_2/final_backend/domain/order/query/dto/OrderSelectAllDTO.java new file mode 100644 index 00000000..a17b8908 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/order/query/dto/OrderSelectAllDTO.java @@ -0,0 +1,20 @@ +package stanl_2.final_backend.domain.order.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class OrderSelectAllDTO { + private String orderId; + private String title; + private String status; + private String contractTitle; + private String adminName; + private String memberName; + private String productName; +} diff --git a/src/main/java/stanl_2/final_backend/domain/order/query/dto/OrderSelectIdDTO.java b/src/main/java/stanl_2/final_backend/domain/order/query/dto/OrderSelectIdDTO.java new file mode 100644 index 00000000..91c5a92a --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/order/query/dto/OrderSelectIdDTO.java @@ -0,0 +1,24 @@ +package stanl_2.final_backend.domain.order.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class OrderSelectIdDTO { + private String orderId; + private String title; + private String content; + private Boolean active; + private String status; + private String createdAt; + private String updatedAt; + private String deletedAt; + private String contractId; + private String adminId; + private String memberId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/order/query/dto/OrderSelectSearchDTO.java b/src/main/java/stanl_2/final_backend/domain/order/query/dto/OrderSelectSearchDTO.java new file mode 100644 index 00000000..979d63e5 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/order/query/dto/OrderSelectSearchDTO.java @@ -0,0 +1,24 @@ +package stanl_2.final_backend.domain.order.query.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class OrderSelectSearchDTO { + private String orderId; + private String title; + private String status; + private String contractTitle; + private String adminId; + private String searchMemberId; + private String memberId; + private String adminName; + private String memberName; + private String productName; + private String startDate; + private String endDate; + private String createdAt; + private String centerId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/order/query/repository/OrderMapper.java b/src/main/java/stanl_2/final_backend/domain/order/query/repository/OrderMapper.java new file mode 100644 index 00000000..f09d6b48 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/order/query/repository/OrderMapper.java @@ -0,0 +1,65 @@ +package stanl_2.final_backend.domain.order.query.repository; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import stanl_2.final_backend.domain.order.query.dto.OrderExcelDTO; +import stanl_2.final_backend.domain.order.query.dto.OrderSelectAllDTO; +import stanl_2.final_backend.domain.order.query.dto.OrderSelectIdDTO; +import stanl_2.final_backend.domain.order.query.dto.OrderSelectSearchDTO; + +import java.util.List; + +@Mapper +public interface OrderMapper { + List findSearchOrderByMemberId(@Param("offset") int offset, + @Param("pageSize") int pageSize, + @Param("orderSelectSearchDTO") OrderSelectSearchDTO orderSelectSearchDTO, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + int findOrderCountByMemberId(@Param("memberId") String memberId); + + OrderSelectIdDTO findOrderByIdAndMemberId(String orderId, String memberId); + + List findAllOrderByMemberId(@Param("offset") int offset, + @Param("pageSize") int pageSize, + @Param("memberId") String memberId, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + int findOrderSearchCountByMemberId(OrderSelectSearchDTO orderSelectSearchDTO); + + List findAllOrder(@Param("offset") int offset, + @Param("pageSize") int pageSize); + + int findOrderCount(); + + OrderSelectIdDTO findOrderByOrderId(String orderId); + + List findSearchOrder(@Param("offset") int offset, + @Param("pageSize") int pageSize, + @Param("orderSelectSearchDTO") OrderSelectSearchDTO orderSelectSearchDTO, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + int findOrderSearchCount(OrderSelectSearchDTO orderSelectSearchDTO); + + List findOrderForExcel(); + + String selectByContractId(String orderId); + + List findAllOrderCenter(@Param("offset") int offset, + @Param("pageSize") int pageSize, + @Param("memberId") String memberId1, + @Param("centerId") String centerId); + + Integer findOrderCountCenter(); + + List findSearchOrderCenter(@Param("offset") int offset, + @Param("pageSize") int pageSize, + @Param("orderSelectSearchDTO") OrderSelectSearchDTO orderSelectSearchDTO, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + Integer findOrderSearchCountCenter(OrderSelectSearchDTO orderSelectSearchDTO); +} diff --git a/src/main/java/stanl_2/final_backend/domain/order/query/service/OrderQueryService.java b/src/main/java/stanl_2/final_backend/domain/order/query/service/OrderQueryService.java new file mode 100644 index 00000000..8a0d332f --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/order/query/service/OrderQueryService.java @@ -0,0 +1,32 @@ +package stanl_2.final_backend.domain.order.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import stanl_2.final_backend.domain.order.query.dto.OrderSelectAllDTO; +import stanl_2.final_backend.domain.order.query.dto.OrderSelectIdDTO; +import stanl_2.final_backend.domain.order.query.dto.OrderSelectSearchDTO; + +import java.security.GeneralSecurityException; + +public interface OrderQueryService { + Page selectAllEmployee(String loginId, Pageable pageable); + + OrderSelectIdDTO selectDetailOrderEmployee(OrderSelectIdDTO orderSelectIdDTO); + + Page selectSearchOrdersEmployee(OrderSelectSearchDTO orderSelectSearchDTO, Pageable pageable) throws GeneralSecurityException; + + Page selectAll(Pageable pageable); + + OrderSelectIdDTO selectDetailOrder(OrderSelectIdDTO orderSelectIdDTO); + + Page selectSearchOrders(OrderSelectSearchDTO orderSelectSearchDTO, Pageable pageable) throws GeneralSecurityException; + + String selectByContractId(String orderId); + + void exportOrder(HttpServletResponse response); + + Page selectAllCenter(Pageable pageable, String memberId) throws GeneralSecurityException; + + Page selectSearchOrdersCenter(OrderSelectSearchDTO orderSelectSearchDTO, Pageable pageable) throws GeneralSecurityException; +} diff --git a/src/main/java/stanl_2/final_backend/domain/order/query/service/OrderQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/order/query/service/OrderQueryServiceImpl.java new file mode 100644 index 00000000..6eba05a1 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/order/query/service/OrderQueryServiceImpl.java @@ -0,0 +1,337 @@ +package stanl_2.final_backend.domain.order.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.domain.order.common.exception.OrderCommonException; +import stanl_2.final_backend.domain.order.common.exception.OrderErrorCode; +import stanl_2.final_backend.domain.order.query.dto.OrderExcelDTO; +import stanl_2.final_backend.domain.order.query.dto.OrderSelectAllDTO; +import stanl_2.final_backend.domain.order.query.dto.OrderSelectIdDTO; +import stanl_2.final_backend.domain.order.query.dto.OrderSelectSearchDTO; +import stanl_2.final_backend.domain.order.query.repository.OrderMapper; +import stanl_2.final_backend.global.excel.ExcelUtilsV1; + +import java.security.GeneralSecurityException; +import java.util.List; + +@Slf4j +@Service +public class OrderQueryServiceImpl implements OrderQueryService { + + private final OrderMapper orderMapper; + private final AuthQueryService authQueryService; + private final MemberQueryService memberQueryService; + private final RedisTemplate redisTemplate; + private final ExcelUtilsV1 excelUtilsV1; + + @Autowired + public OrderQueryServiceImpl(OrderMapper orderMapper, AuthQueryService authQueryService, MemberQueryService memberQueryService, RedisTemplate redisTemplate, ExcelUtilsV1 excelUtilsV1) { + this.orderMapper = orderMapper; + this.authQueryService = authQueryService; + this.memberQueryService = memberQueryService; + this.redisTemplate = redisTemplate; + this.excelUtilsV1 = excelUtilsV1; + } + + // 영업사원 조회 + @Override + @Transactional(readOnly = true) + public Page selectAllEmployee(String loginId, Pageable pageable) { + String memberId = authQueryService.selectMemberIdByLoginId(loginId); + + int offset = Math.toIntExact(pageable.getOffset()); + int pageSize = pageable.getPageSize(); + + // 정렬 정보 가져오기 + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + List orders = orderMapper.findAllOrderByMemberId(offset, pageSize, memberId, sortField, sortOrder); + + // 결과가 null이거나 빈 리스트인지 확인 + if (orders == null || orders.isEmpty()) { + throw new OrderCommonException(OrderErrorCode.ORDER_NOT_FOUND); + } + + // 전체 개수 조회 + Integer count = orderMapper.findOrderCountByMemberId(memberId); + int totalOrder = (count != null) ? count : 0; + + return new PageImpl<>(orders, pageable, totalOrder); + } + + + @Override + @Transactional(readOnly = true) + public OrderSelectIdDTO selectDetailOrderEmployee(OrderSelectIdDTO orderSelectIdDTO) { + String memberId = authQueryService.selectMemberIdByLoginId(orderSelectIdDTO.getMemberId()); + + OrderSelectIdDTO order = orderMapper.findOrderByIdAndMemberId(orderSelectIdDTO.getOrderId(), memberId); + + if (order == null) { + throw new OrderCommonException(OrderErrorCode.ORDER_NOT_FOUND); + } + + return order; + } + + @Override + @Transactional(readOnly = true) + public Page selectSearchOrdersEmployee(OrderSelectSearchDTO orderSelectSearchDTO, Pageable pageable) throws GeneralSecurityException { + + String memberId = authQueryService.selectMemberIdByLoginId(orderSelectSearchDTO.getMemberId()); + orderSelectSearchDTO.setMemberId(memberId); + + if ("대기".equals(orderSelectSearchDTO.getStatus())) { + orderSelectSearchDTO.setStatus("WAIT"); + } + if ("승인".equals(orderSelectSearchDTO.getStatus())) { + orderSelectSearchDTO.setStatus("APPROVED"); + } + if ("취소".equals(orderSelectSearchDTO.getStatus())) { + orderSelectSearchDTO.setStatus("CANCEL"); + } + + int offset = Math.toIntExact(pageable.getOffset()); + int pageSize = pageable.getPageSize(); + // 정렬 정보 가져오기 + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + List orders = orderMapper.findSearchOrderByMemberId(offset, pageSize, orderSelectSearchDTO, sortField, sortOrder); + + if (orders == null) { + throw new OrderCommonException(OrderErrorCode.ORDER_NOT_FOUND); + } + + for (OrderSelectSearchDTO order : orders) { + if (order.getMemberId() != null) { + String memberName = memberQueryService.selectNameById(order.getMemberId()); + order.setMemberName(memberName); + } + + if (order.getAdminId() != null) { + String adminName = memberQueryService.selectNameById(order.getAdminId()); + order.setAdminName(adminName); + } else { + order.setAdminName("-"); + } + } + + int count = orderMapper.findOrderSearchCountByMemberId(orderSelectSearchDTO); + if (count == 0) { + throw new OrderCommonException(OrderErrorCode.ORDER_NOT_FOUND); + } + + return new PageImpl<>(orders, pageable, count); + } + + // 영업담당자, 영업관리자 조회 + @Override + @Transactional(readOnly = true) + public Page selectAll(Pageable pageable) { + + int offset = Math.toIntExact(pageable.getOffset()); + int pageSize = pageable.getPageSize(); + + // 캐시 조회 + List orders = orderMapper.findAllOrder(offset, pageSize); + + // 결과가 null이거나 빈 리스트인지 확인 + if (orders == null || orders.isEmpty()) { + throw new OrderCommonException(OrderErrorCode.ORDER_NOT_FOUND); + } + + // 전체 개수 조회 + Integer count = orderMapper.findOrderCount(); + int totalOrder = (count != null) ? count : 0; + + return new PageImpl<>(orders, pageable, totalOrder); + } + + @Override + @Transactional(readOnly = true) + public OrderSelectIdDTO selectDetailOrder(OrderSelectIdDTO orderSelectIdDTO) { + + OrderSelectIdDTO order = orderMapper.findOrderByOrderId(orderSelectIdDTO.getOrderId()); + + if (order == null) { + throw new OrderCommonException(OrderErrorCode.ORDER_NOT_FOUND); + } + + return order; + } + + @Override + @Transactional(readOnly = true) + public Page selectSearchOrders(OrderSelectSearchDTO orderSelectSearchDTO, Pageable pageable) throws GeneralSecurityException { + + if ("대기".equals(orderSelectSearchDTO.getStatus())) { + orderSelectSearchDTO.setStatus("WAIT"); + } + if ("승인".equals(orderSelectSearchDTO.getStatus())) { + orderSelectSearchDTO.setStatus("APPROVED"); + } + if ("취소".equals(orderSelectSearchDTO.getStatus())) { + orderSelectSearchDTO.setStatus("CANCEL"); + } + + int offset = Math.toIntExact(pageable.getOffset()); + int pageSize = pageable.getPageSize(); + + // 정렬 정보 가져오기 + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + List orders = orderMapper.findSearchOrder(offset, pageSize, orderSelectSearchDTO, sortField, sortOrder); + + if (orders == null || orders.isEmpty()) { + throw new OrderCommonException(OrderErrorCode.ORDER_NOT_FOUND); + } + + for (OrderSelectSearchDTO order : orders) { + if (order.getMemberId() != null) { + String memberName = memberQueryService.selectNameById(order.getMemberId()); + order.setMemberName(memberName); + } + + if (order.getAdminId() != null) { + String adminName = memberQueryService.selectNameById(order.getAdminId()); + order.setAdminName(adminName); + } else { + order.setAdminName("-"); + } + } + + Integer count = orderMapper.findOrderSearchCount(orderSelectSearchDTO); + int totalOrder = (count != null) ? count : 0; + + return new PageImpl<>(orders, pageable, totalOrder); + } + + @Override + @Transactional(readOnly = true) + public String selectByContractId(String orderId) { + + String contractId = orderMapper.selectByContractId(orderId); + + return contractId; + } + + @Override + @Transactional(readOnly = true) + public void exportOrder(HttpServletResponse response) { + List orderExcels = orderMapper.findOrderForExcel(); + + if (orderExcels == null) { + throw new OrderCommonException(OrderErrorCode.ORDER_NOT_FOUND); + } + + excelUtilsV1.download(OrderExcelDTO.class, orderExcels, "orderExcel", response); + } + + @Override + public Page selectAllCenter(Pageable pageable, String memberId) throws GeneralSecurityException { + + String memberId1 = authQueryService.selectMemberIdByLoginId(memberId); + String centerId = memberQueryService.selectMemberInfo(memberId).getCenterId(); + + int offset = Math.toIntExact(pageable.getOffset()); + int pageSize = pageable.getPageSize(); + + // 캐시 조회 + List orders = orderMapper.findAllOrderCenter(offset, pageSize, memberId1, centerId); + + // 결과가 null이거나 빈 리스트인지 확인 + if (orders == null || orders.isEmpty()) { + throw new OrderCommonException(OrderErrorCode.ORDER_NOT_FOUND); + } + + // 전체 개수 조회 + Integer count = orderMapper.findOrderCountCenter(); + int totalOrder = (count != null) ? count : 0; + + return new PageImpl<>(orders, pageable, totalOrder); + } + + @Override + public Page selectSearchOrdersCenter(OrderSelectSearchDTO orderSelectSearchDTO, Pageable pageable) throws GeneralSecurityException { + + String memberId = authQueryService.selectMemberIdByLoginId(orderSelectSearchDTO.getMemberId()); + String centerId = memberQueryService.selectMemberInfo(orderSelectSearchDTO.getMemberId()).getCenterId(); + orderSelectSearchDTO.setMemberId(memberId); + orderSelectSearchDTO.setCenterId(centerId); + + if ("대기".equals(orderSelectSearchDTO.getStatus())) { + orderSelectSearchDTO.setStatus("WAIT"); + } + if ("승인".equals(orderSelectSearchDTO.getStatus())) { + orderSelectSearchDTO.setStatus("APPROVED"); + } + if ("취소".equals(orderSelectSearchDTO.getStatus())) { + orderSelectSearchDTO.setStatus("CANCEL"); + } + + int offset = Math.toIntExact(pageable.getOffset()); + int pageSize = pageable.getPageSize(); + + // 정렬 정보 가져오기 + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + List orders = orderMapper.findSearchOrderCenter(offset, pageSize, orderSelectSearchDTO, sortField, sortOrder); + + if (orders == null || orders.isEmpty()) { + throw new OrderCommonException(OrderErrorCode.ORDER_NOT_FOUND); + } + + for (OrderSelectSearchDTO order : orders) { + if (order.getMemberId() != null) { + String memberName = memberQueryService.selectNameById(order.getMemberId()); + order.setMemberName(memberName); + } + + if (order.getAdminId() != null) { + String adminName = memberQueryService.selectNameById(order.getAdminId()); + order.setAdminName(adminName); + } else { + order.setAdminName("-"); + } + } + + Integer count = orderMapper.findOrderSearchCountCenter(orderSelectSearchDTO); + int totalOrder = (count != null) ? count : 0; + + return new PageImpl<>(orders, pageable, totalOrder); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/organization/command/application/controller/OrganizationController.java b/src/main/java/stanl_2/final_backend/domain/organization/command/application/controller/OrganizationController.java new file mode 100644 index 00000000..87855ae9 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/organization/command/application/controller/OrganizationController.java @@ -0,0 +1,18 @@ +package stanl_2.final_backend.domain.organization.command.application.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import stanl_2.final_backend.domain.organization.command.application.service.OrganizationCommandService; + +@RestController("commandOrganizationController") +@RequestMapping("/api/v1/organization") +public class OrganizationController { + + private final OrganizationCommandService organizationCommandService; + + @Autowired + public OrganizationController(OrganizationCommandService organizationCommandService) { + this.organizationCommandService = organizationCommandService; + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/organization/command/application/dto/OrganizationRegisterDTO.java b/src/main/java/stanl_2/final_backend/domain/organization/command/application/dto/OrganizationRegisterDTO.java new file mode 100644 index 00000000..a76e0982 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/organization/command/application/dto/OrganizationRegisterDTO.java @@ -0,0 +1,15 @@ +package stanl_2.final_backend.domain.organization.command.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class OrganizationRegisterDTO { + private String name; + private String parent; +} diff --git a/src/main/java/stanl_2/final_backend/domain/organization/command/application/service/OrganizationCommandService.java b/src/main/java/stanl_2/final_backend/domain/organization/command/application/service/OrganizationCommandService.java new file mode 100644 index 00000000..d13400e8 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/organization/command/application/service/OrganizationCommandService.java @@ -0,0 +1,4 @@ +package stanl_2.final_backend.domain.organization.command.application.service; + +public interface OrganizationCommandService { +} diff --git a/src/main/java/stanl_2/final_backend/domain/organization/command/domain/aggregate/entity/Organization.java b/src/main/java/stanl_2/final_backend/domain/organization/command/domain/aggregate/entity/Organization.java new file mode 100644 index 00000000..07db4118 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/organization/command/domain/aggregate/entity/Organization.java @@ -0,0 +1,32 @@ +package stanl_2.final_backend.domain.organization.command.domain.aggregate.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "TB_organization_chart") +public class Organization { + @Id + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name = "prefix", value = "ORG") + ) + @Column(name = "ORG_ID") + private String organizationId; + + @Column(name = "ORG_CHA_NAME") + private String name; + + @Column(name = "ORG_CHA_PARENT") + private String parent; +} diff --git a/src/main/java/stanl_2/final_backend/domain/organization/command/domain/repository/OrganizationRepository.java b/src/main/java/stanl_2/final_backend/domain/organization/command/domain/repository/OrganizationRepository.java new file mode 100644 index 00000000..fa94a423 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/organization/command/domain/repository/OrganizationRepository.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.organization.command.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import stanl_2.final_backend.domain.organization.command.domain.aggregate.entity.Organization; + +@Repository +public interface OrganizationRepository extends JpaRepository { +} diff --git a/src/main/java/stanl_2/final_backend/domain/organization/command/domain/service/OrganizationCommandServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/organization/command/domain/service/OrganizationCommandServiceImpl.java new file mode 100644 index 00000000..a3d1dfdd --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/organization/command/domain/service/OrganizationCommandServiceImpl.java @@ -0,0 +1,23 @@ +package stanl_2.final_backend.domain.organization.command.domain.service; + +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import stanl_2.final_backend.domain.organization.command.application.service.OrganizationCommandService; +import stanl_2.final_backend.domain.organization.command.domain.repository.OrganizationRepository; + +@Service("commandOrganizationService") +public class OrganizationCommandServiceImpl implements OrganizationCommandService { + + private final OrganizationRepository organizationRepository; + private final ModelMapper modelMapper; + + @Autowired + public OrganizationCommandServiceImpl(OrganizationRepository organizationRepository, + ModelMapper modelMapper) { + this.organizationRepository = organizationRepository; + this.modelMapper = modelMapper; + } + + +} diff --git a/src/main/java/stanl_2/final_backend/domain/organization/common/exception/OrganizationCommonException.java b/src/main/java/stanl_2/final_backend/domain/organization/common/exception/OrganizationCommonException.java new file mode 100644 index 00000000..73f7143d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/organization/common/exception/OrganizationCommonException.java @@ -0,0 +1,17 @@ +package stanl_2.final_backend.domain.organization.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import stanl_2.final_backend.domain.A_sample.common.exception.SampleErrorCode; + +@Getter +@RequiredArgsConstructor +public class OrganizationCommonException extends RuntimeException { + private final SampleErrorCode sampleErrorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.sampleErrorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/organization/common/exception/OrganizationErrorCode.java b/src/main/java/stanl_2/final_backend/domain/organization/common/exception/OrganizationErrorCode.java new file mode 100644 index 00000000..42ed83b9 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/organization/common/exception/OrganizationErrorCode.java @@ -0,0 +1,52 @@ +package stanl_2.final_backend.domain.organization.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum OrganizationErrorCode { + + /** + * 400(Bad Request) + * 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. + */ + + + + /** + * 401(Unauthorized) + * 비록 HTTP 표준에서는 "미승인(unauthorized)"를 명확히 하고 있지만, + * 의미상 이 응답은 "비인증(unauthenticated)"을 의미합니다. + * 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로를 인증해야 합니다. + */ + + + /** + * 403(Forbidden) + * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. + * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. + */ + + + + /** + * 404(Not Found) + * 서버는 요청받은 리소스를 찾을 수 없습니다. 브라우저에서는 알려지지 않은 URL을 의미합니다. + * 이것은 API에서 종점은 적절하지만 리소스 자체는 존재하지 않음을 의미할 수도 있습니다. + * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. + * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. + */ + SAMPLE_NOT_FOUND(404001, HttpStatus.NOT_FOUND, "sample 데이터를 찾지 못했습니다"), + CENTER_NOT_FOUND(404002, HttpStatus.NOT_FOUND, "center 데이터를 찾지 못했습니다."), + /** + * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. + */ + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); + + + private final Integer code; + private final HttpStatus httpStatus; + private final String msg; +} diff --git a/src/main/java/stanl_2/final_backend/domain/organization/common/exception/OrganizationExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/organization/common/exception/OrganizationExceptionResponse.java new file mode 100644 index 00000000..7c6d6720 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/organization/common/exception/OrganizationExceptionResponse.java @@ -0,0 +1,23 @@ +package stanl_2.final_backend.domain.organization.common.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; +import stanl_2.final_backend.domain.A_sample.common.exception.SampleErrorCode; + +@Getter +public class OrganizationExceptionResponse { + private final Integer code; + private final String msg; + private final HttpStatus httpStatus; + + public OrganizationExceptionResponse(stanl_2.final_backend.domain.A_sample.common.exception.SampleErrorCode sampleErrorCode) { + this.code = sampleErrorCode.getCode(); + this.msg = sampleErrorCode.getMsg(); + this.httpStatus = sampleErrorCode.getHttpStatus(); + } + + public static OrganizationExceptionResponse of(SampleErrorCode sampleErrorCode) { + return new OrganizationExceptionResponse(sampleErrorCode); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/organization/common/response/OrganizationResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/organization/common/response/OrganizationResponseMessage.java new file mode 100644 index 00000000..be617d66 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/organization/common/response/OrganizationResponseMessage.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.organization.common.response; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +public class OrganizationResponseMessage { + private int httpStatus; + private String msg; + private Object result; +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/organization/query/controller/OrganizationController.java b/src/main/java/stanl_2/final_backend/domain/organization/query/controller/OrganizationController.java new file mode 100644 index 00000000..cc25c963 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/organization/query/controller/OrganizationController.java @@ -0,0 +1,50 @@ +package stanl_2.final_backend.domain.organization.query.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import stanl_2.final_backend.domain.organization.common.response.OrganizationResponseMessage; +import stanl_2.final_backend.domain.organization.query.dto.OrganizationDTO; +import stanl_2.final_backend.domain.organization.query.service.OrganizationQueryService; + +import java.util.List; + +@RestController(value = "queryOrganizationController") +@RequestMapping("/api/v1/organization") +public class OrganizationController { + + private final OrganizationQueryService organizationQueryService; + + @Autowired + public OrganizationController(OrganizationQueryService organizationQueryService) { + this.organizationQueryService = organizationQueryService; + } + + + @Operation(summary = "사이드바 메뉴 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = OrganizationResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("") + public ResponseEntity getOrganizationById() { + + List organizationDTO = organizationQueryService.selectOrganizationChart(); + + return ResponseEntity.ok(OrganizationResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(organizationDTO) + .build()); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/organization/query/dto/OrganizationDTO.java b/src/main/java/stanl_2/final_backend/domain/organization/query/dto/OrganizationDTO.java new file mode 100644 index 00000000..3c3c9f5e --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/organization/query/dto/OrganizationDTO.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.organization.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class OrganizationDTO { + private String organizationId; + private String name; + private String parent; +} diff --git a/src/main/java/stanl_2/final_backend/domain/organization/query/repository/OrganizationMapper.java b/src/main/java/stanl_2/final_backend/domain/organization/query/repository/OrganizationMapper.java new file mode 100644 index 00000000..a35df811 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/organization/query/repository/OrganizationMapper.java @@ -0,0 +1,11 @@ +package stanl_2.final_backend.domain.organization.query.repository; + +import org.apache.ibatis.annotations.Mapper; +import stanl_2.final_backend.domain.organization.query.dto.OrganizationDTO; + +import java.util.List; + +@Mapper +public interface OrganizationMapper { + List selectOrganizationChart(); +} diff --git a/src/main/java/stanl_2/final_backend/domain/organization/query/service/OrganizationQueryService.java b/src/main/java/stanl_2/final_backend/domain/organization/query/service/OrganizationQueryService.java new file mode 100644 index 00000000..47874a8c --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/organization/query/service/OrganizationQueryService.java @@ -0,0 +1,10 @@ +package stanl_2.final_backend.domain.organization.query.service; + +import stanl_2.final_backend.domain.organization.query.dto.OrganizationDTO; + +import java.util.List; + +public interface OrganizationQueryService { + List selectOrganizationChart(); + +} diff --git a/src/main/java/stanl_2/final_backend/domain/organization/query/service/OrganizationQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/organization/query/service/OrganizationQueryServiceImpl.java new file mode 100644 index 00000000..0450edd5 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/organization/query/service/OrganizationQueryServiceImpl.java @@ -0,0 +1,29 @@ +package stanl_2.final_backend.domain.organization.query.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.organization.query.dto.OrganizationDTO; +import stanl_2.final_backend.domain.organization.query.repository.OrganizationMapper; + +import java.util.List; + +@Service("queryOrganizationService") +public class OrganizationQueryServiceImpl implements OrganizationQueryService{ + + private final OrganizationMapper organizationMapper; + + @Autowired + public OrganizationQueryServiceImpl(OrganizationMapper organizationMapper) { + this.organizationMapper = organizationMapper; + } + + @Override + @Transactional(readOnly = true) + public List selectOrganizationChart() { + + List organizationDTO = organizationMapper.selectOrganizationChart(); + + return organizationDTO; + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/problem/command/application/controller/ProblemController.java b/src/main/java/stanl_2/final_backend/domain/problem/command/application/controller/ProblemController.java new file mode 100644 index 00000000..b4973ecc --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/problem/command/application/controller/ProblemController.java @@ -0,0 +1,143 @@ +package stanl_2.final_backend.domain.problem.command.application.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.domain.problem.command.application.dto.ProblemModifyDTO; +import stanl_2.final_backend.domain.problem.command.application.dto.ProblemRegistDTO; +import stanl_2.final_backend.domain.problem.command.application.service.ProblemCommandService; +import stanl_2.final_backend.domain.problem.command.domain.aggregate.entity.Problem; +import stanl_2.final_backend.domain.problem.common.response.ProblemResponseMessage; +import stanl_2.final_backend.domain.s3.command.domain.service.S3FileServiceImpl; + +import java.security.GeneralSecurityException; +import java.security.Principal; + +@RestController("commandProblemController") +@RequestMapping("/api/v1/problem") +public class ProblemController { + private final ProblemCommandService problemCommandService; + private final AuthQueryService authQueryService; + private final S3FileServiceImpl s3FileService; + private final MemberQueryService memberQueryService; + private final ModelMapper modelMapper; + @Autowired + public ProblemController(ProblemCommandService problemCommandService, AuthQueryService authQueryService, S3FileServiceImpl s3FileService, + MemberQueryService memberQueryService, ModelMapper modelMapper) { + this.problemCommandService = problemCommandService; + this.authQueryService = authQueryService; + this.s3FileService = s3FileService; + this.memberQueryService =memberQueryService; + this.modelMapper =modelMapper; + } + + @Operation(summary = "문제사항 작성") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = ProblemResponseMessage.class))}) + }) + @PostMapping("") + public ResponseEntity postProblem(@RequestPart("dto") ProblemRegistDTO problemRegistDTO, // JSON 데이터 + @RequestPart(value = "file", required = false) MultipartFile file, + Principal principal) throws GeneralSecurityException { + String memberLoginId = principal.getName(); + problemRegistDTO.setMemberLoginId(memberLoginId); + if (file != null && !file.isEmpty()) { + problemRegistDTO.setFileUrl(s3FileService.uploadOneFile(file)); + }else if(file==null || file.isEmpty()){ + problemRegistDTO.setFileUrl(null); + } else { + problemRegistDTO.setFileUrl(null); + } + problemCommandService.registerProblem(problemRegistDTO,principal); + return ResponseEntity.ok(ProblemResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(null) + .build()); + + } + @Operation(summary = "문제사항 수정") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = ProblemResponseMessage.class))}) + }) + @PutMapping("{problemId}") + public ResponseEntity modifyProblem(@PathVariable String problemId, + @RequestPart("dto") ProblemModifyDTO problemModifyDTO, + @RequestPart(value = "file", required = false) MultipartFile file, + Principal principal) throws GeneralSecurityException { + String memberLoginId = principal.getName(); + String memberId = authQueryService.selectMemberIdByLoginId(memberLoginId); + memberId=memberQueryService.selectNameById(memberId); + problemModifyDTO.setMemberId(memberId); + problemModifyDTO.setMemberLoginId(memberLoginId); + problemModifyDTO.setContent(problemModifyDTO.getContent()); + problemModifyDTO.setFileUrl(problemModifyDTO.getFileUrl()); + Problem updateProblem = modelMapper.map(problemModifyDTO, Problem.class); + System.out.println("1."+updateProblem.getFileUrl()); + if(problemModifyDTO.getFileUrl()==null){ + System.out.println("테스트중"); + problemModifyDTO.setFileUrl(updateProblem.getFileUrl()); + } + if (file != null && !file.isEmpty()) { + System.out.println("1번"); + problemModifyDTO.setFileUrl(s3FileService.uploadOneFile(file)); + }else if(file==null || file.isEmpty()) { + System.out.println("2번"); + problemModifyDTO.setFileUrl(updateProblem.getFileUrl()); + } else { + System.out.println("3번"); + problemModifyDTO.setFileUrl(s3FileService.uploadOneFile(file)); + } + problemCommandService.modifyProblem(problemId,problemModifyDTO,principal); + return ResponseEntity.ok(ProblemResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(problemModifyDTO) + .build()); + } + + @Operation(summary = "문제사항 상태 수정") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = ProblemResponseMessage.class))}) + }) + @PutMapping("/status/{problemId}") + public ResponseEntity modifyProblemStatus(@PathVariable String problemId, Principal principal){ + + problemCommandService.modifyStatus(problemId,principal); + + return ResponseEntity.ok(ProblemResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(null) + .build()); + } + + @Operation(summary = "문제사항 삭제") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = ProblemResponseMessage.class))}) + }) + @DeleteMapping("{problemId}") + public ResponseEntity deleteProblem(@PathVariable String problemId, Principal principal) { + + problemCommandService.deleteProblem(problemId,principal); + + return ResponseEntity.ok(ProblemResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(null) + .build()); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/problem/command/application/dto/ProblemModifyDTO.java b/src/main/java/stanl_2/final_backend/domain/problem/command/application/dto/ProblemModifyDTO.java new file mode 100644 index 00000000..206bad9c --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/problem/command/application/dto/ProblemModifyDTO.java @@ -0,0 +1,21 @@ +package stanl_2.final_backend.domain.problem.command.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.stereotype.Component; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class ProblemModifyDTO { + private String problemId; + private String title; + private String content; + private String memberId; + private String memberLoginId; + private String fileUrl; + +} diff --git a/src/main/java/stanl_2/final_backend/domain/problem/command/application/dto/ProblemRegistDTO.java b/src/main/java/stanl_2/final_backend/domain/problem/command/application/dto/ProblemRegistDTO.java new file mode 100644 index 00000000..472ce950 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/problem/command/application/dto/ProblemRegistDTO.java @@ -0,0 +1,27 @@ +package stanl_2.final_backend.domain.problem.command.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class ProblemRegistDTO { + + private String title; + + private String content; + + private String customerId; + + private String memberId; + + private String memberLoginId; + + private String productId; + + private String fileUrl; +} diff --git a/src/main/java/stanl_2/final_backend/domain/problem/command/application/service/ProblemCommandService.java b/src/main/java/stanl_2/final_backend/domain/problem/command/application/service/ProblemCommandService.java new file mode 100644 index 00000000..7c576f45 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/problem/command/application/service/ProblemCommandService.java @@ -0,0 +1,17 @@ +package stanl_2.final_backend.domain.problem.command.application.service; + +import stanl_2.final_backend.domain.problem.command.application.dto.ProblemModifyDTO; +import stanl_2.final_backend.domain.problem.command.application.dto.ProblemRegistDTO; + +import java.security.GeneralSecurityException; +import java.security.Principal; + +public interface ProblemCommandService { + + void registerProblem(ProblemRegistDTO problemRegistDTO, Principal principal) throws GeneralSecurityException; + + ProblemModifyDTO modifyProblem(String problemId, ProblemModifyDTO problemModifyDTO, Principal principal) throws GeneralSecurityException; + + void modifyStatus(String problemId, Principal principal); + void deleteProblem(String problemId, Principal principal); +} diff --git a/src/main/java/stanl_2/final_backend/domain/problem/command/domain/aggregate/entity/Problem.java b/src/main/java/stanl_2/final_backend/domain/problem/command/domain/aggregate/entity/Problem.java new file mode 100644 index 00000000..65eb4b3b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/problem/command/domain/aggregate/entity/Problem.java @@ -0,0 +1,80 @@ +package stanl_2.final_backend.domain.problem.command.domain.aggregate.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@Entity +@Table(name="TB_PROBLEM") +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class Problem { + @Id + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name="prefix", value = "PROB") + ) + @Column(name = "PROB_ID") + private String problemId; + + @Column(name = "PROB_TTL", nullable = false) + private String title; + + @Column(name = "PROB_CONT",columnDefinition = "TEXT",nullable = false) + private String content; + + @Column(name = "CREATED_AT", nullable = false, updatable = false, length=19) + private String createdAt; + + @Column(name = "UPDATED_AT", nullable = false, length=19) + private String updatedAt; + + @Column(name = "DELETED_AT", length=19) + private String deletedAt; + + @Column(name = "ACTIVE", nullable = false) + private Boolean active = true; + + @Column(name = "CST_ID") + private String customerId; + + @Column(name = "MEM_ID", nullable = false) + private String memberId; + + @Column(name = "PROD_ID", nullable = false) + private String productId; + + @Column(name ="PROB_STATUS",nullable = false) + private String status = "PROGRESS"; + + @Column(name = "FILE_URL") + private String fileUrl; + + @PrePersist + private void prePersist() { + this.createdAt = getCurrentTime(); + this.updatedAt = this.createdAt; + } + + // Update 되기 전에 실행 + @PreUpdate + private void preUpdate() { + this.updatedAt = getCurrentTime(); + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/problem/command/domain/aggregate/repository/ProblemRepository.java b/src/main/java/stanl_2/final_backend/domain/problem/command/domain/aggregate/repository/ProblemRepository.java new file mode 100644 index 00000000..db232407 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/problem/command/domain/aggregate/repository/ProblemRepository.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.problem.command.domain.aggregate.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import stanl_2.final_backend.domain.problem.command.domain.aggregate.entity.Problem; + +@Repository +public interface ProblemRepository extends JpaRepository { +} diff --git a/src/main/java/stanl_2/final_backend/domain/problem/command/domain/aggregate/service/ProblemServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/problem/command/domain/aggregate/service/ProblemServiceImpl.java new file mode 100644 index 00000000..8c42e79f --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/problem/command/domain/aggregate/service/ProblemServiceImpl.java @@ -0,0 +1,159 @@ +package stanl_2.final_backend.domain.problem.command.domain.aggregate.service; + +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.domain.notices.common.exception.NoticeCommonException; +import stanl_2.final_backend.domain.notices.common.exception.NoticeErrorCode; +import stanl_2.final_backend.domain.problem.command.application.dto.ProblemModifyDTO; +import stanl_2.final_backend.domain.problem.command.application.dto.ProblemRegistDTO; +import stanl_2.final_backend.domain.problem.command.application.service.ProblemCommandService; +import stanl_2.final_backend.domain.problem.command.domain.aggregate.entity.Problem; +import stanl_2.final_backend.domain.problem.command.domain.aggregate.repository.ProblemRepository; +import stanl_2.final_backend.domain.problem.common.exception.ProblemCommonException; +import stanl_2.final_backend.domain.problem.common.exception.ProblemErrorCode; +import stanl_2.final_backend.global.redis.RedisService; + +import java.security.GeneralSecurityException; +import java.security.Principal; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@Service("commandProblemService") +public class ProblemServiceImpl implements ProblemCommandService { + + private final ProblemRepository problemRepository; + private final AuthQueryService authQueryService; + private final RedisService redisService; + private final ModelMapper modelMapper; + private final MemberQueryService memberQueryService; + + + @Autowired + public ProblemServiceImpl(ProblemRepository problemRepository, + AuthQueryService authQueryService, + ModelMapper modelMapper, + RedisService redisService, + MemberQueryService memberQueryService) { + this.problemRepository = problemRepository; + this.modelMapper = modelMapper; + this.redisService = redisService; + this.authQueryService =authQueryService; + this.memberQueryService = memberQueryService; + } + private String getCurrentTimestamp() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + + @Transactional + @Override + public void registerProblem(ProblemRegistDTO problemRegistDTO, + Principal principal) throws GeneralSecurityException { + redisService.clearProblemCache(); + String memberId = authQueryService.selectMemberIdByLoginId(problemRegistDTO.getMemberLoginId()); + memberId=memberQueryService.selectNameById(memberId); + problemRegistDTO.setMemberId(memberId); + try { + Problem problem = modelMapper.map(problemRegistDTO, Problem.class); + problemRepository.save(problem); + } catch (DataIntegrityViolationException e){ + throw new ProblemCommonException(ProblemErrorCode.DATA_INTEGRITY_VIOLATION); + }catch (Exception e) { + // 서버 오류 + throw new NoticeCommonException(NoticeErrorCode.INTERNAL_SERVER_ERROR); + } + } + + @Transactional + @Override + public ProblemModifyDTO modifyProblem(String problemId, ProblemModifyDTO problemModifyDTO,Principal principal) throws GeneralSecurityException { + redisService.clearProblemCache(); + String memberId = authQueryService.selectMemberIdByLoginId(problemModifyDTO.getMemberLoginId()); + memberId=memberQueryService.selectNameById(memberId); + + Problem problem = problemRepository.findById(problemId) + .orElseThrow(() -> new ProblemCommonException(ProblemErrorCode.PROBLEM_NOT_FOUND)); + if(!problem.getMemberId().equals(memberId)){ + throw new ProblemCommonException(ProblemErrorCode.AUTHORIZATION_VIOLATION); + + } + try { + System.out.println("test1"); + Problem updateProblem = modelMapper.map(problemModifyDTO, Problem.class); + updateProblem.setProblemId(problem.getProblemId()); + updateProblem.setMemberId(problem.getMemberId()); + updateProblem.setStatus(problem.getStatus()); + updateProblem.setCreatedAt(problem.getCreatedAt()); + updateProblem.setActive(problem.getActive()); + updateProblem.setCustomerId(problem.getCustomerId()); + updateProblem.setProductId(problem.getProductId()); + problemRepository.save(updateProblem); + + ProblemModifyDTO problemModify = modelMapper.map(updateProblem,ProblemModifyDTO.class); + + return problemModify; + } catch (DataIntegrityViolationException e) { + // 데이터 무결성 위반 예외 처리 + throw new ProblemCommonException(ProblemErrorCode.DATA_INTEGRITY_VIOLATION); + } catch (Exception e) { + // 서버 오류 + throw new ProblemCommonException(ProblemErrorCode.INTERNAL_SERVER_ERROR); + } + } + + + @Transactional + @Override + public void modifyStatus(String problemId,Principal principal) { + redisService.clearProblemCache(); + String memberId= principal.getName(); + Problem problem = problemRepository.findById(problemId) + .orElseThrow(() -> new ProblemCommonException(ProblemErrorCode.PROBLEM_NOT_FOUND)); + if(problem.getMemberId().equals(memberId)){ + throw new ProblemCommonException(ProblemErrorCode.AUTHORIZATION_VIOLATION); + } + try { + problem.setStatus("DONE"); + problem.setUpdatedAt(getCurrentTimestamp()); + } catch (DataIntegrityViolationException e) { + // 데이터 무결성 위반 예외 처리 + throw new ProblemCommonException(ProblemErrorCode.DATA_INTEGRITY_VIOLATION); + } catch (Exception e) { + // 서버 오류 + throw new ProblemCommonException(ProblemErrorCode.INTERNAL_SERVER_ERROR); + } + } + + @Transactional + @Override + public void deleteProblem(String problemId, Principal principal) { + redisService.clearProblemCache(); + String memberId= principal.getName(); + Problem problem = problemRepository.findById(problemId) + .orElseThrow(()-> new ProblemCommonException(ProblemErrorCode.PROBLEM_NOT_FOUND)); + + if(!problem.getMemberId().equals(memberId)){ + // 권한 오류 + throw new ProblemCommonException(ProblemErrorCode.AUTHORIZATION_VIOLATION); + } + + problem.setActive(false); + problem.setDeletedAt(getCurrentTimestamp()); + + try { + problemRepository.save(problem); + } catch (DataIntegrityViolationException e) { + // 데이터 무결성 위반 예외 처리 + throw new ProblemCommonException(ProblemErrorCode.DATA_INTEGRITY_VIOLATION); + } catch (Exception e) { + // 서버 오류 + throw new ProblemCommonException(ProblemErrorCode.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/problem/common/exception/ProblemCommonException.java b/src/main/java/stanl_2/final_backend/domain/problem/common/exception/ProblemCommonException.java new file mode 100644 index 00000000..7799e30a --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/problem/common/exception/ProblemCommonException.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.problem.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class ProblemCommonException extends RuntimeException { + private final ProblemErrorCode problemErrorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.problemErrorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/problem/common/exception/ProblemErrorCode.java b/src/main/java/stanl_2/final_backend/domain/problem/common/exception/ProblemErrorCode.java new file mode 100644 index 00000000..4b54f853 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/problem/common/exception/ProblemErrorCode.java @@ -0,0 +1,55 @@ +package stanl_2.final_backend.domain.problem.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum ProblemErrorCode { + + /** + * 400(Bad Request) + * 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. + */ + + + + /** + * 401(Unauthorized) + * 비록 HTTP 표준에서는 "미승인(unauthorized)"를 명확히 하고 있지만, + * 의미상 이 응답은 "비인증(unauthenticated)"을 의미합니다. + * 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로를 인증해야 합니다. + */ + + + /** + * 403(Forbidden) + * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. + * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. + */ + + + + /** + * 404(Not Found) + * 서버는 요청받은 리소스를 찾을 수 없습니다. 브라우저에서는 알려지지 않은 URL을 의미합니다. + * 이것은 API에서 종점은 적절하지만 리소스 자체는 존재하지 않음을 의미할 수도 있습니다. + * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. + * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. + */ + PROBLEM_NOT_FOUND(404001, HttpStatus.NOT_FOUND, "problem 데이터를 찾지 못했습니다"), + DATA_INTEGRITY_VIOLATION(40402, HttpStatus.BAD_REQUEST, "데이터 무결 위반하였습니다."), + + AUTHORIZATION_VIOLATION(40403, HttpStatus.BAD_REQUEST, "접근 권한이 없습니다."), + + /** + * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. + */ + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); + + + private final Integer code; + private final HttpStatus httpStatus; + private final String msg; +} diff --git a/src/main/java/stanl_2/final_backend/domain/problem/common/exception/ProblemExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/problem/common/exception/ProblemExceptionResponse.java new file mode 100644 index 00000000..9da737c6 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/problem/common/exception/ProblemExceptionResponse.java @@ -0,0 +1,4 @@ +package stanl_2.final_backend.domain.problem.common.exception; + +public class ProblemExceptionResponse { +} diff --git a/src/main/java/stanl_2/final_backend/domain/problem/common/response/ProblemResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/problem/common/response/ProblemResponseMessage.java new file mode 100644 index 00000000..b6dd5845 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/problem/common/response/ProblemResponseMessage.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.problem.common.response; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +public class ProblemResponseMessage { + private int httpStatus; + private String msg; + private Object result; +} diff --git a/src/main/java/stanl_2/final_backend/domain/problem/common/util/RequestList.java b/src/main/java/stanl_2/final_backend/domain/problem/common/util/RequestList.java new file mode 100644 index 00000000..7771cb6b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/problem/common/util/RequestList.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.problem.common.util; + +import lombok.*; +import org.springframework.data.domain.Pageable; + +@Builder +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class RequestList { + private T data; + private Pageable pageable; +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/problem/query/config/MyBatisConfiguration.java b/src/main/java/stanl_2/final_backend/domain/problem/query/config/MyBatisConfiguration.java new file mode 100644 index 00000000..63c22b3d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/problem/query/config/MyBatisConfiguration.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.problem.query.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Configuration; + +@Configuration("problemMybatisConfiguration") +@MapperScan(basePackages = "stanl_2.final_backend.domain.problem.query.repository") +public class MyBatisConfiguration { +} diff --git a/src/main/java/stanl_2/final_backend/domain/problem/query/controller/ProblemController.java b/src/main/java/stanl_2/final_backend/domain/problem/query/controller/ProblemController.java new file mode 100644 index 00000000..d128ad2b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/problem/query/controller/ProblemController.java @@ -0,0 +1,75 @@ +package stanl_2.final_backend.domain.problem.query.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stanl_2.final_backend.domain.problem.common.response.ProblemResponseMessage; +import stanl_2.final_backend.domain.problem.query.dto.ProblemDTO; +import stanl_2.final_backend.domain.problem.query.dto.ProblemSearchDTO; +import stanl_2.final_backend.domain.problem.query.service.ProblemService; + +@RestController("queryProblemController") +@RequestMapping("/api/v1/problem") +public class ProblemController { + + private final ProblemService problemService; + + @Autowired + public ProblemController(ProblemService problemService) { + this.problemService = problemService; + } + + @Operation(summary = "문제사항 조건별 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = ProblemResponseMessage.class))}) + }) + @GetMapping + public ResponseEntity> getProblems( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(required = false) String title, + @RequestParam(required = false) String memberId, + @RequestParam(required = false) String productId, + @RequestParam(required = false) String customerId, + @RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate + ) + { + Pageable pageable = PageRequest.of(page, size); + ProblemSearchDTO problemsearchDTO = new ProblemSearchDTO(title, memberId, productId, customerId, startDate, endDate); + Page problemDTOPage = problemService.findProblems(pageable, problemsearchDTO); + + return ResponseEntity.ok(problemDTOPage); + } + + @Operation(summary = "문제사항 Id로 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = ProblemResponseMessage.class))}) + }) + @GetMapping("{problemId}") + public ResponseEntity getProblem(@PathVariable String problemId){ + ProblemDTO problemDTO = problemService.findProblem(problemId); + return ResponseEntity.ok(problemDTO); + } + @Operation(summary = "문제사항 엑셀 다운 테스트") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "문제사항 엑셀 다운 테스트 성공", + content = {@Content(schema = @Schema(implementation = ProblemResponseMessage.class))}), + }) + @GetMapping("/excel") + public void exportNotice(HttpServletResponse response){ + + problemService.exportProblemsToExcel(response); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/problem/query/dto/ProblemDTO.java b/src/main/java/stanl_2/final_backend/domain/problem/query/dto/ProblemDTO.java new file mode 100644 index 00000000..5875e433 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/problem/query/dto/ProblemDTO.java @@ -0,0 +1,37 @@ +package stanl_2.final_backend.domain.problem.query.dto; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class ProblemDTO { + private String problemId; + + private String title; + + private String content; + + private String createdAt; + + private String updatedAt; + + private String deletedAt; + + private Boolean active; + + private String status; + + private String customerId; + + private String memberId; + + private String productId; + + private String fileUrl; +} diff --git a/src/main/java/stanl_2/final_backend/domain/problem/query/dto/ProblemExcelDownload.java b/src/main/java/stanl_2/final_backend/domain/problem/query/dto/ProblemExcelDownload.java new file mode 100644 index 00000000..a7c3bd99 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/problem/query/dto/ProblemExcelDownload.java @@ -0,0 +1,33 @@ +package stanl_2.final_backend.domain.problem.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import stanl_2.final_backend.global.excel.ExcelColumnName; + +@Getter +@AllArgsConstructor +public class ProblemExcelDownload { + @ExcelColumnName(name = "문제제목") + private String title; + + @ExcelColumnName(name = "문제내용") + private String content; + + @ExcelColumnName(name = "생성일자") + private String createdAt; + + @ExcelColumnName(name = "수정일자") + private String updatedAt; + + @ExcelColumnName(name = "작성자") + private String memberId; + + @ExcelColumnName(name = "고객이름") + private String customerId; + + @ExcelColumnName(name = "제품명") + private String productId; + + @ExcelColumnName(name = "상태") + private String status; +} diff --git a/src/main/java/stanl_2/final_backend/domain/problem/query/dto/ProblemSearchDTO.java b/src/main/java/stanl_2/final_backend/domain/problem/query/dto/ProblemSearchDTO.java new file mode 100644 index 00000000..5dde1f81 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/problem/query/dto/ProblemSearchDTO.java @@ -0,0 +1,34 @@ +package stanl_2.final_backend.domain.problem.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class ProblemSearchDTO { + private String problemId; + private String title; + private String content; + private String memberId; + private String productId; + private String customerId; + private String createdAt; + private String updatedAt; + private String deletedAt; + private String startDate; + private String endDate; + private String status; + + public ProblemSearchDTO(String title, String memberId, String productId, String customerId, String startDate, String endDate) { + this.title = title; + this.memberId = memberId; + this.productId = productId; + this.customerId = customerId; + this.startDate = startDate; + this.endDate = endDate; + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/problem/query/repository/ProblemMapper.java b/src/main/java/stanl_2/final_backend/domain/problem/query/repository/ProblemMapper.java new file mode 100644 index 00000000..8fb9766e --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/problem/query/repository/ProblemMapper.java @@ -0,0 +1,24 @@ +package stanl_2.final_backend.domain.problem.query.repository; + +import org.apache.ibatis.annotations.Mapper; +import org.springframework.data.repository.query.Param; +import stanl_2.final_backend.domain.notices.query.dto.NoticeExcelDownload; +import stanl_2.final_backend.domain.problem.query.dto.ProblemDTO; +import stanl_2.final_backend.domain.problem.query.dto.ProblemExcelDownload; +import stanl_2.final_backend.domain.problem.query.dto.ProblemSearchDTO; + +import java.util.List; + +@Mapper +public interface ProblemMapper { + List findProblems( + @Param("offset") Integer offset, + @Param("size") Integer size, + @Param("problemSearchDTO") ProblemSearchDTO problemSearchDTO + ); + int findProblemsCount(@Param("problemSearchDTO") ProblemSearchDTO problemSearchDTO); + + ProblemDTO findProblem(@Param("problemId") String problemId); + + List findProblemsForExcel(); +} diff --git a/src/main/java/stanl_2/final_backend/domain/problem/query/service/ProblemService.java b/src/main/java/stanl_2/final_backend/domain/problem/query/service/ProblemService.java new file mode 100644 index 00000000..87f96f06 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/problem/query/service/ProblemService.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.problem.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import stanl_2.final_backend.domain.problem.query.dto.ProblemDTO; +import stanl_2.final_backend.domain.problem.query.dto.ProblemSearchDTO; + +public interface ProblemService { + Page findProblems(Pageable pageable, ProblemSearchDTO problemSearchDTO); + ProblemDTO findProblem(String problemId); + void exportProblemsToExcel(HttpServletResponse response); + +} diff --git a/src/main/java/stanl_2/final_backend/domain/problem/query/service/ProblemServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/problem/query/service/ProblemServiceImpl.java new file mode 100644 index 00000000..394cff5e --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/problem/query/service/ProblemServiceImpl.java @@ -0,0 +1,85 @@ +package stanl_2.final_backend.domain.problem.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.member.command.domain.aggregate.entity.Member; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.domain.notices.query.dto.NoticeExcelDownload; +import stanl_2.final_backend.domain.problem.query.dto.ProblemDTO; +import stanl_2.final_backend.domain.problem.query.dto.ProblemExcelDownload; +import stanl_2.final_backend.domain.problem.query.dto.ProblemSearchDTO; +import stanl_2.final_backend.domain.problem.query.repository.ProblemMapper; +import stanl_2.final_backend.domain.sales_history.common.exception.SalesHistoryCommonException; +import stanl_2.final_backend.domain.sales_history.common.exception.SalesHistoryErrorCode; +import stanl_2.final_backend.global.excel.ExcelUtilsV1; +import stanl_2.final_backend.global.redis.RedisService; + +import java.util.List; + +@Service("queryProblemServiceImpl") +public class ProblemServiceImpl implements ProblemService { + private final ProblemMapper problemMapper; + private final RedisTemplate redisTemplate; + private final RedisService redisService; + private final ExcelUtilsV1 excelUtilsV1; + private final MemberQueryService memberQueryService; + + @Autowired + public ProblemServiceImpl(ProblemMapper problemMapper, RedisTemplate redisTemplate, RedisService redisService, ExcelUtilsV1 excelUtilsV1, MemberQueryService memberQueryService) { + this.problemMapper = problemMapper; + this.redisTemplate = redisTemplate; + this.redisService = redisService; + this.excelUtilsV1 =excelUtilsV1; + this.memberQueryService =memberQueryService; + } + + @Transactional + @Override + public Page findProblems(Pageable pageable, ProblemSearchDTO problemSearchDTO) { + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + String cacheKey = "ProblemCache::offset=" + offset + "::size=" + size +"::title="+problemSearchDTO.getTitle() + + "::memberId="+problemSearchDTO.getMemberId()+"::productId="+problemSearchDTO.getProductId()+ + "::customerId="+problemSearchDTO.getCustomerId()+"::startDate"+problemSearchDTO.getStartDate()+ + "::endDate"+problemSearchDTO.getEndDate(); + // Redis에서 데이터 조회 + List problems = (List) redisTemplate.opsForValue().get(cacheKey); + + if (problems == null) { + System.out.println("데이터베이스에서 문제 데이터 조회 중..."); + problems = problemMapper.findProblems(offset, size, problemSearchDTO); + if (problems != null && !problems.isEmpty()) + redisService.setKeyWithTTL(cacheKey, problems, 30 * 60); + } else { + System.out.println("캐시에서 문제 데이터 조회 중..."); + } + problems.forEach(problem -> { + try { + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.MEMBER_NOT_FOUND); + } + }); + Integer totalElements = problemMapper.findProblemsCount(problemSearchDTO); + return new PageImpl<>(problems, pageable, totalElements); + } + + @Transactional + @Override + public ProblemDTO findProblem(String problemId) { + ProblemDTO problemDTO = problemMapper.findProblem(problemId); + return problemDTO; + } + @Transactional + @Override + public void exportProblemsToExcel(HttpServletResponse response) { + List problemList = problemMapper.findProblemsForExcel(); + + excelUtilsV1.download(ProblemExcelDownload.class, problemList, "problemExcel", response); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/product/command/application/command/controller/ProductController.java b/src/main/java/stanl_2/final_backend/domain/product/command/application/command/controller/ProductController.java new file mode 100644 index 00000000..f84c2b32 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/product/command/application/command/controller/ProductController.java @@ -0,0 +1,47 @@ +package stanl_2.final_backend.domain.product.command.application.command.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import stanl_2.final_backend.domain.product.command.application.command.dto.ProductRegistDTO; +import stanl_2.final_backend.domain.product.command.application.command.service.ProductCommandService; +import stanl_2.final_backend.domain.product.common.response.ProductResponseMessage; + +@RestController("commandProductController") +@RequestMapping("/api/v1/product") + +public class ProductController { + + private final ProductCommandService productCommandService; + + @Autowired + public ProductController(ProductCommandService productCommandService) { + this.productCommandService = productCommandService; + } + + @Operation(summary = "제품 등록") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = ProductResponseMessage.class))}) + }) + @PostMapping("") + public ResponseEntity postTest(@RequestPart("dto") ProductRegistDTO productRegistDTO, + @RequestPart("file") MultipartFile imageUrl){ + productCommandService.registProduct(productRegistDTO, imageUrl); + + return ResponseEntity.ok(ProductResponseMessage.builder() + .httpStatus(200) + .msg("제품 등록 성공") + .result(null) + .build()); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/product/command/application/command/dto/ProductRegistDTO.java b/src/main/java/stanl_2/final_backend/domain/product/command/application/command/dto/ProductRegistDTO.java new file mode 100644 index 00000000..118e6b09 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/product/command/application/command/dto/ProductRegistDTO.java @@ -0,0 +1,18 @@ +package stanl_2.final_backend.domain.product.command.application.command.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class ProductRegistDTO { + private String name; + private String serialNumber; + private String cost; + private String stock; + private String imageUrl; +} diff --git a/src/main/java/stanl_2/final_backend/domain/product/command/application/command/service/ProductCommandService.java b/src/main/java/stanl_2/final_backend/domain/product/command/application/command/service/ProductCommandService.java new file mode 100644 index 00000000..8dde319e --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/product/command/application/command/service/ProductCommandService.java @@ -0,0 +1,12 @@ +package stanl_2.final_backend.domain.product.command.application.command.service; + +import org.springframework.web.multipart.MultipartFile; +import stanl_2.final_backend.domain.product.command.application.command.dto.ProductRegistDTO; + +public interface ProductCommandService { + void modifyProductStock(String productId, Integer numberOfVehicles); + + void deleteProductStock(String productId, Integer numberOfVehicles); + + void registProduct(ProductRegistDTO productRegistDTO, MultipartFile imageUrl); +} diff --git a/src/main/java/stanl_2/final_backend/domain/product/command/application/domain/aggregate/entity/Product.java b/src/main/java/stanl_2/final_backend/domain/product/command/application/domain/aggregate/entity/Product.java new file mode 100644 index 00000000..c9c96020 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/product/command/application/domain/aggregate/entity/Product.java @@ -0,0 +1,80 @@ +package stanl_2.final_backend.domain.product.command.application.domain.aggregate.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@Entity +@Table(name="TB_PRODUCT") +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class Product { + @Id + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name = "prefix", value = "PRO") + ) + @Column(name ="PROD_ID") + private String productId; + + @Column(name ="PROD_SER_NO", nullable = false) + private String serialNumber; + + @Column(name ="PROD_COST", nullable = false) + private Integer cost; + + @Column(name ="PROD_NAME", nullable = false) + private String name; + + @Column(name ="PROD_STCK", nullable = false) + private Integer stock; + + @Column(name ="CREATED_AT", nullable = false) + private String createdAt; + + @Column(name ="UPDATED_AT", nullable = false) + private String updatedAt; + + @Column(name ="DELETED_AT") + private String deletedAt; + + @Column(name ="ACTIVE", nullable = false) + private Boolean active = true; + + @Column(name ="IMAGE_URL") + private String imageUrl; + + public Product(String serialNumber, Integer cost, String name, Integer stock) { + this.serialNumber = serialNumber; + this.cost = cost; + this.name = name; + this.stock = stock; + } + + @PrePersist + private void prePersist() { + this.createdAt = getCurrentTime(); + this.updatedAt = this.createdAt; + } + + @PreUpdate + private void preUpdate() { + this.updatedAt = getCurrentTime(); + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/product/command/application/domain/aggregate/entity/ProductOption.java b/src/main/java/stanl_2/final_backend/domain/product/command/application/domain/aggregate/entity/ProductOption.java new file mode 100644 index 00000000..26702026 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/product/command/application/domain/aggregate/entity/ProductOption.java @@ -0,0 +1,158 @@ +package stanl_2.final_backend.domain.product.command.application.domain.aggregate.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + +@Entity +@Table(name="TB_PRODUCT_OPTION") +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class ProductOption { + + @Id // @Id를 명시적으로 선언합니다. + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name = "prefix", value = "PRO") + ) + @Column(name ="PROD_ID") + private String productId; // 옵션을 위한 식별자 필드 + + @Column(name ="OPT_CNTY", nullable = false) + private Character country; + + @Column(name ="OPT_MNFR", nullable = false) + private Character manufacturer; + + @Column(name ="OPT_VHC_TYPE", nullable = false) + private Character vehicleType; + + @Column(name ="OPT_CHSS", nullable = false) + private Character chassis; + + @Column(name ="OPT_DTIL_TYPE", nullable = false) + private Character detailType; + + @Column(name ="OPT_BODY_TYPE", nullable = false) + private Character bodyType; + + @Column(name ="OPT_SFTY_DVCE", nullable = false) + private Character safetyDevice; + + @Column(name ="OPT_ENGN_CPCT", nullable = false) + private Character engineCapacity; + + @Column(name ="OPT_SCRT_CODE", nullable = false) + private Character secretCode; + + @Column(name ="OPT_PRDC_YEAR", nullable = false) + private Character productYear; + + @Column(name ="OPT_PRDC_PLNT", nullable = false) + private Character productPlant; + + @Column(name ="OPT_ENGN", nullable = false) + private Character engine; + + @Column(name ="OPT_MSSN", nullable = false) + private Character mission; + + @Column(name ="OPT_TRIM", nullable = false) + private Character trim; + + @Column(name ="OPT_XTNL_COLR", nullable = false) + private Character externalColor; + + @Column(name ="OPT_ITNL_COLR", nullable = false) + private Character internalColor; + + @Column(name ="OPT_HUD", nullable = false) + private Character headUpDisplay; + + @Column(name ="OPT_NAVI", nullable = false) + private Character navigation; + + @Column(name ="OPT_DRVE_WISE", nullable = false) + private Character driveWise; + + @Column(name ="OPT_SMRT_CNCT", nullable = false) + private Character smartConnect; + + @Column(name ="OPT_STYL", nullable = false) + private Character style; + + @Column(name ="OPT_MY_CFRT_PCKG", nullable = false) + private Character myComfortPackage; + + @Column(name ="OPT_OTDR_PCKG", nullable = false) + private Character outdoorPackage; + + @Column(name ="OPT_SUN_ROOF", nullable = false) + private Character sunRoof; + + @Column(name ="OPT_SOND", nullable = false) + private Character sound; + + @Column(name ="ACTIVE") + private Boolean active = true; + + public ProductOption(Character country, + Character manufacturer, + Character vehicleType, + Character chassis, + Character detailType, + Character bodyType, + Character safetyDevice, + Character engineCapacity, + Character secretCode, + Character productYear, + Character productPlant, + Character engine, + Character mission, + Character trim, + Character externalColor, + Character internalColor, + Character headUpDisplay, + Character navigation, + Character driveWise, + Character smartConnect, + Character style, + Character myComfortPackage, + Character outdoorPackage, + Character sunRoof, + Character sound) { + + this.country = country; + this.manufacturer = manufacturer; + this.vehicleType = vehicleType; + this.chassis = chassis; + this.detailType = detailType; + this.bodyType = bodyType; + this.safetyDevice = safetyDevice; + this.engineCapacity = engineCapacity; + this.secretCode = secretCode; + this.productYear = productYear; + this.productPlant = productPlant; + this.engine = engine; + this.mission = mission; + this.trim = trim; + this.externalColor = externalColor; + this.internalColor = internalColor; + this.headUpDisplay = headUpDisplay; + this.navigation = navigation; + this.driveWise = driveWise; + this.smartConnect = smartConnect; + this.style = style; + this.myComfortPackage = myComfortPackage; + this.outdoorPackage = outdoorPackage; + this.sunRoof = sunRoof; + this.sound = sound; + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/product/command/application/domain/repository/ProductOptionRepository.java b/src/main/java/stanl_2/final_backend/domain/product/command/application/domain/repository/ProductOptionRepository.java new file mode 100644 index 00000000..e7b37805 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/product/command/application/domain/repository/ProductOptionRepository.java @@ -0,0 +1,7 @@ +package stanl_2.final_backend.domain.product.command.application.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import stanl_2.final_backend.domain.product.command.application.domain.aggregate.entity.ProductOption; + +public interface ProductOptionRepository extends JpaRepository { +} diff --git a/src/main/java/stanl_2/final_backend/domain/product/command/application/domain/repository/ProductRepository.java b/src/main/java/stanl_2/final_backend/domain/product/command/application/domain/repository/ProductRepository.java new file mode 100644 index 00000000..972507f7 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/product/command/application/domain/repository/ProductRepository.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.product.command.application.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import stanl_2.final_backend.domain.product.command.application.domain.aggregate.entity.Product; + +@Repository +public interface ProductRepository extends JpaRepository { +} diff --git a/src/main/java/stanl_2/final_backend/domain/product/command/application/domain/service/ProductCommandServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/product/command/application/domain/service/ProductCommandServiceImpl.java new file mode 100644 index 00000000..f5a62f85 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/product/command/application/domain/service/ProductCommandServiceImpl.java @@ -0,0 +1,59 @@ +package stanl_2.final_backend.domain.product.command.application.domain.service; + +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import stanl_2.final_backend.domain.product.command.application.command.dto.ProductRegistDTO; +import stanl_2.final_backend.domain.product.command.application.command.service.ProductCommandService; +import stanl_2.final_backend.domain.product.command.application.domain.aggregate.entity.Product; +import stanl_2.final_backend.domain.product.command.application.domain.repository.ProductRepository; +import stanl_2.final_backend.domain.product.common.exception.ProductCommonException; +import stanl_2.final_backend.domain.product.common.exception.ProductErrorCode; +import stanl_2.final_backend.domain.s3.command.application.service.S3FileService; + +@Service +public class ProductCommandServiceImpl implements ProductCommandService { + + private final ProductRepository productRepository; + private final ModelMapper modelMapper; + private final S3FileService s3FileService; + + public ProductCommandServiceImpl(ProductRepository productRepository, ModelMapper modelMapper, S3FileService s3FileService) { + this.productRepository = productRepository; + this.modelMapper = modelMapper; + this.s3FileService = s3FileService; + } + + @Override + @Transactional + public void modifyProductStock(String productId, Integer numberOfVehicles) { + Product product = productRepository.findById(productId) + .orElseThrow(() -> new ProductCommonException(ProductErrorCode.PRODUCT_NOT_FOUND)); + + product.setStock(product.getStock() - numberOfVehicles); + + productRepository.save(product); + } + + @Override + @Transactional + public void deleteProductStock(String productId, Integer numberOfVehicles) { + Product product = productRepository.findById(productId) + .orElseThrow(() -> new ProductCommonException(ProductErrorCode.PRODUCT_NOT_FOUND)); + + product.setStock(product.getStock() + numberOfVehicles); + productRepository.save(product); + } + + @Override + @Transactional + public void registProduct(ProductRegistDTO productRegistDTO, MultipartFile imageUrl) { + Product newProduct = modelMapper.map(productRegistDTO, Product.class); + + newProduct.setImageUrl(s3FileService.uploadOneFile(imageUrl)); + + productRepository.save(newProduct); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/product/common/exception/ProductCommonException.java b/src/main/java/stanl_2/final_backend/domain/product/common/exception/ProductCommonException.java new file mode 100644 index 00000000..d1d51f8d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/product/common/exception/ProductCommonException.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.product.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class ProductCommonException extends RuntimeException { + private final ProductErrorCode errorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.errorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/common/exception/ErrorCode.java b/src/main/java/stanl_2/final_backend/domain/product/common/exception/ProductErrorCode.java similarity index 90% rename from src/main/java/stanl_2/final_backend/domain/schedule/common/exception/ErrorCode.java rename to src/main/java/stanl_2/final_backend/domain/product/common/exception/ProductErrorCode.java index 295cb79f..35e41847 100644 --- a/src/main/java/stanl_2/final_backend/domain/schedule/common/exception/ErrorCode.java +++ b/src/main/java/stanl_2/final_backend/domain/product/common/exception/ProductErrorCode.java @@ -1,4 +1,4 @@ -package stanl_2.final_backend.domain.schedule.common.exception; +package stanl_2.final_backend.domain.product.common.exception; import lombok.AllArgsConstructor; import lombok.Getter; @@ -6,7 +6,7 @@ @Getter @AllArgsConstructor -public enum ErrorCode { +public enum ProductErrorCode { /** * 400(Bad Request) @@ -38,7 +38,7 @@ public enum ErrorCode { * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. */ - SCHEDULE_NOT_FOUND(40401, HttpStatus.NOT_FOUND, "해당하는 일정표를 찾을 수 없습니다."), + PRODUCT_NOT_FOUND(40401, HttpStatus.NOT_FOUND, "제품을 찾을 수 없습니다."), /** diff --git a/src/main/java/stanl_2/final_backend/domain/product/common/exception/ProductExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/product/common/exception/ProductExceptionResponse.java new file mode 100644 index 00000000..a73fac08 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/product/common/exception/ProductExceptionResponse.java @@ -0,0 +1,22 @@ +package stanl_2.final_backend.domain.product.common.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class ProductExceptionResponse { + private final Integer code; + private final String msg; + private final HttpStatus httpStatus; + + public ProductExceptionResponse(ProductErrorCode errorCode) { + this.code = errorCode.getCode(); + this.msg = errorCode.getMsg(); + this.httpStatus = errorCode.getHttpStatus(); + } + + public static ProductExceptionResponse of(ProductErrorCode errorCode) { + return new ProductExceptionResponse(errorCode); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/product/common/response/ProductResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/product/common/response/ProductResponseMessage.java new file mode 100644 index 00000000..108883e2 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/product/common/response/ProductResponseMessage.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.product.common.response; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +public class ProductResponseMessage { + private int httpStatus; + private String msg; + private Object result; +} diff --git a/src/main/java/stanl_2/final_backend/domain/product/common/util/RequestList.java b/src/main/java/stanl_2/final_backend/domain/product/common/util/RequestList.java new file mode 100644 index 00000000..9a861d2c --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/product/common/util/RequestList.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.product.common.util; + +import lombok.*; +import org.springframework.data.domain.Pageable; + +@Builder +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class RequestList { + private T data; + private Pageable pageable; +} diff --git a/src/main/java/stanl_2/final_backend/domain/product/query/controller/ProductController.java b/src/main/java/stanl_2/final_backend/domain/product/query/controller/ProductController.java new file mode 100644 index 00000000..f0f8db9c --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/product/query/controller/ProductController.java @@ -0,0 +1,127 @@ +package stanl_2.final_backend.domain.product.query.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stanl_2.final_backend.domain.A_sample.common.response.SampleResponseMessage; +import stanl_2.final_backend.domain.center.common.response.CenterResponseMessage; +import stanl_2.final_backend.domain.product.common.response.ProductResponseMessage; +import stanl_2.final_backend.domain.product.query.dto.ProductSearchRequestDTO; +import stanl_2.final_backend.domain.product.query.dto.ProductSelectAllDTO; +import stanl_2.final_backend.domain.product.query.dto.ProductSelectIdDTO; +import stanl_2.final_backend.domain.product.query.service.ProductQueryService; + +import java.util.HashMap; +import java.util.Map; + +@RestController("queryProductController") +@RequestMapping("/api/v1/product") +public class ProductController { + + private final ProductQueryService productQueryService; + + @Autowired + public ProductController(ProductQueryService productQueryService) { + this.productQueryService = productQueryService; + } + + @Operation(summary = "제품 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = ProductResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("") + public ResponseEntity getProductAll(@PageableDefault(size = 10) Pageable pageable, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder){ + + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + Page responseProducts = productQueryService.selectAll(pageable); + + return ResponseEntity.ok(ProductResponseMessage.builder() + .httpStatus(200) + .msg("제품 조회 성공") + .result(responseProducts) + .build()); + } + @Operation(summary = "제품 상세조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = ProductResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("{productId}") + public ResponseEntity getProductById(@PathVariable("productId") String productId){ + + ProductSelectIdDTO productSelectIdDTO = productQueryService.selectByProductId(productId); + + return ResponseEntity.ok(ProductResponseMessage.builder() + .httpStatus(200) + .msg("제품 상세조회 성공") + .result(productSelectIdDTO) + .build()); + } + @Operation(summary = "제품 검색") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = ProductResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/search") + public ResponseEntity getProductBySearch(@RequestParam Map params + ,@PageableDefault(size = 10) Pageable pageable){ + + ProductSearchRequestDTO productSearchRequestDTO = new ProductSearchRequestDTO(); + productSearchRequestDTO.setProductId(params.get("productId")); + productSearchRequestDTO.setName(params.get("name")); + productSearchRequestDTO.setSerialNumber(params.get("serialNumber")); + + String sortField = params.get("sortField"); + String sortOrder = params.get("sortOrder"); + + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + Page responseProducts = productQueryService.selectProductBySearch(productSearchRequestDTO, pageable); + + return ResponseEntity.ok(ProductResponseMessage.builder() + .httpStatus(200) + .msg("제품 검색 성공") + .result(responseProducts) + .build()); + } + + @Operation(summary = "제품 엑셀 다운") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "제품 엑셀 다운 성공", + content = {@Content(schema = @Schema(implementation = ProductResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/excel") + public void exportProduct(HttpServletResponse response){ + + productQueryService.exportProductsToExcel(response); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/product/query/dto/ProductExcelDownload.java b/src/main/java/stanl_2/final_backend/domain/product/query/dto/ProductExcelDownload.java new file mode 100644 index 00000000..cf60069f --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/product/query/dto/ProductExcelDownload.java @@ -0,0 +1,24 @@ +package stanl_2.final_backend.domain.product.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import stanl_2.final_backend.global.excel.ExcelColumnName; + +@Getter +@AllArgsConstructor +public class ProductExcelDownload { + @ExcelColumnName(name = "제품번호") + private String productId; + + @ExcelColumnName(name = "제품 이름") + private String name; + + @ExcelColumnName(name = "일련번호") + private String serialNumber; + + @ExcelColumnName(name = "차량가액") + private Integer cost; + + @ExcelColumnName(name = "재고 수") + private String stock; +} diff --git a/src/main/java/stanl_2/final_backend/domain/product/query/dto/ProductSearchRequestDTO.java b/src/main/java/stanl_2/final_backend/domain/product/query/dto/ProductSearchRequestDTO.java new file mode 100644 index 00000000..39cebaef --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/product/query/dto/ProductSearchRequestDTO.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.product.query.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@ToString +public class ProductSearchRequestDTO { + private String productId; + private String name; + private String serialNumber; +} diff --git a/src/main/java/stanl_2/final_backend/domain/product/query/dto/ProductSelectAllDTO.java b/src/main/java/stanl_2/final_backend/domain/product/query/dto/ProductSelectAllDTO.java new file mode 100644 index 00000000..d905e17c --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/product/query/dto/ProductSelectAllDTO.java @@ -0,0 +1,15 @@ +package stanl_2.final_backend.domain.product.query.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class ProductSelectAllDTO { + private String productId; + private String name; + private String serialNumber; + private String cost; + private String stock; +} diff --git a/src/main/java/stanl_2/final_backend/domain/product/query/dto/ProductSelectIdDTO.java b/src/main/java/stanl_2/final_backend/domain/product/query/dto/ProductSelectIdDTO.java new file mode 100644 index 00000000..1d7067a5 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/product/query/dto/ProductSelectIdDTO.java @@ -0,0 +1,44 @@ +package stanl_2.final_backend.domain.product.query.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@ToString +public class ProductSelectIdDTO { + private String productId; + private String name; + private String serialNumber; + private String cost; + private String stock; + private String imageUrl; + + /* 설명. product option 받아올 필드 */ + private Character country; + private Character manufacturer; + private Character vehicleType; + private Character chassis; + private Character detailType; + private Character bodyType; + private Character safetyDevice; + private Character engineCapacity; + private Character secretCode; + private Character productYear; + private Character productPlant; + private Character engine; + private Character mission; + private Character trim; + private Character externalColor; + private Character internalColor; + private Character headUpDisplay; + private Character navigation; + private Character driveWise; + private Character smartConnect; + private Character style; + private Character myComfortPackage; + private Character outdoorPackage; + private Character sunRoof; + private Character sound; +} diff --git a/src/main/java/stanl_2/final_backend/domain/product/query/repository/ProductMapper.java b/src/main/java/stanl_2/final_backend/domain/product/query/repository/ProductMapper.java new file mode 100644 index 00000000..9a546b8a --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/product/query/repository/ProductMapper.java @@ -0,0 +1,38 @@ +package stanl_2.final_backend.domain.product.query.repository; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import stanl_2.final_backend.domain.A_sample.query.dto.SampleExcelDownload; +import stanl_2.final_backend.domain.center.query.dto.CenterSearchRequestDTO; +import stanl_2.final_backend.domain.product.common.util.RequestList; +import stanl_2.final_backend.domain.product.query.dto.ProductExcelDownload; +import stanl_2.final_backend.domain.product.query.dto.ProductSearchRequestDTO; +import stanl_2.final_backend.domain.product.query.dto.ProductSelectAllDTO; +import stanl_2.final_backend.domain.product.query.dto.ProductSelectIdDTO; + +import java.util.List; +import java.util.Map; + +@Mapper +public interface ProductMapper { + List findProductAll(@Param("size") int size + , @Param("offset") int offset, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + int findProductCount(); + + ProductSelectIdDTO findProductById(String productId); + + List findProductBySearch(@Param("size") int size + , @Param("offset") int offset + , @Param("productSearchRequestDTO") ProductSearchRequestDTO productSearchRequestDTO, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + int findProductBySearchCount(@Param("productSearchRequestDTO") ProductSearchRequestDTO productSearchRequestDTO); + + ProductSelectIdDTO findProductBySerialNumber(String serialNumber); + + List findProductsForExcel(); +} diff --git a/src/main/java/stanl_2/final_backend/domain/product/query/service/ProductQueryService.java b/src/main/java/stanl_2/final_backend/domain/product/query/service/ProductQueryService.java new file mode 100644 index 00000000..9b6dd57d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/product/query/service/ProductQueryService.java @@ -0,0 +1,25 @@ +package stanl_2.final_backend.domain.product.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.product.query.dto.ProductSearchRequestDTO; +import stanl_2.final_backend.domain.product.query.dto.ProductSelectAllDTO; +import stanl_2.final_backend.domain.product.query.dto.ProductSelectIdDTO; + +import java.util.Map; + +@Service +public interface ProductQueryService { + Page selectAll(Pageable pageable); + + ProductSelectIdDTO selectByProductId(String productId); + + Page selectProductBySearch(ProductSearchRequestDTO productSearchRequestDTO, Pageable pageable); + + ProductSelectIdDTO selectByProductSerialNumber(String id); + + void exportProductsToExcel(HttpServletResponse response); +} diff --git a/src/main/java/stanl_2/final_backend/domain/product/query/service/ProductQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/product/query/service/ProductQueryServiceImpl.java new file mode 100644 index 00000000..9bc30ffc --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/product/query/service/ProductQueryServiceImpl.java @@ -0,0 +1,125 @@ +package stanl_2.final_backend.domain.product.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.A_sample.query.dto.SampleExcelDownload; +import stanl_2.final_backend.domain.product.common.exception.ProductCommonException; +import stanl_2.final_backend.domain.product.common.exception.ProductErrorCode; +import stanl_2.final_backend.domain.product.common.util.RequestList; +import stanl_2.final_backend.domain.product.query.dto.ProductExcelDownload; +import stanl_2.final_backend.domain.product.query.dto.ProductSearchRequestDTO; +import stanl_2.final_backend.domain.product.query.dto.ProductSelectAllDTO; +import stanl_2.final_backend.domain.product.query.dto.ProductSelectIdDTO; +import stanl_2.final_backend.domain.product.query.repository.ProductMapper; +import stanl_2.final_backend.global.excel.ExcelUtilsV1; + +import java.util.List; +import java.util.Map; + +@Service +public class ProductQueryServiceImpl implements ProductQueryService { + + private final ProductMapper productMapper; + private final ExcelUtilsV1 excelUtilsV1; + + @Autowired + public ProductQueryServiceImpl(ProductMapper productMapper, ExcelUtilsV1 excelUtilsV1) { + this.productMapper = productMapper; + this.excelUtilsV1 = excelUtilsV1; + } + + @Override + @Transactional + public Page selectAll(Pageable pageable) { + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + List productList = productMapper.findProductAll(size, offset, sortField, sortOrder); + + int total = productMapper.findProductCount(); + + if(productList == null || total == 0) { + throw new ProductCommonException(ProductErrorCode.PRODUCT_NOT_FOUND); + } + + return new PageImpl<>(productList, pageable, total); + + } + + @Override + @Transactional + public ProductSelectIdDTO selectByProductId(String productId) { + ProductSelectIdDTO productSelectIdDTO = productMapper.findProductById(productId); + + if(productSelectIdDTO == null) { + throw new ProductCommonException(ProductErrorCode.PRODUCT_NOT_FOUND); + } + + return productSelectIdDTO; + } + + @Override + @Transactional + public Page selectProductBySearch(ProductSearchRequestDTO productSearchRequestDTO, Pageable pageable) { + + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + List productList = productMapper.findProductBySearch(size, offset, productSearchRequestDTO, sortField, sortOrder); + + int total = productMapper.findProductBySearchCount(productSearchRequestDTO); + + if(productList == null || total == 0) { + throw new ProductCommonException(ProductErrorCode.PRODUCT_NOT_FOUND); + } + + return new PageImpl<>(productList, pageable, total); + } + + @Override + @Transactional + public ProductSelectIdDTO selectByProductSerialNumber(String id) { + + ProductSelectIdDTO productSelectIdDTO = productMapper.findProductBySerialNumber(id); + + if(productSelectIdDTO == null) { + throw new ProductCommonException(ProductErrorCode.PRODUCT_NOT_FOUND); + } + + return productSelectIdDTO; + } + + @Override + @Transactional + public void exportProductsToExcel(HttpServletResponse response) { + List productList = productMapper.findProductsForExcel(); + + if(productList == null) { + throw new ProductCommonException(ProductErrorCode.PRODUCT_NOT_FOUND); + } + + excelUtilsV1.download(ProductExcelDownload.class, productList, "productExcel", response); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/promotion/command/application/controller/PromotionController.java b/src/main/java/stanl_2/final_backend/domain/promotion/command/application/controller/PromotionController.java new file mode 100644 index 00000000..259e591a --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/promotion/command/application/controller/PromotionController.java @@ -0,0 +1,128 @@ +package stanl_2.final_backend.domain.promotion.command.application.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.domain.problem.command.domain.aggregate.entity.Problem; +import stanl_2.final_backend.domain.promotion.command.application.dto.PromotionModifyDTO; +import stanl_2.final_backend.domain.promotion.command.application.dto.PromotionRegistDTO; +import stanl_2.final_backend.domain.promotion.command.application.service.PromotionCommandService; +import stanl_2.final_backend.domain.promotion.command.domain.aggregate.entity.Promotion; +import stanl_2.final_backend.domain.promotion.common.response.PromotionResponseMessage; +import stanl_2.final_backend.domain.s3.command.domain.service.S3FileServiceImpl; + +import java.security.GeneralSecurityException; +import java.security.Principal; + +@RestController("commandPromotionController") +@RequestMapping("/api/v1/promotion") +public class PromotionController { + private final PromotionCommandService promotionCommandService; + private final AuthQueryService authQueryService; + private final S3FileServiceImpl s3FileService; + private final MemberQueryService memberQueryService; + + private final ModelMapper modelMapper; + + @Autowired + public PromotionController(PromotionCommandService promotionCommandService, AuthQueryService authQueryService, S3FileServiceImpl s3FileService,MemberQueryService memberQueryService,ModelMapper modelMapper) { + this.promotionCommandService = promotionCommandService; + this.authQueryService =authQueryService; + this.s3FileService = s3FileService; + this.memberQueryService =memberQueryService; + this.modelMapper =modelMapper; + } + + @Operation(summary = "프로모션 작성") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = PromotionResponseMessage.class))}) + }) + @PostMapping("") + public ResponseEntity postNotice(@RequestPart("dto") PromotionRegistDTO promotionRegistDTO, // JSON 데이터 + @RequestPart(value = "file", required = false) MultipartFile file, + Principal principal) throws GeneralSecurityException { + String memberLoginId = principal.getName(); + promotionRegistDTO.setMemberLoginId(memberLoginId); + if (file != null && !file.isEmpty()) { + promotionRegistDTO.setFileUrl(s3FileService.uploadOneFile(file)); + }else if(file==null || file.isEmpty()){ + promotionRegistDTO.setFileUrl(null); + } else { + promotionRegistDTO.setFileUrl(null); + } + promotionCommandService.registerPromotion(promotionRegistDTO,principal); + return ResponseEntity.ok(PromotionResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(null) + .build()); + } + @Operation(summary = "프로모션 수정") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = PromotionResponseMessage.class))}) + }) + @PutMapping("{promotionId}") + public ResponseEntity modifyNotice( + @PathVariable String promotionId, + @RequestPart("dto") PromotionModifyDTO promotionModifyDTO, + @RequestPart(value = "file", required = false) MultipartFile file, + Principal principal) throws GeneralSecurityException { + String memberLoginId = principal.getName(); + String memberId = authQueryService.selectMemberIdByLoginId(memberLoginId); + memberId=memberQueryService.selectNameById(memberId); + promotionModifyDTO.setMemberId(memberId); + promotionModifyDTO.setMemberLoginId(memberLoginId); + promotionModifyDTO.setContent(promotionModifyDTO.getContent()); + Promotion updatePromotion = modelMapper.map(promotionModifyDTO, Promotion.class); + System.out.println("1."+updatePromotion.getFileUrl()); + if(promotionModifyDTO.getFileUrl()==null){ + System.out.println("테스트중"); + promotionModifyDTO.setFileUrl(updatePromotion.getFileUrl()); + } + if (file != null && !file.isEmpty()) { + System.out.println("1번"); + promotionModifyDTO.setFileUrl(s3FileService.uploadOneFile(file)); + }else if(file==null || file.isEmpty()) { + System.out.println("2번"); + promotionModifyDTO.setFileUrl(updatePromotion.getFileUrl()); + } else { + System.out.println("3번"); + promotionModifyDTO.setFileUrl(s3FileService.uploadOneFile(file)); + } + promotionCommandService.modifyPromotion(promotionId,promotionModifyDTO,principal); + + return ResponseEntity.ok(PromotionResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(promotionModifyDTO) + .build()); + } + + @Operation(summary = "프로모션 삭제") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = PromotionResponseMessage.class))}) + }) + @DeleteMapping("{promotionId}") + public ResponseEntity deleteNotice(@PathVariable String promotionId, Principal principal) throws GeneralSecurityException { + + promotionCommandService.deletePromotion(promotionId,principal); + + return ResponseEntity.ok(PromotionResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(null) + .build()); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/promotion/command/application/dto/PromotionModifyDTO.java b/src/main/java/stanl_2/final_backend/domain/promotion/command/application/dto/PromotionModifyDTO.java new file mode 100644 index 00000000..9fd998d8 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/promotion/command/application/dto/PromotionModifyDTO.java @@ -0,0 +1,21 @@ +package stanl_2.final_backend.domain.promotion.command.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.stereotype.Component; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class PromotionModifyDTO { + + private String title; + private String memberId; + private String content; + private String fileUrl; + private String memberLoginId; + +} diff --git a/src/main/java/stanl_2/final_backend/domain/promotion/command/application/dto/PromotionRegistDTO.java b/src/main/java/stanl_2/final_backend/domain/promotion/command/application/dto/PromotionRegistDTO.java new file mode 100644 index 00000000..130db466 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/promotion/command/application/dto/PromotionRegistDTO.java @@ -0,0 +1,23 @@ +package stanl_2.final_backend.domain.promotion.command.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class PromotionRegistDTO { + + private String title; + + private String content; + + private String memberId; + + private String memberLoginId; + + private String fileUrl; +} diff --git a/src/main/java/stanl_2/final_backend/domain/promotion/command/application/service/PromotionCommandService.java b/src/main/java/stanl_2/final_backend/domain/promotion/command/application/service/PromotionCommandService.java new file mode 100644 index 00000000..db34fc6e --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/promotion/command/application/service/PromotionCommandService.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.promotion.command.application.service; + +import stanl_2.final_backend.domain.promotion.command.application.dto.PromotionModifyDTO; +import stanl_2.final_backend.domain.promotion.command.application.dto.PromotionRegistDTO; + +import java.security.GeneralSecurityException; +import java.security.Principal; + +public interface PromotionCommandService { + + void registerPromotion(PromotionRegistDTO promotionRegistDTO, Principal principal) throws GeneralSecurityException; + + PromotionModifyDTO modifyPromotion(String promotionId, PromotionModifyDTO promotionModifyDTO, Principal principal) throws GeneralSecurityException; + + void deletePromotion(String promotionId, Principal principal) throws GeneralSecurityException; +} diff --git a/src/main/java/stanl_2/final_backend/domain/promotion/command/domain/aggregate/entity/Promotion.java b/src/main/java/stanl_2/final_backend/domain/promotion/command/domain/aggregate/entity/Promotion.java new file mode 100644 index 00000000..1080a460 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/promotion/command/domain/aggregate/entity/Promotion.java @@ -0,0 +1,73 @@ +package stanl_2.final_backend.domain.promotion.command.domain.aggregate.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@Entity +@Table(name="TB_PROMOTION") +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class Promotion { + @Id + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name="prefix", value = "PROM") + ) + @Column(name = "PRM_ID") + private String promotionId; + + @Column(name = "PRM_TTL", nullable = false) + private String title; + + @Column(name = "PRM_CONT",columnDefinition = "TEXT", nullable = false) + private String content; + + @Column(name = "CREATED_AT", nullable = false, updatable = false, length=19) + private String createdAt; + + @Column(name = "UPDATED_AT", nullable = false, length=19) + private String updatedAt; + + @Column(name = "DELETED_AT", length=19) + private String deletedAt; + + @Column(name = "ACTIVE", nullable = false) + private Boolean active = true; + + @Column(name = "MEM_ID", nullable = false) + private String memberId; + + @Column(name = "FILE_URL") + private String fileUrl; + + @PrePersist + private void prePersist() { + this.createdAt = getCurrentTime(); + this.updatedAt = this.createdAt; + } + + // Update 되기 전에 실행 + @PreUpdate + private void preUpdate() { + this.updatedAt = getCurrentTime(); + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + + +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/promotion/command/domain/aggregate/repository/PromotionRepository.java b/src/main/java/stanl_2/final_backend/domain/promotion/command/domain/aggregate/repository/PromotionRepository.java new file mode 100644 index 00000000..f1256a0a --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/promotion/command/domain/aggregate/repository/PromotionRepository.java @@ -0,0 +1,7 @@ +package stanl_2.final_backend.domain.promotion.command.domain.aggregate.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import stanl_2.final_backend.domain.promotion.command.domain.aggregate.entity.Promotion; + +public interface PromotionRepository extends JpaRepository { +} diff --git a/src/main/java/stanl_2/final_backend/domain/promotion/command/domain/aggregate/service/PromotionServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/promotion/command/domain/aggregate/service/PromotionServiceImpl.java new file mode 100644 index 00000000..5f347db5 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/promotion/command/domain/aggregate/service/PromotionServiceImpl.java @@ -0,0 +1,132 @@ +package stanl_2.final_backend.domain.promotion.command.domain.aggregate.service; + +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.domain.notices.common.exception.NoticeCommonException; +import stanl_2.final_backend.domain.notices.common.exception.NoticeErrorCode; +import stanl_2.final_backend.domain.problem.common.exception.ProblemCommonException; +import stanl_2.final_backend.domain.problem.common.exception.ProblemErrorCode; +import stanl_2.final_backend.domain.promotion.command.application.dto.PromotionModifyDTO; +import stanl_2.final_backend.domain.promotion.command.application.dto.PromotionRegistDTO; +import stanl_2.final_backend.domain.promotion.command.application.service.PromotionCommandService; +import stanl_2.final_backend.domain.promotion.command.domain.aggregate.entity.Promotion; +import stanl_2.final_backend.domain.promotion.command.domain.aggregate.repository.PromotionRepository; +import stanl_2.final_backend.domain.promotion.common.exception.PromotionCommonException; +import stanl_2.final_backend.domain.promotion.common.exception.PromotionErrorCode; +import stanl_2.final_backend.global.redis.RedisService; + +import java.security.GeneralSecurityException; +import java.security.Principal; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@Service("commandPromotionService") +public class PromotionServiceImpl implements PromotionCommandService { + + private final RedisService redisService; + private final PromotionRepository promotionRepository; + + private final AuthQueryService authQueryService; + private final ModelMapper modelMapper; + private final MemberQueryService memberQueryService; + + @Autowired + public PromotionServiceImpl(PromotionRepository promotionRepository, ModelMapper modelMapper,RedisService redisService, AuthQueryService authQueryService, MemberQueryService memberQueryService) { + this.redisService = redisService; + this.promotionRepository = promotionRepository; + this.authQueryService = authQueryService; + this.modelMapper = modelMapper; + this.memberQueryService =memberQueryService; + } + private String getCurrentTimestamp() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + + @Transactional + @Override + public void registerPromotion(PromotionRegistDTO promotionRegistDTO, Principal principal) throws GeneralSecurityException { + redisService.clearPromotionCache(); + String memberId = authQueryService.selectMemberIdByLoginId(promotionRegistDTO.getMemberLoginId()); + memberId=memberQueryService.selectNameById(memberId); + promotionRegistDTO.setMemberId(memberId); + try { + Promotion promotion =modelMapper.map(promotionRegistDTO,Promotion.class); + promotionRepository.save(promotion); + } catch (DataIntegrityViolationException e){ + throw new PromotionCommonException(PromotionErrorCode.DATA_INTEGRITY_VIOLATION); + }catch (Exception e) { + // 서버 오류 + throw new NoticeCommonException(NoticeErrorCode.INTERNAL_SERVER_ERROR); + } + } + + @Transactional + @Override + public PromotionModifyDTO modifyPromotion(String promotionId, PromotionModifyDTO promotionModifyDTO, Principal principal) throws GeneralSecurityException { + redisService.clearPromotionCache(); + String memberId=authQueryService.selectMemberIdByLoginId(promotionModifyDTO.getMemberLoginId()); + memberId=memberQueryService.selectNameById(memberId); + + Promotion promotion = promotionRepository.findById(promotionId) + .orElseThrow(() -> new PromotionCommonException(PromotionErrorCode.PROMOTION_NOT_FOUND)); + if(!promotion.getMemberId().equals(memberId)){ + throw new ProblemCommonException(ProblemErrorCode.AUTHORIZATION_VIOLATION); + } + try { + Promotion updatePromotion = modelMapper.map(promotionModifyDTO, Promotion.class); + updatePromotion.setPromotionId(promotion.getPromotionId()); + updatePromotion.setMemberId(promotion.getMemberId()); + updatePromotion.setCreatedAt(promotion.getCreatedAt()); + updatePromotion.setActive(promotion.getActive()); + + promotionRepository.save(updatePromotion); + + PromotionModifyDTO promotionModify = modelMapper.map(updatePromotion,PromotionModifyDTO.class); + + return promotionModify; + } catch (DataIntegrityViolationException e) { + // 데이터 무결성 위반 예외 처리 + throw new PromotionCommonException(PromotionErrorCode.DATA_INTEGRITY_VIOLATION); + } catch (Exception e) { + // 서버 오류 + throw new PromotionCommonException(PromotionErrorCode.INTERNAL_SERVER_ERROR); + } + } + + @Transactional + @Override + public void deletePromotion(String promotionId, Principal principal) throws GeneralSecurityException { + redisService.clearPromotionCache(); + String loginId= principal.getName(); + String memberId = authQueryService.selectMemberIdByLoginId(loginId); + memberId=memberQueryService.selectNameById(memberId); + Promotion promotion = promotionRepository.findById(promotionId) + .orElseThrow(()-> new PromotionCommonException(PromotionErrorCode.PROMOTION_NOT_FOUND)); + + if(!promotion.getMemberId().equals(memberId)){ + // 권한 오류 + throw new PromotionCommonException(PromotionErrorCode.AUTHORIZATION_VIOLATION); + } + else { + promotion.setActive(false); + promotion.setDeletedAt(getCurrentTimestamp()); + } + try { + promotionRepository.save(promotion); + } catch (DataIntegrityViolationException e) { + // 데이터 무결성 위반 예외 처리 + throw new PromotionCommonException(PromotionErrorCode.DATA_INTEGRITY_VIOLATION); + } catch (Exception e) { + // 서버 오류 + throw new PromotionCommonException(PromotionErrorCode.INTERNAL_SERVER_ERROR); + } + + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/promotion/common/exception/PromotionCommonException.java b/src/main/java/stanl_2/final_backend/domain/promotion/common/exception/PromotionCommonException.java new file mode 100644 index 00000000..e4f79a35 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/promotion/common/exception/PromotionCommonException.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.promotion.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class PromotionCommonException extends RuntimeException { + private final PromotionErrorCode promotionErrorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.promotionErrorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/promotion/common/exception/PromotionErrorCode.java b/src/main/java/stanl_2/final_backend/domain/promotion/common/exception/PromotionErrorCode.java new file mode 100644 index 00000000..e853787d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/promotion/common/exception/PromotionErrorCode.java @@ -0,0 +1,54 @@ +package stanl_2.final_backend.domain.promotion.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum PromotionErrorCode { + + /** + * 400(Bad Request) + * 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. + */ + + + + /** + * 401(Unauthorized) + * 비록 HTTP 표준에서는 "미승인(unauthorized)"를 명확히 하고 있지만, + * 의미상 이 응답은 "비인증(unauthenticated)"을 의미합니다. + * 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로를 인증해야 합니다. + */ + + + /** + * 403(Forbidden) + * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. + * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. + */ + + + + /** + * 404(Not Found) + * 서버는 요청받은 리소스를 찾을 수 없습니다. 브라우저에서는 알려지지 않은 URL을 의미합니다. + * 이것은 API에서 종점은 적절하지만 리소스 자체는 존재하지 않음을 의미할 수도 있습니다. + * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. + * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. + */ + PROMOTION_NOT_FOUND(404001, HttpStatus.NOT_FOUND, "promotion 데이터를 찾지 못했습니다"), + DATA_INTEGRITY_VIOLATION(40402, HttpStatus.BAD_REQUEST, "데이터 무결 위반하였습니다."), + + AUTHORIZATION_VIOLATION(40403, HttpStatus.BAD_REQUEST, "접근 권한이 없습니다."), + /** + * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. + */ + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); + + + private final Integer code; + private final HttpStatus httpStatus; + private final String msg; +} diff --git a/src/main/java/stanl_2/final_backend/domain/promotion/common/exception/PromotionExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/promotion/common/exception/PromotionExceptionResponse.java new file mode 100644 index 00000000..2fc8f780 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/promotion/common/exception/PromotionExceptionResponse.java @@ -0,0 +1,4 @@ +package stanl_2.final_backend.domain.promotion.common.exception; + +public class PromotionExceptionResponse { +} diff --git a/src/main/java/stanl_2/final_backend/domain/promotion/common/response/PromotionResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/promotion/common/response/PromotionResponseMessage.java new file mode 100644 index 00000000..30b3106d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/promotion/common/response/PromotionResponseMessage.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.promotion.common.response; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +public class PromotionResponseMessage { + private int httpStatus; + private String msg; + private Object result; +} diff --git a/src/main/java/stanl_2/final_backend/domain/promotion/common/util/RequestList.java b/src/main/java/stanl_2/final_backend/domain/promotion/common/util/RequestList.java new file mode 100644 index 00000000..a3d83a2c --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/promotion/common/util/RequestList.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.promotion.common.util; + +import lombok.*; +import org.springframework.data.domain.Pageable; + +@Builder +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class RequestList { + private T data; + private Pageable pageable; +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/promotion/query/config/MyBatisConfiguration.java b/src/main/java/stanl_2/final_backend/domain/promotion/query/config/MyBatisConfiguration.java new file mode 100644 index 00000000..dffa7434 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/promotion/query/config/MyBatisConfiguration.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.promotion.query.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Configuration; + +@Configuration("promotionMybatisConfiguration") +@MapperScan(basePackages = "stanl_2.final_backend.domain.promotion.query.repository") +public class MyBatisConfiguration { +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/promotion/query/controller/PromotionController.java b/src/main/java/stanl_2/final_backend/domain/promotion/query/controller/PromotionController.java new file mode 100644 index 00000000..0f872f6f --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/promotion/query/controller/PromotionController.java @@ -0,0 +1,74 @@ +package stanl_2.final_backend.domain.promotion.query.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stanl_2.final_backend.domain.promotion.common.response.PromotionResponseMessage; +import stanl_2.final_backend.domain.promotion.query.dto.PromotionDTO; +import stanl_2.final_backend.domain.promotion.query.dto.PromotionSearchDTO; +import stanl_2.final_backend.domain.promotion.query.service.PromotionService; + +@RestController("queryPromotionController") +@RequestMapping("/api/v1/promotion") +public class PromotionController { + private final PromotionService promotionService; + + @Autowired + public PromotionController(PromotionService promotionService) { + this.promotionService = promotionService; + } + + @Operation(summary = "프로모션 조건별 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = PromotionResponseMessage.class))}) + }) + @GetMapping + public ResponseEntity> getPromotions( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(required = false) String title, + @RequestParam(required = false) String memberId, + @RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate + ) + { + Pageable pageable = PageRequest.of(page, size); + PromotionSearchDTO promotionsearchDTO = new PromotionSearchDTO(title, memberId, startDate, endDate); + Page promotionDTOPage = promotionService.findPromotions(pageable,promotionsearchDTO); + + return ResponseEntity.ok(promotionDTOPage); + } + + @Operation(summary = "프로모션 Id로 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = PromotionResponseMessage.class))}) + }) + @GetMapping("{promotionId}") + public ResponseEntity getPromotion(@PathVariable String promotionId){ + PromotionDTO promotionDTO = promotionService.findPromotion(promotionId); + return ResponseEntity.ok(promotionDTO); + } + @Operation(summary = "프로모션 엑셀 다운") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = PromotionResponseMessage.class))}) + }) + @GetMapping("/excel") + public void exportPromotion(HttpServletResponse response){ + + promotionService.exportPromotionToExcel(response); + } +} + + diff --git a/src/main/java/stanl_2/final_backend/domain/promotion/query/dto/PromotionDTO.java b/src/main/java/stanl_2/final_backend/domain/promotion/query/dto/PromotionDTO.java new file mode 100644 index 00000000..32e85f84 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/promotion/query/dto/PromotionDTO.java @@ -0,0 +1,32 @@ +package stanl_2.final_backend.domain.promotion.query.dto; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class PromotionDTO { + private String promotionId; + + private String title; + + private String content; + + private String createdAt; + + private String updatedAt; + + private String deletedAt; + + private Boolean active; + + private String memberId; + + private String fileUrl; + +} diff --git a/src/main/java/stanl_2/final_backend/domain/promotion/query/dto/PromotionExcelDownload.java b/src/main/java/stanl_2/final_backend/domain/promotion/query/dto/PromotionExcelDownload.java new file mode 100644 index 00000000..04658b52 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/promotion/query/dto/PromotionExcelDownload.java @@ -0,0 +1,25 @@ +package stanl_2.final_backend.domain.promotion.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import stanl_2.final_backend.global.excel.ExcelColumnName; + +@Getter +@AllArgsConstructor +public class PromotionExcelDownload { + @ExcelColumnName(name = "프로모션 제목") + private String title; + + @ExcelColumnName(name = "프로모션 내용") + private String content; + + @ExcelColumnName(name = "생성일자") + private String createdAt; + + @ExcelColumnName(name = "수정일자") + private String updatedAt; + + @ExcelColumnName(name = "작성자") + private String memberId; + +} diff --git a/src/main/java/stanl_2/final_backend/domain/promotion/query/dto/PromotionSearchDTO.java b/src/main/java/stanl_2/final_backend/domain/promotion/query/dto/PromotionSearchDTO.java new file mode 100644 index 00000000..67ea887d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/promotion/query/dto/PromotionSearchDTO.java @@ -0,0 +1,26 @@ +package stanl_2.final_backend.domain.promotion.query.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class PromotionSearchDTO { + private String promotionId; + private String title; + private String content; + private String memberId; + private String createdAt; + private String updatedAt; + private String deletedAt; + private String startDate; + private String endDate; + + public PromotionSearchDTO(String title, String memberId, String startDate, String endDate) { + this.title = title; + this.memberId = memberId; + this.startDate = startDate; + this.endDate = endDate; + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/promotion/query/repository/PromotionMapper.java b/src/main/java/stanl_2/final_backend/domain/promotion/query/repository/PromotionMapper.java new file mode 100644 index 00000000..aab59668 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/promotion/query/repository/PromotionMapper.java @@ -0,0 +1,24 @@ +package stanl_2.final_backend.domain.promotion.query.repository; + +import org.apache.ibatis.annotations.Mapper; +import org.springframework.data.repository.query.Param; +import stanl_2.final_backend.domain.problem.query.dto.ProblemExcelDownload; +import stanl_2.final_backend.domain.promotion.query.dto.PromotionDTO; +import stanl_2.final_backend.domain.promotion.query.dto.PromotionExcelDownload; +import stanl_2.final_backend.domain.promotion.query.dto.PromotionSearchDTO; + +import java.util.List; + +@Mapper +public interface PromotionMapper { + List findPromotions( + @Param("offset") int offset, + @Param("size") int size, + @Param("promotionDTO") PromotionSearchDTO promotionSearchDTO + ); + int findPromotionsCount(@Param("promotionSearchDTO") PromotionSearchDTO promotionSearchDTO); + + PromotionDTO findPromotion(@Param("promotionId") String promotionId); + + List findPromotionsForExcel(); +} diff --git a/src/main/java/stanl_2/final_backend/domain/promotion/query/service/PromotionService.java b/src/main/java/stanl_2/final_backend/domain/promotion/query/service/PromotionService.java new file mode 100644 index 00000000..dfe8d41f --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/promotion/query/service/PromotionService.java @@ -0,0 +1,15 @@ +package stanl_2.final_backend.domain.promotion.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import stanl_2.final_backend.domain.promotion.query.dto.PromotionDTO; +import stanl_2.final_backend.domain.promotion.query.dto.PromotionSearchDTO; + +public interface PromotionService { + Page findPromotions(Pageable pageable, PromotionSearchDTO PromotionSearchDTO); + + PromotionDTO findPromotion(String promotionId); + + void exportPromotionToExcel(HttpServletResponse response); +} diff --git a/src/main/java/stanl_2/final_backend/domain/promotion/query/service/PromotionServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/promotion/query/service/PromotionServiceImpl.java new file mode 100644 index 00000000..28145e40 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/promotion/query/service/PromotionServiceImpl.java @@ -0,0 +1,80 @@ +package stanl_2.final_backend.domain.promotion.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.domain.problem.query.dto.ProblemDTO; +import stanl_2.final_backend.domain.problem.query.dto.ProblemExcelDownload; +import stanl_2.final_backend.domain.promotion.query.dto.PromotionDTO; +import stanl_2.final_backend.domain.promotion.query.dto.PromotionExcelDownload; +import stanl_2.final_backend.domain.promotion.query.dto.PromotionSearchDTO; +import stanl_2.final_backend.domain.promotion.query.repository.PromotionMapper; +import stanl_2.final_backend.domain.sales_history.common.exception.SalesHistoryCommonException; +import stanl_2.final_backend.domain.sales_history.common.exception.SalesHistoryErrorCode; +import stanl_2.final_backend.global.excel.ExcelUtilsV1; +import stanl_2.final_backend.global.redis.RedisService; + +import java.util.List; + +@Service("queryPromoteServiceImpl") +public class PromotionServiceImpl implements PromotionService{ + private final PromotionMapper promotionMapper; + private final RedisTemplate redisTemplate; + private final RedisService redisService; + private final ExcelUtilsV1 excelUtilsV1; + private final MemberQueryService memberQueryService; + @Autowired + public PromotionServiceImpl(PromotionMapper promotionMapper, RedisTemplate redisTemplate, RedisService redisService, ExcelUtilsV1 excelUtilsV1, MemberQueryService memberQueryService) { + this.promotionMapper = promotionMapper; + this.redisTemplate = redisTemplate; + this.memberQueryService = memberQueryService; + this.redisService = redisService; + this.excelUtilsV1 =excelUtilsV1; + } + + @Transactional + @Override + public Page findPromotions(Pageable pageable, PromotionSearchDTO promotionSearchDTO) { + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + String cacheKey = "PromotionCache::offset=" + offset + "::size=" + size +"::title="+ promotionSearchDTO.getTitle()+"::memberId="+ promotionSearchDTO.getMemberId()+"::startDate="+ promotionSearchDTO.getStartDate()+"::endDate="+ promotionSearchDTO.getEndDate(); + List promotions = (List) redisTemplate.opsForValue().get(cacheKey); + if (promotions == null) { + System.out.println("데이터베이스에서 프로모션 데이터 조회 중..."); + promotions = promotionMapper.findPromotions(offset, size, promotionSearchDTO); + if (promotions != null && !promotions.isEmpty()) + redisService.setKeyWithTTL(cacheKey, promotions, 30 * 60); + } else { + System.out.println("캐시에서 프로모션 데이터 조회 중..."); + } + promotions.forEach(promotion -> { + try { + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.MEMBER_NOT_FOUND); + } + }); + Integer totalElements = promotionMapper.findPromotionsCount(promotionSearchDTO); + return new PageImpl<>(promotions, pageable, totalElements); + } + + @Transactional + @Override + public PromotionDTO findPromotion(String promotionId) { + PromotionDTO promotionDTO = promotionMapper.findPromotion(promotionId); + return promotionDTO; + } + + @Transactional + @Override + public void exportPromotionToExcel(HttpServletResponse response) { + List promotionList = promotionMapper.findPromotionsForExcel(); + + excelUtilsV1.download(PromotionExcelDownload.class, promotionList, "promotionExcel", response); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/purchase_order/command/application/controller/PurchaseOrderController.java b/src/main/java/stanl_2/final_backend/domain/purchase_order/command/application/controller/PurchaseOrderController.java new file mode 100644 index 00000000..2606d4a3 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/purchase_order/command/application/controller/PurchaseOrderController.java @@ -0,0 +1,116 @@ +package stanl_2.final_backend.domain.purchase_order.command.application.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stanl_2.final_backend.domain.purchase_order.command.application.dto.PurchaseOrderModifyDTO; +import stanl_2.final_backend.domain.purchase_order.command.application.dto.PurchaseOrderRegistDTO; +import stanl_2.final_backend.domain.purchase_order.command.application.dto.PurchaseOrderStatusModifyDTO; +import stanl_2.final_backend.domain.purchase_order.command.application.service.PurchaseOrderCommandService; +import stanl_2.final_backend.domain.purchase_order.common.response.PurchaseOrderResponseMessage; +import stanl_2.final_backend.domain.purchase_order.query.service.PurchaseOrderQueryService; + +import java.security.GeneralSecurityException; +import java.security.Principal; + +@Slf4j +@RestController("PurchaseOrderCommandController") +@RequestMapping("/api/v1/purchase-order") +public class PurchaseOrderController { + + private final PurchaseOrderCommandService purchaseOrderCommandService; + private final PurchaseOrderQueryService purchaseOrderQueryService; + + @Autowired + public PurchaseOrderController(PurchaseOrderCommandService purchaseOrderCommandService, @Qualifier("purchaseOrderQueryService") PurchaseOrderQueryService purchaseOrderQueryService) { + this.purchaseOrderCommandService = purchaseOrderCommandService; + this.purchaseOrderQueryService = purchaseOrderQueryService; + } + + @Operation(summary = "발주서 등록(영업관리자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "발주서 등록 성공", + content = {@Content(schema = @Schema(implementation = PurchaseOrderResponseMessage.class))}) + }) + @PostMapping("") + public ResponseEntity postPurchaseOrder(@RequestBody PurchaseOrderRegistDTO purchaseOrderRegistDTO, + Principal principal) { + + purchaseOrderRegistDTO.setMemberId(principal.getName()); + + purchaseOrderCommandService.registerPurchaseOrder(purchaseOrderRegistDTO); + + return ResponseEntity.ok(PurchaseOrderResponseMessage.builder() + .httpStatus(200) + .msg("발주서가 성공적으로 등록되었습니다.") + .build()); + } + + @Operation(summary = "발주서 수정(영업관리자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "발주서 수정 성공", + content = {@Content(schema = @Schema(implementation = PurchaseOrderResponseMessage.class))}) + }) + @PutMapping("{purchaseOrderId}") + public ResponseEntity putPurchaseOrder(@PathVariable String purchaseOrderId, + @RequestBody PurchaseOrderModifyDTO purchaseOrderModifyDTO, + Principal principal) { + purchaseOrderModifyDTO.setPurchaseOrderId(purchaseOrderId); + purchaseOrderModifyDTO.setMemberId(principal.getName()); + PurchaseOrderModifyDTO purchaseOrderModifyResponse = purchaseOrderCommandService.modifyPurchaseOrder(purchaseOrderModifyDTO); + + return ResponseEntity.ok(PurchaseOrderResponseMessage.builder() + .httpStatus(200) + .msg("발주서가 성공적으로 수정되었습니다.") + .result(purchaseOrderModifyResponse) + .build()); + } + + @Operation(summary = "발주서 삭제(영업관리자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "발주서 삭제 성공", + content = {@Content(schema = @Schema(implementation = PurchaseOrderResponseMessage.class))}) + }) + @DeleteMapping("{purchaseOrderId}") + public ResponseEntity deletePurchaseOrder(@PathVariable String purchaseOrderId, + Principal principal) { + + purchaseOrderCommandService.deletePurchaseOrder(purchaseOrderId, principal.getName()); + + return ResponseEntity.ok(PurchaseOrderResponseMessage.builder() + .httpStatus(200) + .msg("발주서가 성공적으로 삭제되었습니다.") + .result(null) + .build()); + } + + @Operation(summary = "발주서 승인 상태 수정(영업담당자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "발주서 승인 상태 수정 성공", + content = {@Content(schema = @Schema(implementation = PurchaseOrderResponseMessage.class))}) + }) + @PutMapping("status/{purchaseOrderId}") + public ResponseEntity putPurchaseOrderStatus(@PathVariable String purchaseOrderId, + @RequestBody PurchaseOrderStatusModifyDTO purchaseOrderStatusModifyDTO, + Principal principal) throws GeneralSecurityException { + + purchaseOrderStatusModifyDTO.setPurchaseOrderId(purchaseOrderId); + purchaseOrderStatusModifyDTO.setAdminId(principal.getName()); + + purchaseOrderCommandService.modifyPurchaseOrderStatus(purchaseOrderStatusModifyDTO); + + return ResponseEntity.ok(PurchaseOrderResponseMessage.builder() + .httpStatus(200) + .msg("발주서 승인 상태가 성공적으로 변경되었습니다.") + .result(null) + .build()); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/purchase_order/command/application/dto/PurchaseOrderAlarmDTO.java b/src/main/java/stanl_2/final_backend/domain/purchase_order/command/application/dto/PurchaseOrderAlarmDTO.java new file mode 100644 index 00000000..02277e50 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/purchase_order/command/application/dto/PurchaseOrderAlarmDTO.java @@ -0,0 +1,18 @@ +package stanl_2.final_backend.domain.purchase_order.command.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class PurchaseOrderAlarmDTO { + + private String purchaseOrderId; + private String title; + private String memberId; + private String adminId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/purchase_order/command/application/dto/PurchaseOrderModifyDTO.java b/src/main/java/stanl_2/final_backend/domain/purchase_order/command/application/dto/PurchaseOrderModifyDTO.java new file mode 100644 index 00000000..e4e5a8ff --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/purchase_order/command/application/dto/PurchaseOrderModifyDTO.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.purchase_order.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class PurchaseOrderModifyDTO { + private String purchaseOrderId; + private String title; + private String content; + private String memberId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/purchase_order/command/application/dto/PurchaseOrderRegistDTO.java b/src/main/java/stanl_2/final_backend/domain/purchase_order/command/application/dto/PurchaseOrderRegistDTO.java new file mode 100644 index 00000000..55a05a39 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/purchase_order/command/application/dto/PurchaseOrderRegistDTO.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.purchase_order.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class PurchaseOrderRegistDTO { + private String title; + private String content; + private String orderId; + private String memberId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/purchase_order/command/application/dto/PurchaseOrderStatusModifyDTO.java b/src/main/java/stanl_2/final_backend/domain/purchase_order/command/application/dto/PurchaseOrderStatusModifyDTO.java new file mode 100644 index 00000000..1ce6c560 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/purchase_order/command/application/dto/PurchaseOrderStatusModifyDTO.java @@ -0,0 +1,13 @@ +package stanl_2.final_backend.domain.purchase_order.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class PurchaseOrderStatusModifyDTO { + private String purchaseOrderId; + private String status; + private String adminId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/purchase_order/command/application/service/PurchaseOrderCommandService.java b/src/main/java/stanl_2/final_backend/domain/purchase_order/command/application/service/PurchaseOrderCommandService.java new file mode 100644 index 00000000..f4c65537 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/purchase_order/command/application/service/PurchaseOrderCommandService.java @@ -0,0 +1,18 @@ +package stanl_2.final_backend.domain.purchase_order.command.application.service; + +import stanl_2.final_backend.domain.purchase_order.command.application.dto.PurchaseOrderModifyDTO; +import stanl_2.final_backend.domain.purchase_order.command.application.dto.PurchaseOrderRegistDTO; +import stanl_2.final_backend.domain.purchase_order.command.application.dto.PurchaseOrderStatusModifyDTO; + +import java.security.GeneralSecurityException; + + +public interface PurchaseOrderCommandService { + void registerPurchaseOrder(PurchaseOrderRegistDTO purchaseOrderRegistDTO); + + PurchaseOrderModifyDTO modifyPurchaseOrder(PurchaseOrderModifyDTO purchaseOrderModifyDTO); + + void deletePurchaseOrder(String id, String loginId); + + void modifyPurchaseOrderStatus(PurchaseOrderStatusModifyDTO purchaseOrderStatusModifyDTO) throws GeneralSecurityException; +} diff --git a/src/main/java/stanl_2/final_backend/domain/purchase_order/command/domain/aggregate/entity/PurchaseOrder.java b/src/main/java/stanl_2/final_backend/domain/purchase_order/command/domain/aggregate/entity/PurchaseOrder.java new file mode 100644 index 00000000..a464d56b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/purchase_order/command/domain/aggregate/entity/PurchaseOrder.java @@ -0,0 +1,80 @@ +package stanl_2.final_backend.domain.purchase_order.command.domain.aggregate.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "TB_PURCHASE_ORDER") +public class PurchaseOrder { + + @Id + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name = "prefix", value = "PUR_ORD") + ) + @Column(name = "PUR_ORD_ID") + private String purchaseOrderId; + + @Column(name = "PUR_ORD_TTL", nullable = false) + private String title; + + @Lob + @Column(name = "PUR_ORD_CONT", nullable = false, columnDefinition = "TEXT") + private String content; + + @Column(name = "ACTIVE", nullable = false) + private Boolean active = true; + + @Column(name = "CREATED_AT", nullable = false, updatable = false) + private String createdAt; + + @Column(name = "UPDATED_AT", nullable = false) + private String updatedAt; + + @Column(name = "DELETED_AT") + private String deletedAt; + + @Column(name = "PUR_ORD_STAT", nullable = false) + private String status = "WAIT"; + + @Column(name = "ORD_ID", nullable = false) + private String orderId; + + @Column(name = "ADMIN_ID") + private String adminId; + + @Column(name = "MEM_ID", nullable = false) + private String memberId; + + // Insert 되기 전에 실행 + @PrePersist + private void prePersist() { + this.createdAt = getCurrentTime(); + this.updatedAt = this.createdAt; + } + + // Update 되기 전에 실행 + @PreUpdate + private void preUpdate() { + this.updatedAt = getCurrentTime(); + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/purchase_order/command/domain/repository/PurchaseOrderRepository.java b/src/main/java/stanl_2/final_backend/domain/purchase_order/command/domain/repository/PurchaseOrderRepository.java new file mode 100644 index 00000000..b12b846c --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/purchase_order/command/domain/repository/PurchaseOrderRepository.java @@ -0,0 +1,12 @@ +package stanl_2.final_backend.domain.purchase_order.command.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import stanl_2.final_backend.domain.purchase_order.command.domain.aggregate.entity.PurchaseOrder; + +import java.util.Optional; + +public interface PurchaseOrderRepository extends JpaRepository { + Optional findByPurchaseOrderIdAndMemberId(String purchaseOrderId, String memberId); + + Optional findByPurchaseOrderId(String id); +} diff --git a/src/main/java/stanl_2/final_backend/domain/purchase_order/command/domain/service/PurchaseOrderCommandServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/purchase_order/command/domain/service/PurchaseOrderCommandServiceImpl.java new file mode 100644 index 00000000..a6a2e934 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/purchase_order/command/domain/service/PurchaseOrderCommandServiceImpl.java @@ -0,0 +1,165 @@ +package stanl_2.final_backend.domain.purchase_order.command.domain.service; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringEscapeUtils; +import lombok.extern.slf4j.Slf4j; +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.alarm.command.application.service.AlarmCommandService; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.order.command.domain.aggregate.entity.Order; +import stanl_2.final_backend.domain.order.command.domain.repository.OrderRepository; +import stanl_2.final_backend.domain.order.common.exception.OrderCommonException; +import stanl_2.final_backend.domain.order.common.exception.OrderErrorCode; +import stanl_2.final_backend.domain.purchase_order.command.application.dto.PurchaseOrderAlarmDTO; +import stanl_2.final_backend.domain.purchase_order.command.application.dto.PurchaseOrderModifyDTO; +import stanl_2.final_backend.domain.purchase_order.command.application.dto.PurchaseOrderRegistDTO; +import stanl_2.final_backend.domain.purchase_order.command.application.dto.PurchaseOrderStatusModifyDTO; +import stanl_2.final_backend.domain.purchase_order.command.application.service.PurchaseOrderCommandService; +import stanl_2.final_backend.domain.purchase_order.command.domain.aggregate.entity.PurchaseOrder; +import stanl_2.final_backend.domain.purchase_order.command.domain.repository.PurchaseOrderRepository; +import stanl_2.final_backend.domain.purchase_order.common.exception.PurchaseOrderCommonException; +import stanl_2.final_backend.domain.purchase_order.common.exception.PurchaseOrderErrorCode; +import stanl_2.final_backend.domain.s3.command.application.service.S3FileService; + +import java.security.GeneralSecurityException; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@Slf4j +@Service +public class PurchaseOrderCommandServiceImpl implements PurchaseOrderCommandService { + + private final PurchaseOrderRepository purchaseOrderRepository; + private final OrderRepository orderRepository; + private final AuthQueryService authQueryService; + private final ModelMapper modelMapper; + private final AlarmCommandService alarmCommandService; + private final S3FileService s3FileService; + + @Autowired + public PurchaseOrderCommandServiceImpl(PurchaseOrderRepository purchaseOrderRepository, OrderRepository orderRepository, + AuthQueryService authQueryService, ModelMapper modelMapper, + AlarmCommandService alarmCommandService,S3FileService s3FileService) { + this.purchaseOrderRepository = purchaseOrderRepository; + this.orderRepository = orderRepository; + this.authQueryService = authQueryService; + this.modelMapper = modelMapper; + this.alarmCommandService = alarmCommandService; + this.s3FileService = s3FileService; + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + + @Override + @Transactional + public void registerPurchaseOrder(PurchaseOrderRegistDTO purchaseOrderRegistDTO) { + + String memberId = authQueryService.selectMemberIdByLoginId(purchaseOrderRegistDTO.getMemberId()); + + // 수주서가 존재하는지 확인 + Order order = orderRepository.findByOrderId(purchaseOrderRegistDTO.getOrderId()); + + if (order == null) { + throw new OrderCommonException(OrderErrorCode.ORDER_NOT_FOUND); + } + + // 수주서의 상태가 APPROVED인지 확인 + if (!"APPROVED".equals(order.getStatus())) { + throw new OrderCommonException(OrderErrorCode.ORDER_STATUS_NOT_APPROVED); + } + + String unescapedHtml = StringEscapeUtils.unescapeJson(purchaseOrderRegistDTO.getContent()); + String updatedS3Url = s3FileService.uploadHtml(unescapedHtml, purchaseOrderRegistDTO.getTitle()); + + PurchaseOrder purchaseOrder = modelMapper.map(purchaseOrderRegistDTO, PurchaseOrder.class); + purchaseOrder.setMemberId(memberId); + purchaseOrder.setContent(updatedS3Url); + + purchaseOrderRepository.save(purchaseOrder); + } + + @Override + @Transactional + public PurchaseOrderModifyDTO modifyPurchaseOrder(PurchaseOrderModifyDTO purchaseOrderModifyDTO) { + + String memberId = authQueryService.selectMemberIdByLoginId(purchaseOrderModifyDTO.getMemberId()); + + // 회원인지 확인 및 발주서 조회 + PurchaseOrder purchaseOrder = (PurchaseOrder) purchaseOrderRepository.findByPurchaseOrderIdAndMemberId( + purchaseOrderModifyDTO.getPurchaseOrderId(), memberId) + .orElseThrow(() -> new PurchaseOrderCommonException(PurchaseOrderErrorCode.PURCHASE_ORDER_NOT_FOUND)); + + // 수주서가 존재하는지 확인 + Order order = orderRepository.findByOrderIdAndMemberId(purchaseOrder.getOrderId(), memberId); + if (order == null) { + throw new OrderCommonException(OrderErrorCode.ORDER_NOT_FOUND); + } + + // 수주서의 상태가 APPROVED인지 확인 + if (!"APPROVED".equals(order.getStatus())) { + throw new OrderCommonException(OrderErrorCode.ORDER_STATUS_NOT_APPROVED); + } + + String unescapedHtml = StringEscapeUtils.unescapeJson(purchaseOrderModifyDTO.getContent()); + String updatedS3Url = s3FileService.uploadHtml(unescapedHtml, purchaseOrderModifyDTO.getTitle()); + + PurchaseOrder updatePurchaseOrder = modelMapper.map(purchaseOrderModifyDTO, PurchaseOrder.class); + updatePurchaseOrder.setCreatedAt(purchaseOrder.getCreatedAt()); + updatePurchaseOrder.setUpdatedAt(purchaseOrder.getUpdatedAt()); + updatePurchaseOrder.setStatus(purchaseOrder.getStatus()); + updatePurchaseOrder.setActive(purchaseOrder.getActive()); + updatePurchaseOrder.setContent(updatedS3Url); + updatePurchaseOrder.setOrderId(purchaseOrder.getOrderId()); + updatePurchaseOrder.setMemberId(memberId); + + purchaseOrderRepository.save(updatePurchaseOrder); + + PurchaseOrderModifyDTO purchaseOrderModifyResponse = modelMapper.map(updatePurchaseOrder, PurchaseOrderModifyDTO.class); + + return purchaseOrderModifyResponse; + } + + @Override + @Transactional + public void deletePurchaseOrder(String purchaseOrderId, String loginId) { + + String memberId = authQueryService.selectMemberIdByLoginId(loginId); + + // 발주서가 해당 회원의 것인지 확인 + PurchaseOrder purchaseOrder = (PurchaseOrder) purchaseOrderRepository.findByPurchaseOrderIdAndMemberId(purchaseOrderId, memberId) + .orElseThrow(() -> new PurchaseOrderCommonException(PurchaseOrderErrorCode.PURCHASE_ORDER_NOT_FOUND)); + + purchaseOrder.setActive(false); + purchaseOrder.setDeletedAt(getCurrentTime()); + + purchaseOrderRepository.save(purchaseOrder); + } + + @Override + @Transactional + public void modifyPurchaseOrderStatus(PurchaseOrderStatusModifyDTO purchaseOrderStatusModifyDTO) throws GeneralSecurityException { + + String adminId = authQueryService.selectMemberIdByLoginId(purchaseOrderStatusModifyDTO.getAdminId()); + + PurchaseOrder purchaseOrder = purchaseOrderRepository.findByPurchaseOrderId(purchaseOrderStatusModifyDTO.getPurchaseOrderId()) + .orElseThrow(() -> new PurchaseOrderCommonException(PurchaseOrderErrorCode.PURCHASE_ORDER_NOT_FOUND)); + + purchaseOrder.setStatus(purchaseOrderStatusModifyDTO.getStatus()); + purchaseOrder.setAdminId(adminId); + + + purchaseOrderRepository.save(purchaseOrder); + + PurchaseOrderAlarmDTO purchaseOrderAlarmDTO = new PurchaseOrderAlarmDTO(purchaseOrder.getPurchaseOrderId(), + purchaseOrder.getTitle(), purchaseOrder.getMemberId(), purchaseOrder.getAdminId()); + + alarmCommandService.sendPurchaseOrderAlarm(purchaseOrderAlarmDTO); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/purchase_order/common/exception/PurchaseOrderCommonException.java b/src/main/java/stanl_2/final_backend/domain/purchase_order/common/exception/PurchaseOrderCommonException.java new file mode 100644 index 00000000..97000a72 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/purchase_order/common/exception/PurchaseOrderCommonException.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.purchase_order.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class PurchaseOrderCommonException extends RuntimeException { + private final PurchaseOrderErrorCode sampleErrorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.sampleErrorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/purchase_order/common/exception/PurchaseOrderErrorCode.java b/src/main/java/stanl_2/final_backend/domain/purchase_order/common/exception/PurchaseOrderErrorCode.java new file mode 100644 index 00000000..f11505ff --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/purchase_order/common/exception/PurchaseOrderErrorCode.java @@ -0,0 +1,51 @@ +package stanl_2.final_backend.domain.purchase_order.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum PurchaseOrderErrorCode { + + /** + * 400(Bad Request) + * 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. + */ + + + + /** + * 401(Unauthorized) + * 비록 HTTP 표준에서는 "미승인(unauthorized)"를 명확히 하고 있지만, + * 의미상 이 응답은 "비인증(unauthenticated)"을 의미합니다. + * 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로를 인증해야 합니다. + */ + + + /** + * 403(Forbidden) + * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. + * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. + */ + + + + /** + * 404(Not Found) + * 서버는 요청받은 리소스를 찾을 수 없습니다. 브라우저에서는 알려지지 않은 URL을 의미합니다. + * 이것은 API에서 종점은 적절하지만 리소스 자체는 존재하지 않음을 의미할 수도 있습니다. + * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. + * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. + */ + PURCHASE_ORDER_NOT_FOUND(404001, HttpStatus.NOT_FOUND, "발주서를 찾지 못했습니다"), + /** + * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. + */ + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); + + + private final Integer code; + private final HttpStatus httpStatus; + private final String msg; +} diff --git a/src/main/java/stanl_2/final_backend/domain/purchase_order/common/exception/PurchaseOrderExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/purchase_order/common/exception/PurchaseOrderExceptionResponse.java new file mode 100644 index 00000000..6b4c2851 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/purchase_order/common/exception/PurchaseOrderExceptionResponse.java @@ -0,0 +1,22 @@ +package stanl_2.final_backend.domain.purchase_order.common.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class PurchaseOrderExceptionResponse { + private final Integer code; + private final String msg; + private final HttpStatus httpStatus; + + public PurchaseOrderExceptionResponse(PurchaseOrderErrorCode sampleErrorCode) { + this.code = sampleErrorCode.getCode(); + this.msg = sampleErrorCode.getMsg(); + this.httpStatus = sampleErrorCode.getHttpStatus(); + } + + public static PurchaseOrderExceptionResponse of(PurchaseOrderErrorCode sampleErrorCode) { + return new PurchaseOrderExceptionResponse(sampleErrorCode); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/purchase_order/common/response/PurchaseOrderResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/purchase_order/common/response/PurchaseOrderResponseMessage.java new file mode 100644 index 00000000..e844a46a --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/purchase_order/common/response/PurchaseOrderResponseMessage.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.purchase_order.common.response; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +public class PurchaseOrderResponseMessage { + private int httpStatus; + private String msg; + private Object result; +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/purchase_order/query/controller/PurchaseOrderController.java b/src/main/java/stanl_2/final_backend/domain/purchase_order/query/controller/PurchaseOrderController.java new file mode 100644 index 00000000..0b3bb3e5 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/purchase_order/query/controller/PurchaseOrderController.java @@ -0,0 +1,214 @@ +package stanl_2.final_backend.domain.purchase_order.query.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stanl_2.final_backend.domain.purchase_order.common.response.PurchaseOrderResponseMessage; +import stanl_2.final_backend.domain.purchase_order.query.dto.PurchaseOrderSelectAllDTO; +import stanl_2.final_backend.domain.purchase_order.query.dto.PurchaseOrderSelectIdDTO; +import stanl_2.final_backend.domain.purchase_order.query.dto.PurchaseOrderSelectSearchDTO; +import stanl_2.final_backend.domain.purchase_order.query.service.PurchaseOrderQueryService; + +import java.security.GeneralSecurityException; +import java.security.Principal; + +@RestController("PurchaseOrderQueryController") +@RequestMapping("/api/v1/purchase-order") +public class PurchaseOrderController { + + private final PurchaseOrderQueryService purchaseOrderQueryService; + + @Autowired + public PurchaseOrderController(PurchaseOrderQueryService purchaseOrderQueryService) { + this.purchaseOrderQueryService = purchaseOrderQueryService; + } + + // 영업관리자 조회 + @Operation(summary = "발주서 상세조회(영업관리자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "발주서 상세조회 성공", + content = {@Content(schema = @Schema(implementation = PurchaseOrderResponseMessage.class))}) + }) + @GetMapping("admin/{purchaseOrderId}") + public ResponseEntity getDetailPurchaseOrderAdmin(@PathVariable String purchaseOrderId, + Principal principal) { + + PurchaseOrderSelectIdDTO purchaseOrderSelectIdDTO = new PurchaseOrderSelectIdDTO(); + purchaseOrderSelectIdDTO.setPurchaseOrderId(purchaseOrderId); + purchaseOrderSelectIdDTO.setMemberId(principal.getName()); + + PurchaseOrderSelectIdDTO responsePurchaseOrder = purchaseOrderQueryService.selectDetailPurchaseOrderAdmin(purchaseOrderSelectIdDTO); + + return ResponseEntity.ok(PurchaseOrderResponseMessage.builder() + .httpStatus(200) + .msg("발주서 상세 조회 성공") + .result(responsePurchaseOrder) + .build()); + } + + @Operation(summary = "발주서 전체조회(영업관리자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "발주서 전체조회 성공", + content = {@Content(schema = @Schema(implementation = PurchaseOrderResponseMessage.class))}) + }) + @GetMapping("admin") + public ResponseEntity getAllPurchaseOrdersAdmin(Principal principal, + @PageableDefault(size = 10) Pageable pageable) { + PurchaseOrderSelectAllDTO purchaseOrderSelectAllDTO = new PurchaseOrderSelectAllDTO(); + purchaseOrderSelectAllDTO.setMemberId(principal.getName()); + + Page responsePurchaseOrders = purchaseOrderQueryService.selectAllPurchaseOrderAdmin(pageable, purchaseOrderSelectAllDTO); + + return ResponseEntity.ok(PurchaseOrderResponseMessage.builder() + .httpStatus(200) + .msg("발주서 전체 조회 성공") + .result(responsePurchaseOrders) + .build()); + } + + @Operation(summary = "발주서 검색조회(영업관리자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "발주서 검색조회 성공", + content = {@Content(schema = @Schema(implementation = PurchaseOrderResponseMessage.class))}) + }) + @GetMapping("admin/search") + public ResponseEntity getSearchPurchaseOrdersAdmin(@RequestParam(required = false) String title, + @RequestParam(required = false) String status, + @RequestParam(required = false) String adminId, + @RequestParam(required = false) String searchMemberId, + @RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate, + @RequestParam(required = false) String productName, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder, + Principal principal, + @PageableDefault(size = 10) Pageable pageable) throws GeneralSecurityException { + + // 정렬 추가 + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + PurchaseOrderSelectSearchDTO purchaseOrderSelectSearchDTO = new PurchaseOrderSelectSearchDTO(); + purchaseOrderSelectSearchDTO.setTitle(title); + purchaseOrderSelectSearchDTO.setStatus(status); + purchaseOrderSelectSearchDTO.setAdminId(adminId); + purchaseOrderSelectSearchDTO.setStartDate(startDate); + purchaseOrderSelectSearchDTO.setEndDate(endDate); + purchaseOrderSelectSearchDTO.setSearchMemberId(searchMemberId); + purchaseOrderSelectSearchDTO.setMemberId(principal.getName()); + purchaseOrderSelectSearchDTO.setProductName(productName); + + Page responsePurchaseOrder = purchaseOrderQueryService.selectSearchPurchaseOrderAdmin(purchaseOrderSelectSearchDTO, pageable); + + return ResponseEntity.ok(PurchaseOrderResponseMessage.builder() + .httpStatus(200) + .msg("발주서 검색 조회 성공") + .result(responsePurchaseOrder) + .build()); + } + + // 영업담당자 조회 + @Operation(summary = "발주서 상세조회(영업담당자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "발주서 상세조회 성공", + content = {@Content(schema = @Schema(implementation = PurchaseOrderResponseMessage.class))}) + }) + @GetMapping("{purchaseOrderId}") + public ResponseEntity getDetailPurchaseOrder(@PathVariable String purchaseOrderId) { + + PurchaseOrderSelectIdDTO purchaseOrderSelectIdDTO = new PurchaseOrderSelectIdDTO(); + purchaseOrderSelectIdDTO.setPurchaseOrderId(purchaseOrderId); + + PurchaseOrderSelectIdDTO responsePurchaseOrder = purchaseOrderQueryService.selectDetailPurchaseOrder(purchaseOrderSelectIdDTO); + + return ResponseEntity.ok(PurchaseOrderResponseMessage.builder() + .httpStatus(200) + .msg("발주서 상세 조회 성공") + .result(responsePurchaseOrder) + .build()); + } + + @Operation(summary = "발주서 전체조회(영업담당자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "발주서 전체조회 성공", + content = {@Content(schema = @Schema(implementation = PurchaseOrderResponseMessage.class))}) + }) + @GetMapping("") + public ResponseEntity getAllPurchaseOrders(@PageableDefault(size = 10) Pageable pageable) { + PurchaseOrderSelectAllDTO purchaseOrderSelectAllDTO = new PurchaseOrderSelectAllDTO(); + + Page responsePurchaseOrders = purchaseOrderQueryService.selectAllPurchaseOrder(pageable, purchaseOrderSelectAllDTO); + + return ResponseEntity.ok(PurchaseOrderResponseMessage.builder() + .httpStatus(200) + .msg("발주서 전체 조회 성공") + .result(responsePurchaseOrders) + .build()); + } + + @Operation(summary = "발주서 검색조회(영업담당자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "발주서 검색조회 성공", + content = {@Content(schema = @Schema(implementation = PurchaseOrderResponseMessage.class))}) + }) + @GetMapping("search") + public ResponseEntity getSearchPurchaseOrders(@RequestParam(required = false) String title, + @RequestParam(required = false) String status, + @RequestParam(required = false) String adminId, + @RequestParam(required = false) String searchMemberId, + @RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder, + @RequestParam(required = false) String productName, + @PageableDefault(size = 10) Pageable pageable) throws GeneralSecurityException { + + // 정렬 추가 + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + PurchaseOrderSelectSearchDTO purchaseOrderSelectSearchDTO = new PurchaseOrderSelectSearchDTO(); + purchaseOrderSelectSearchDTO.setTitle(title); + purchaseOrderSelectSearchDTO.setStatus(status); + purchaseOrderSelectSearchDTO.setAdminId(adminId); + purchaseOrderSelectSearchDTO.setStartDate(startDate); + purchaseOrderSelectSearchDTO.setEndDate(endDate); + purchaseOrderSelectSearchDTO.setSearchMemberId(searchMemberId); + purchaseOrderSelectSearchDTO.setProductName(productName); + + Page responsePurchaseOrder = purchaseOrderQueryService.selectSearchPurchaseOrder(purchaseOrderSelectSearchDTO, pageable); + + return ResponseEntity.ok(PurchaseOrderResponseMessage.builder() + .httpStatus(200) + .msg("발주서 검색 조회 성공") + .result(responsePurchaseOrder) + .build()); + } + + @Operation(summary = "엑셀 다운로드") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "엑셀 다운로드 성공", + content = {@Content(schema = @Schema(implementation = PurchaseOrderResponseMessage.class))}) + }) + @GetMapping("excel") + public void exportPurchaseOrder(HttpServletResponse response) throws GeneralSecurityException { + + purchaseOrderQueryService.exportPurchaseOrder(response); + } +} + diff --git a/src/main/java/stanl_2/final_backend/domain/purchase_order/query/dto/PurchaseOrderExcelDTO.java b/src/main/java/stanl_2/final_backend/domain/purchase_order/query/dto/PurchaseOrderExcelDTO.java new file mode 100644 index 00000000..af2d3b13 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/purchase_order/query/dto/PurchaseOrderExcelDTO.java @@ -0,0 +1,30 @@ +package stanl_2.final_backend.domain.purchase_order.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import stanl_2.final_backend.global.excel.ExcelColumnName; + +@Getter +@Setter +@AllArgsConstructor +public class PurchaseOrderExcelDTO { + + @ExcelColumnName(name = "발주서 번호") + private String purchaseOrderId; + + @ExcelColumnName(name = "제목") + private String title; + + @ExcelColumnName(name = "승인 상태") + private String status; + + @ExcelColumnName(name = "수주자") + private String memberName; + + @ExcelColumnName(name = "제품명") + private String productName; + + @ExcelColumnName(name = "수주일") + private String createdAt; +} diff --git a/src/main/java/stanl_2/final_backend/domain/purchase_order/query/dto/PurchaseOrderSelectAllDTO.java b/src/main/java/stanl_2/final_backend/domain/purchase_order/query/dto/PurchaseOrderSelectAllDTO.java new file mode 100644 index 00000000..d821772b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/purchase_order/query/dto/PurchaseOrderSelectAllDTO.java @@ -0,0 +1,21 @@ +package stanl_2.final_backend.domain.purchase_order.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class PurchaseOrderSelectAllDTO { + private String PurchaseOrderId; + private String title; + private String status; + private String orderId; + private String adminName; + private String memberName; + private String productName; + private String memberId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/purchase_order/query/dto/PurchaseOrderSelectIdDTO.java b/src/main/java/stanl_2/final_backend/domain/purchase_order/query/dto/PurchaseOrderSelectIdDTO.java new file mode 100644 index 00000000..86abfde9 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/purchase_order/query/dto/PurchaseOrderSelectIdDTO.java @@ -0,0 +1,24 @@ +package stanl_2.final_backend.domain.purchase_order.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class PurchaseOrderSelectIdDTO { + private String PurchaseOrderId; + private String title; + private String content; + private Boolean active; + private String status; + private String createdAt; + private String updatedAt; + private String deletedAt; + private String orderId; + private String adminId; + private String memberId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/purchase_order/query/dto/PurchaseOrderSelectSearchDTO.java b/src/main/java/stanl_2/final_backend/domain/purchase_order/query/dto/PurchaseOrderSelectSearchDTO.java new file mode 100644 index 00000000..5f7cfac1 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/purchase_order/query/dto/PurchaseOrderSelectSearchDTO.java @@ -0,0 +1,26 @@ +package stanl_2.final_backend.domain.purchase_order.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class PurchaseOrderSelectSearchDTO { + private String PurchaseOrderId; + private String title; + private String status; + private String orderId; + private String adminName; + private String memberName; + private String productName; + private String adminId; + private String memberId; + private String searchMemberId; + private String startDate; + private String endDate; + private String createdAt; +} diff --git a/src/main/java/stanl_2/final_backend/domain/purchase_order/query/repository/PurchaseOrderMapper.java b/src/main/java/stanl_2/final_backend/domain/purchase_order/query/repository/PurchaseOrderMapper.java new file mode 100644 index 00000000..af82a582 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/purchase_order/query/repository/PurchaseOrderMapper.java @@ -0,0 +1,48 @@ +package stanl_2.final_backend.domain.purchase_order.query.repository; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import stanl_2.final_backend.domain.purchase_order.query.dto.PurchaseOrderExcelDTO; +import stanl_2.final_backend.domain.purchase_order.query.dto.PurchaseOrderSelectAllDTO; +import stanl_2.final_backend.domain.purchase_order.query.dto.PurchaseOrderSelectIdDTO; +import stanl_2.final_backend.domain.purchase_order.query.dto.PurchaseOrderSelectSearchDTO; + +import java.util.List; + +@Mapper +public interface PurchaseOrderMapper { + PurchaseOrderSelectIdDTO findPurchaseOrderByPurchaseOrderIdAndMemberId(@Param("purchaseOrderId") String purchaseOrderId, + @Param("memberId") String memberId); + + List findAllPurchaseOrderByMemberId(@Param("offset") int offset, + @Param("pageSize") int pageSize, + @Param("memberId") String memberId); + + Integer findAllPurchaseOrderCountByMemberId(String memberId); + + List findSearchPurchaseOrder(@Param("offset") int offset, + @Param("pageSize") int pageSize, + @Param("purchaseOrderSelectSearchDTO") + PurchaseOrderSelectSearchDTO purchaseOrderSelectSearchDTO, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + List findSearchPurchaseOrderMemberId(@Param("offset") int offset, + @Param("pageSize") int pageSize, + @Param("purchaseOrderSelectSearchDTO") PurchaseOrderSelectSearchDTO purchaseOrderSelectSearchDTO, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + int findSearchPurchaseOrderCountMemberId(@Param("purchaseOrderSelectSearchDTO") PurchaseOrderSelectSearchDTO purchaseOrderSelectSearchDTO); + + PurchaseOrderSelectIdDTO findPurchaseOrderByPurchaseOrderId(String purchaseOrderId); + + List findAllPurchaseOrder(@Param("pageSize") int offset, + @Param("pageSize") int pageSize); + + Integer findAllPurchaseOrderCount(); + + int findSearchPurchaseOrderCount(@Param("purchaseOrderSelectSearchDTO") PurchaseOrderSelectSearchDTO purchaseOrderSelectSearchDTO); + + List findPurchaseOrderForExcel(); +} diff --git a/src/main/java/stanl_2/final_backend/domain/purchase_order/query/service/PurchaseOrderQueryService.java b/src/main/java/stanl_2/final_backend/domain/purchase_order/query/service/PurchaseOrderQueryService.java new file mode 100644 index 00000000..98679a65 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/purchase_order/query/service/PurchaseOrderQueryService.java @@ -0,0 +1,26 @@ +package stanl_2.final_backend.domain.purchase_order.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import stanl_2.final_backend.domain.purchase_order.query.dto.PurchaseOrderSelectAllDTO; +import stanl_2.final_backend.domain.purchase_order.query.dto.PurchaseOrderSelectIdDTO; +import stanl_2.final_backend.domain.purchase_order.query.dto.PurchaseOrderSelectSearchDTO; + +import java.security.GeneralSecurityException; + +public interface PurchaseOrderQueryService { + PurchaseOrderSelectIdDTO selectDetailPurchaseOrderAdmin(PurchaseOrderSelectIdDTO purchaseOrderSelectIdDTO); + + Page selectAllPurchaseOrderAdmin(Pageable pageable, PurchaseOrderSelectAllDTO purchaseOrderSelectAllDTO); + + Page selectSearchPurchaseOrderAdmin(PurchaseOrderSelectSearchDTO purchaseOrderSelectSearchDTO, Pageable pageable) throws GeneralSecurityException; + + Page selectSearchPurchaseOrder(PurchaseOrderSelectSearchDTO purchaseOrderSelectSearchDTO, Pageable pageable) throws GeneralSecurityException; + + Page selectAllPurchaseOrder(Pageable pageable, PurchaseOrderSelectAllDTO purchaseOrderSelectAllDTO); + + PurchaseOrderSelectIdDTO selectDetailPurchaseOrder(PurchaseOrderSelectIdDTO purchaseOrderSelectIdDTO); + + void exportPurchaseOrder(HttpServletResponse response) throws GeneralSecurityException; +} diff --git a/src/main/java/stanl_2/final_backend/domain/purchase_order/query/service/PurchaseOrderQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/purchase_order/query/service/PurchaseOrderQueryServiceImpl.java new file mode 100644 index 00000000..46053caf --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/purchase_order/query/service/PurchaseOrderQueryServiceImpl.java @@ -0,0 +1,274 @@ +package stanl_2.final_backend.domain.purchase_order.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringEscapeUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.domain.purchase_order.query.dto.PurchaseOrderExcelDTO; +import stanl_2.final_backend.domain.purchase_order.common.exception.PurchaseOrderCommonException; +import stanl_2.final_backend.domain.purchase_order.common.exception.PurchaseOrderErrorCode; +import stanl_2.final_backend.domain.purchase_order.query.dto.PurchaseOrderSelectAllDTO; +import stanl_2.final_backend.domain.purchase_order.query.dto.PurchaseOrderSelectIdDTO; +import stanl_2.final_backend.domain.purchase_order.query.dto.PurchaseOrderSelectSearchDTO; +import stanl_2.final_backend.domain.purchase_order.query.repository.PurchaseOrderMapper; +import stanl_2.final_backend.global.excel.ExcelUtilsV1; +import stanl_2.final_backend.global.utils.AESUtils; + +import java.security.GeneralSecurityException; +import java.util.List; + +@Slf4j +@Service("purchaseOrderQueryService") +public class PurchaseOrderQueryServiceImpl implements PurchaseOrderQueryService { + + private final PurchaseOrderMapper purchaseOrderMapper; + private final AuthQueryService authQueryService; + private final MemberQueryService memberQueryService; + private final RedisTemplate redisTemplate; + private final ExcelUtilsV1 excelUtilsV1; + private final AESUtils aesUtils; + + @Autowired + public PurchaseOrderQueryServiceImpl(PurchaseOrderMapper purchaseOrderMapper, AuthQueryService authQueryService, + RedisTemplate redisTemplate, ExcelUtilsV1 excelUtilsV1, + MemberQueryService memberQueryService, AESUtils aesUtils) { + this.purchaseOrderMapper = purchaseOrderMapper; + this.authQueryService = authQueryService; + this.redisTemplate = redisTemplate; + this.excelUtilsV1 = excelUtilsV1; + this.memberQueryService = memberQueryService; + this.aesUtils = aesUtils; + } + + // 영업 관리자 조회 + @Override + @Transactional(readOnly = true) + public PurchaseOrderSelectIdDTO selectDetailPurchaseOrderAdmin(PurchaseOrderSelectIdDTO purchaseOrderSelectIdDTO) { + + String memberId = authQueryService.selectMemberIdByLoginId(purchaseOrderSelectIdDTO.getMemberId()); + + PurchaseOrderSelectIdDTO purchaseOrder = purchaseOrderMapper.findPurchaseOrderByPurchaseOrderIdAndMemberId(purchaseOrderSelectIdDTO.getPurchaseOrderId(), memberId); + + if (purchaseOrder == null) { + throw new PurchaseOrderCommonException(PurchaseOrderErrorCode.PURCHASE_ORDER_NOT_FOUND); + } + + String unescapedUrl = StringEscapeUtils.unescapeJson(purchaseOrder.getContent()); + purchaseOrder.setContent(unescapedUrl); + + return purchaseOrder; + } + + @Override + @Transactional(readOnly = true) + public Page selectAllPurchaseOrderAdmin(Pageable pageable, PurchaseOrderSelectAllDTO purchaseOrderSelectAllDTO) { + + String memberId = authQueryService.selectMemberIdByLoginId(purchaseOrderSelectAllDTO.getMemberId()); + + int offset = Math.toIntExact(pageable.getOffset()); + int pageSize = pageable.getPageSize(); + + String cacheKey = "myCache::purchaseOrders::offset=" + offset + "::pageSize=" + pageSize; + + // 캐시 조회 + List purchaseOrders = (List) redisTemplate.opsForValue().get(cacheKey); + + if (purchaseOrders == null) { + purchaseOrders = purchaseOrderMapper.findAllPurchaseOrderByMemberId(offset, pageSize, memberId); + + if(purchaseOrders == null) { + throw new PurchaseOrderCommonException(PurchaseOrderErrorCode.PURCHASE_ORDER_NOT_FOUND); + } + + redisTemplate.opsForValue().set(cacheKey, purchaseOrders); + } + + Integer count = purchaseOrderMapper.findAllPurchaseOrderCountByMemberId(memberId); + int totalPurchaseOrder = (count != null) ? count : 0; + + return new PageImpl<>(purchaseOrders, pageable, totalPurchaseOrder); + } + + @Override + @Transactional(readOnly = true) + public Page selectSearchPurchaseOrderAdmin(PurchaseOrderSelectSearchDTO purchaseOrderSelectSearchDTO, Pageable pageable) throws GeneralSecurityException { + + String memberId = authQueryService.selectMemberIdByLoginId(purchaseOrderSelectSearchDTO.getMemberId()); + purchaseOrderSelectSearchDTO.setMemberId(memberId); + + if ("대기".equals(purchaseOrderSelectSearchDTO.getStatus())) { + purchaseOrderSelectSearchDTO.setStatus("WAIT"); + } + if ("승인".equals(purchaseOrderSelectSearchDTO.getStatus())) { + purchaseOrderSelectSearchDTO.setStatus("APPROVED"); + } + if ("취소".equals(purchaseOrderSelectSearchDTO.getStatus())) { + purchaseOrderSelectSearchDTO.setStatus("CANCEL"); + } + + int offset = Math.toIntExact(pageable.getOffset()); + int pageSize = pageable.getPageSize(); + + // 정렬 정보 가져오기 + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + List purchaseOrders = purchaseOrderMapper.findSearchPurchaseOrderMemberId(offset, pageSize, purchaseOrderSelectSearchDTO, sortField, sortOrder); + + if (purchaseOrders == null) { + throw new PurchaseOrderCommonException(PurchaseOrderErrorCode.PURCHASE_ORDER_NOT_FOUND); + } + + for (PurchaseOrderSelectSearchDTO purchaseOrder : purchaseOrders) { + if (purchaseOrder.getMemberId() != null) { + String memberName = memberQueryService.selectNameById(purchaseOrder.getMemberId()); + purchaseOrder.setMemberName(memberName); + } + + if (purchaseOrder.getAdminId() != null) { + String adminName = memberQueryService.selectNameById(purchaseOrder.getAdminId()); + purchaseOrder.setAdminName(adminName); + } else { + purchaseOrder.setAdminName("-"); + } + } + + int count = purchaseOrderMapper.findSearchPurchaseOrderCountMemberId(purchaseOrderSelectSearchDTO); + + if (count == 0) { + throw new PurchaseOrderCommonException(PurchaseOrderErrorCode.PURCHASE_ORDER_NOT_FOUND); + } + + return new PageImpl<>(purchaseOrders, pageable, count); + } + + // 영업 담장자 조회 + @Override + @Transactional(readOnly = true) + public PurchaseOrderSelectIdDTO selectDetailPurchaseOrder(PurchaseOrderSelectIdDTO purchaseOrderSelectIdDTO) { + + PurchaseOrderSelectIdDTO purchaseOrder = purchaseOrderMapper.findPurchaseOrderByPurchaseOrderId(purchaseOrderSelectIdDTO.getPurchaseOrderId()); + + if (purchaseOrder == null) { + throw new PurchaseOrderCommonException(PurchaseOrderErrorCode.PURCHASE_ORDER_NOT_FOUND); + } + return purchaseOrder; + } + + @Override + @Transactional(readOnly = true) + public Page selectAllPurchaseOrder(Pageable pageable, PurchaseOrderSelectAllDTO purchaseOrderSelectAllDTO) { + + int offset = Math.toIntExact(pageable.getOffset()); + int pageSize = pageable.getPageSize(); + + String cacheKey = "myCache::purchaseOrders::offset=" + offset + "::size=" + pageSize; + + // 캐시 조회 + List purchaseOrders = (List) redisTemplate.opsForValue().get(cacheKey); + + if (purchaseOrders == null) { + purchaseOrders = purchaseOrderMapper.findAllPurchaseOrder(offset, pageSize); + + if(purchaseOrders == null) { + throw new PurchaseOrderCommonException(PurchaseOrderErrorCode.PURCHASE_ORDER_NOT_FOUND); + } + + redisTemplate.opsForValue().set(cacheKey, purchaseOrders); + } + + Integer count = purchaseOrderMapper.findAllPurchaseOrderCount(); + int totalPurchaseOrder = (count != null) ? count : 0; + + return new PageImpl<>(purchaseOrders, pageable, totalPurchaseOrder); + } + + @Override + @Transactional(readOnly = true) + public Page selectSearchPurchaseOrder(PurchaseOrderSelectSearchDTO purchaseOrderSelectSearchDTO, Pageable pageable) throws GeneralSecurityException { + + if ("대기".equals(purchaseOrderSelectSearchDTO.getStatus())) { + purchaseOrderSelectSearchDTO.setStatus("WAIT"); + } + if ("승인".equals(purchaseOrderSelectSearchDTO.getStatus())) { + purchaseOrderSelectSearchDTO.setStatus("APPROVED"); + } + if ("취소".equals(purchaseOrderSelectSearchDTO.getStatus())) { + purchaseOrderSelectSearchDTO.setStatus("CANCEL"); + } + + int offset = Math.toIntExact(pageable.getOffset()); + int pageSize = pageable.getPageSize(); + + // 정렬 정보 가져오기 + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + List purchaseOrders = purchaseOrderMapper.findSearchPurchaseOrder(offset, pageSize, purchaseOrderSelectSearchDTO, sortField, sortOrder); + + if (purchaseOrders == null) { + throw new PurchaseOrderCommonException(PurchaseOrderErrorCode.PURCHASE_ORDER_NOT_FOUND); + } + + for (PurchaseOrderSelectSearchDTO purchaseOrder : purchaseOrders) { + if (purchaseOrder.getMemberId() != null) { + String memberName = memberQueryService.selectNameById(purchaseOrder.getMemberId()); + purchaseOrder.setMemberName(memberName); + } + + if (purchaseOrder.getAdminId() != null) { + String adminName = memberQueryService.selectNameById(purchaseOrder.getAdminId()); + purchaseOrder.setAdminName(adminName); + } else { + purchaseOrder.setAdminName("-"); + } + } + + int count = purchaseOrderMapper.findSearchPurchaseOrderCount(purchaseOrderSelectSearchDTO); + + if (count == 0) { + throw new PurchaseOrderCommonException(PurchaseOrderErrorCode.PURCHASE_ORDER_NOT_FOUND); + } + + return new PageImpl<>(purchaseOrders, pageable, count); + } + + @Override + @Transactional(readOnly = true) + public void exportPurchaseOrder(HttpServletResponse response) throws GeneralSecurityException { + + List purchaseOrderExcels = purchaseOrderMapper.findPurchaseOrderForExcel(); + + if(purchaseOrderExcels == null) { + throw new PurchaseOrderCommonException(PurchaseOrderErrorCode.PURCHASE_ORDER_NOT_FOUND); + } + + for(int i=0;i postFile(FileDTO fileDTO,@RequestPart("file") MultipartFile file) { + fileDTO.setFileUrl(s3FileService.uploadOneFile(file)); + String url=s3FileService.uploadOneFile(file); + s3FileService.registerFile(fileDTO); + return ResponseEntity.ok(S3ResponseMessage.builder() + .httpStatus(200) + .msg(url) + .result(null) + .build()); + + } +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/s3/command/application/dto/FileDTO.java b/src/main/java/stanl_2/final_backend/domain/s3/command/application/dto/FileDTO.java new file mode 100644 index 00000000..d6e8be99 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/s3/command/application/dto/FileDTO.java @@ -0,0 +1,12 @@ +package stanl_2.final_backend.domain.s3.command.application.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class FileDTO { + private String fileId; + private String fileUrl; +} diff --git a/src/main/java/stanl_2/final_backend/domain/s3/command/application/service/S3FileService.java b/src/main/java/stanl_2/final_backend/domain/s3/command/application/service/S3FileService.java new file mode 100644 index 00000000..ada8fffd --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/s3/command/application/service/S3FileService.java @@ -0,0 +1,26 @@ +package stanl_2.final_backend.domain.s3.command.application.service; + +import org.springframework.web.multipart.MultipartFile; +import stanl_2.final_backend.domain.s3.command.application.dto.FileDTO; + +public interface S3FileService { + + // 파일 업로드(단일) + String uploadOneFile(MultipartFile file); + + // 파일 업로드(다중) + + // html 업로드 + String uploadHtml(String htmlContent, String fileName); + + // 파일 삭제 + void deleteFile(String fileName); + + // 파일 URL 조회 + String getFileUrl(String fileName); + + // 파일 다운로드 + byte[] downloadFile(String fileName); + + void registerFile(FileDTO fileDTO); +} diff --git a/src/main/java/stanl_2/final_backend/domain/s3/command/domain/domain/aggregate/File.java b/src/main/java/stanl_2/final_backend/domain/s3/command/domain/domain/aggregate/File.java new file mode 100644 index 00000000..79a538cc --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/s3/command/domain/domain/aggregate/File.java @@ -0,0 +1,29 @@ +package stanl_2.final_backend.domain.s3.command.domain.domain.aggregate; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + +@Entity +@Table(name="TB_FILE") +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class File { + @Id + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name="prefix", value = "FIL") + ) + @Column(name = "FIL_ID") + private String fileId; + + @Column(name = "FIL_URL") + private String fileUrl; +} diff --git a/src/main/java/stanl_2/final_backend/domain/s3/command/domain/repository/FileRepository.java b/src/main/java/stanl_2/final_backend/domain/s3/command/domain/repository/FileRepository.java new file mode 100644 index 00000000..9239851b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/s3/command/domain/repository/FileRepository.java @@ -0,0 +1,8 @@ +package stanl_2.final_backend.domain.s3.command.domain.repository; + + +import org.springframework.data.jpa.repository.JpaRepository; +import stanl_2.final_backend.domain.s3.command.domain.domain.aggregate.File; + +public interface FileRepository extends JpaRepository { +} diff --git a/src/main/java/stanl_2/final_backend/domain/s3/command/domain/service/S3FileServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/s3/command/domain/service/S3FileServiceImpl.java new file mode 100644 index 00000000..9605939d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/s3/command/domain/service/S3FileServiceImpl.java @@ -0,0 +1,170 @@ +package stanl_2.final_backend.domain.s3.command.domain.service; + +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.model.DeleteObjectRequest; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.S3Object; +import com.amazonaws.services.s3.model.S3ObjectInputStream; +import com.amazonaws.util.IOUtils; +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import stanl_2.final_backend.domain.notices.command.application.dto.NoticeAlarmDTO; +import stanl_2.final_backend.domain.notices.command.application.dto.NoticeRegistDTO; +import stanl_2.final_backend.domain.notices.command.domain.aggragate.entity.Notice; +import stanl_2.final_backend.domain.notices.common.exception.NoticeCommonException; +import stanl_2.final_backend.domain.notices.common.exception.NoticeErrorCode; +import stanl_2.final_backend.domain.s3.command.application.dto.FileDTO; +import stanl_2.final_backend.domain.s3.command.application.service.S3FileService; +import stanl_2.final_backend.domain.s3.command.domain.domain.aggregate.File; +import stanl_2.final_backend.domain.s3.command.domain.repository.FileRepository; +import stanl_2.final_backend.domain.s3.common.exception.S3CommonException; +import stanl_2.final_backend.domain.s3.common.exception.S3ErrorCode; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.Principal; +import java.util.ArrayList; +import java.util.UUID; + +@Component +@Service +public class S3FileServiceImpl implements S3FileService { + + private final AmazonS3Client amazonS3Client; + private final FileRepository fileRepository; + private final ModelMapper modelMapper; + + @Autowired + public S3FileServiceImpl(AmazonS3Client amazonS3Client, FileRepository fileRepository, ModelMapper modelMapper) { + this.amazonS3Client = amazonS3Client; + this.fileRepository = fileRepository; + this.modelMapper = modelMapper; + } + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + @Value("${cloud.aws.region.static}") + private String region; + + // 단일 파일 업로드 + @Override + public String uploadOneFile(MultipartFile file) { + + String fileName = createFileName(file.getOriginalFilename()); + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentType(file.getContentType()); + metadata.setContentLength(file.getSize()); + + try(InputStream inputStream = file.getInputStream()) { + amazonS3Client.putObject(bucket, fileName, inputStream, metadata); + + return "https://" + bucket + ".s3." + region + ".amazonaws.com/" + fileName; + } catch (IOException e) { + throw new S3CommonException(S3ErrorCode.FILE_UPLOAD_BAD_REQUEST); + } + } + + public String uploadHtml(String htmlContent, String fileName) { + + String fileKey = createFileName(fileName + ".html"); // 파일 이름 생성 (UUID로 고유화 가능) + + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentType("text/html; charset=UTF-8"); + metadata.setContentLength(htmlContent.getBytes(StandardCharsets.UTF_8).length); + + try (InputStream inputStream = new ByteArrayInputStream(htmlContent.getBytes(StandardCharsets.UTF_8))) { + amazonS3Client.putObject(bucket, fileKey, inputStream, metadata); + + return "https://" + bucket + ".s3." + region + ".amazonaws.com/" + fileKey; + } catch (IOException e) { + throw new S3CommonException(S3ErrorCode.FILE_UPLOAD_BAD_REQUEST); + } + } + + // 파일 삭제 + @Override + public void deleteFile(String fileName) { + amazonS3Client.deleteObject(new DeleteObjectRequest(bucket, fileName)); + } + + // AWS S3에 저장된 파일 이름에 해당하는 URL(경로)를 알아내는 함수 + @Override + public String getFileUrl(String fileName) { + return amazonS3Client.getUrl(bucket, fileName).toString(); + } + + // 파일 다운로드 + @Override + public byte[] downloadFile(String fileName) { + + // 파일 유무 확인 + validateFileExists(fileName); + + S3Object s3Object = amazonS3Client.getObject(bucket, fileName); + S3ObjectInputStream s3ObjectContent = s3Object.getObjectContent(); + + try { + return IOUtils.toByteArray(s3ObjectContent); + } catch (IOException e) { + throw new S3CommonException(S3ErrorCode.FILE_NOT_FOUND); + } + } + + private void validateFileExists(String fileName) { + if (!amazonS3Client.doesObjectExist(bucket, fileName)) { + throw new S3CommonException(S3ErrorCode.FILE_NOT_FOUND); + } + } + + private String createFileName(String fileName) { + return UUID.randomUUID().toString().concat(getFileExtension(fileName)); + } + + private String getFileExtension(String fileName) { + if (fileName.length() == 0) { + throw new S3CommonException(S3ErrorCode.FILE_NOT_FOUND); + } + + ArrayList fileValidate = new ArrayList<>(); + fileValidate.add(".jpg"); + fileValidate.add(".jpeg"); + fileValidate.add(".png"); + fileValidate.add(".JPG"); + fileValidate.add(".JPEG"); + fileValidate.add(".PNG"); + fileValidate.add(".html"); + fileValidate.add(".xlsx"); + fileValidate.add(".pdf"); + fileValidate.add(".webp"); + String idxFileName = fileName.substring(fileName.lastIndexOf(".")); + if(!fileValidate.contains(idxFileName)) { + throw new S3CommonException(S3ErrorCode.FILE_NOT_FOUND); + } + return fileName.substring(fileName.lastIndexOf(".")); + } + + + @Transactional + public void registerFile(FileDTO fileDTO) { + try { + File file = modelMapper.map(fileDTO, File.class); + fileRepository.save(file); + + } catch (DataIntegrityViolationException e){ + // DB 무결정 제약 조건 (NOT NULL, UNIQUE) 위반 + throw new S3CommonException(S3ErrorCode.DATA_INTEGRITY_VIOLATION); + } catch (Exception e) { + // 서버 오류 + throw new S3CommonException(S3ErrorCode.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/s3/common/exception/S3CommonException.java b/src/main/java/stanl_2/final_backend/domain/s3/common/exception/S3CommonException.java new file mode 100644 index 00000000..aac705cb --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/s3/common/exception/S3CommonException.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.s3.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class S3CommonException extends RuntimeException { + private final S3ErrorCode sampleErrorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.sampleErrorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/s3/common/exception/S3ErrorCode.java b/src/main/java/stanl_2/final_backend/domain/s3/common/exception/S3ErrorCode.java new file mode 100644 index 00000000..b2894ad2 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/s3/common/exception/S3ErrorCode.java @@ -0,0 +1,52 @@ +package stanl_2.final_backend.domain.s3.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum S3ErrorCode { + + /** + * 400(Bad Request) + * 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. + */ + FILE_UPLOAD_BAD_REQUEST(400001, HttpStatus.BAD_REQUEST, "업로드에 실패했습니다."), + + + /** + * 401(Unauthorized) + * 비록 HTTP 표준에서는 "미승인(unauthorized)"를 명확히 하고 있지만, + * 의미상 이 응답은 "비인증(unauthenticated)"을 의미합니다. + * 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로를 인증해야 합니다. + */ + + + /** + * 403(Forbidden) + * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. + * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. + */ + + + + /** + * 404(Not Found) + * 서버는 요청받은 리소스를 찾을 수 없습니다. 브라우저에서는 알려지지 않은 URL을 의미합니다. + * 이것은 API에서 종점은 적절하지만 리소스 자체는 존재하지 않음을 의미할 수도 있습니다. + * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. + * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. + */ + FILE_NOT_FOUND(404001, HttpStatus.NOT_FOUND, "파일이 존재하지 않습니다."), + DATA_INTEGRITY_VIOLATION(40402, HttpStatus.BAD_REQUEST, "데이터 무결 위반하였습니다."), + /** + * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. + */ + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); + + + private final Integer code; + private final HttpStatus httpStatus; + private final String msg; +} diff --git a/src/main/java/stanl_2/final_backend/domain/s3/common/exception/S3ExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/s3/common/exception/S3ExceptionResponse.java new file mode 100644 index 00000000..26548574 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/s3/common/exception/S3ExceptionResponse.java @@ -0,0 +1,22 @@ +package stanl_2.final_backend.domain.s3.common.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class S3ExceptionResponse { + private final Integer code; + private final String msg; + private final HttpStatus httpStatus; + + public S3ExceptionResponse(S3ErrorCode sampleErrorCode) { + this.code = sampleErrorCode.getCode(); + this.msg = sampleErrorCode.getMsg(); + this.httpStatus = sampleErrorCode.getHttpStatus(); + } + + public static S3ExceptionResponse of(S3ErrorCode sampleErrorCode) { + return new S3ExceptionResponse(sampleErrorCode); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/s3/common/response/S3ResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/s3/common/response/S3ResponseMessage.java new file mode 100644 index 00000000..a32d184b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/s3/common/response/S3ResponseMessage.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.s3.common.response; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +public class S3ResponseMessage { + private int httpStatus; + private String msg; + private Object result; +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/sales_history/command/application/dto/SalesHistoryRegistDTO.java b/src/main/java/stanl_2/final_backend/domain/sales_history/command/application/dto/SalesHistoryRegistDTO.java new file mode 100644 index 00000000..bbd3032f --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/sales_history/command/application/dto/SalesHistoryRegistDTO.java @@ -0,0 +1,21 @@ +package stanl_2.final_backend.domain.sales_history.command.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class SalesHistoryRegistDTO { + private Integer numberOfVehicles; + private Integer totalSales; + private Double incentive; + private String contractId; + private String customerInfoId; + private String productId; + private String memberId; + private String centerId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/sales_history/command/application/service/SalesHistoryCommandService.java b/src/main/java/stanl_2/final_backend/domain/sales_history/command/application/service/SalesHistoryCommandService.java new file mode 100644 index 00000000..8047eefa --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/sales_history/command/application/service/SalesHistoryCommandService.java @@ -0,0 +1,11 @@ +package stanl_2.final_backend.domain.sales_history.command.application.service; + +import stanl_2.final_backend.domain.sales_history.command.application.dto.SalesHistoryRegistDTO; +import stanl_2.final_backend.domain.sales_history.command.domain.repository.SalesHistoryRepository; + +public interface SalesHistoryCommandService { + + void registerSalesHistory(String contractId); + + void deleteSalesHistory(String id); +} diff --git a/src/main/java/stanl_2/final_backend/domain/sales_history/command/domain/aggregate/entity/SalesHistory.java b/src/main/java/stanl_2/final_backend/domain/sales_history/command/domain/aggregate/entity/SalesHistory.java new file mode 100644 index 00000000..d19f5ec8 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/sales_history/command/domain/aggregate/entity/SalesHistory.java @@ -0,0 +1,73 @@ +package stanl_2.final_backend.domain.sales_history.command.domain.aggregate.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; +import stanl_2.final_backend.global.config.PrefixGeneratorConfig; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "TB_SALES_HISTORY") +public class SalesHistory { + @Id + @GeneratedValue(generator = "PrefixGeneratorConfig") + @GenericGenerator(name = "PrefixGeneratorConfig", + type = PrefixGeneratorConfig.class, + parameters = @org.hibernate.annotations.Parameter(name = "prefix", value = "SAL") + ) + @Column(name = "SAL_HIST_ID") + private String salesHistoryId; + + @Column(name = "SAL_HIST_NO_OF_VEH", nullable = false) + private Integer numberOfVehicles; + + @Column(name = "SAL_HIST_TOTA_SALE", nullable = false) + private Integer totalSales; + + @Column(name = "SAL_HIST_INCE", nullable = false) + private Double incentive; + + @Column(name = "CREATED_AT", nullable = false) + private String createdAt; + + @Column(name = "DELETED_AT") + private String deletedAt; + + @Column(name = "ACTIVE", nullable = false) + private Boolean active = true; + + @Column(name = "CONR_ID", nullable = false) + private String contractId; + + @Column(name = "CUST_ID", nullable = false) + private String customerInfoId; + + @Column(name = "PROD_ID", nullable = false) + private String productId; + + @Column(name = "MEM_ID", nullable = false) + private String memberId; + + @Column(name = "CENT_ID", nullable = false) + private String centerId; + + @PrePersist + private void prePersist() { + this.createdAt = getCurrentTime(); + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/sales_history/command/domain/repository/SalesHistoryRepository.java b/src/main/java/stanl_2/final_backend/domain/sales_history/command/domain/repository/SalesHistoryRepository.java new file mode 100644 index 00000000..f8eb8cf2 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/sales_history/command/domain/repository/SalesHistoryRepository.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.domain.sales_history.command.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import stanl_2.final_backend.domain.sales_history.command.domain.aggregate.entity.SalesHistory; + +@Repository +public interface SalesHistoryRepository extends JpaRepository { +} diff --git a/src/main/java/stanl_2/final_backend/domain/sales_history/command/domain/service/SalesHistoryCommandServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/sales_history/command/domain/service/SalesHistoryCommandServiceImpl.java new file mode 100644 index 00000000..6f99cbe2 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/sales_history/command/domain/service/SalesHistoryCommandServiceImpl.java @@ -0,0 +1,91 @@ +package stanl_2.final_backend.domain.sales_history.command.domain.service; + +import lombok.extern.slf4j.Slf4j; +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.contract.query.dto.ContractSeletIdDTO; +import stanl_2.final_backend.domain.contract.query.service.ContractQueryService; +import stanl_2.final_backend.domain.sales_history.command.application.dto.SalesHistoryRegistDTO; +import stanl_2.final_backend.domain.sales_history.command.application.service.SalesHistoryCommandService; +import stanl_2.final_backend.domain.sales_history.command.domain.aggregate.entity.SalesHistory; +import stanl_2.final_backend.domain.sales_history.command.domain.repository.SalesHistoryRepository; +import stanl_2.final_backend.domain.sales_history.common.exception.SalesHistoryCommonException; +import stanl_2.final_backend.domain.sales_history.common.exception.SalesHistoryErrorCode; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@Slf4j +@Service +@Transactional(readOnly = true) +public class SalesHistoryCommandServiceImpl implements SalesHistoryCommandService { + + private final SalesHistoryRepository salesHistoryRepository; + private final ContractQueryService contractQueryService; + private final ModelMapper modelMapper; + + @Autowired + public SalesHistoryCommandServiceImpl(SalesHistoryRepository salesHistoryRepository, ContractQueryService contractQueryService, ModelMapper modelMapper) { + this.salesHistoryRepository = salesHistoryRepository; + this.contractQueryService = contractQueryService; + this.modelMapper = modelMapper; + } + + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + + @Override + @Transactional + public void registerSalesHistory(String contractId) { + + ContractSeletIdDTO salesHistoryDTO = new ContractSeletIdDTO(); + SalesHistoryRegistDTO salesHistoryRegistDTO = new SalesHistoryRegistDTO(); + + salesHistoryDTO.setContractId(contractId); + + ContractSeletIdDTO responseContract = contractQueryService.selectDetailContract(salesHistoryDTO); + + if (responseContract == null) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.CONTRACT_NOT_FOUND); + } + + salesHistoryRegistDTO.setContractId(contractId); + salesHistoryRegistDTO.setTotalSales(responseContract.getTotalSales()); + salesHistoryRegistDTO.setNumberOfVehicles(responseContract.getNumberOfVehicles()); + salesHistoryRegistDTO.setCustomerInfoId(responseContract.getCustomerId()); + salesHistoryRegistDTO.setProductId(responseContract.getProductId()); + salesHistoryRegistDTO.setCenterId(responseContract.getCenterId()); + salesHistoryRegistDTO.setMemberId(responseContract.getMemberId()); + + String customerPurchaseCondition = responseContract.getCustomerPurchaseCondition(); + + if(customerPurchaseCondition.equals("CASH")){ + salesHistoryRegistDTO.setIncentive(responseContract.getTotalSales() * 0.035); + }else if(customerPurchaseCondition.equals("INSTALLMENT")){ + salesHistoryRegistDTO.setIncentive(responseContract.getTotalSales() * 0.04); + }else if(customerPurchaseCondition.equals("LEASE")){ + salesHistoryRegistDTO.setIncentive(responseContract.getTotalSales() * 0.045); + } + + SalesHistory salesHistory = modelMapper.map(salesHistoryRegistDTO, SalesHistory.class); + + salesHistoryRepository.save(salesHistory); + } + + @Override + @Transactional + public void deleteSalesHistory(String id) { + SalesHistory salesHistory = salesHistoryRepository.findById(id) + .orElseThrow(() -> new SalesHistoryCommonException(SalesHistoryErrorCode.SALES_HISTORY_NOT_FOUND)); + + salesHistory.setActive(false); + salesHistory.setDeletedAt(getCurrentTime()); + + salesHistoryRepository.save(salesHistory); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/sales_history/common/exception/SalesHistoryCommonException.java b/src/main/java/stanl_2/final_backend/domain/sales_history/common/exception/SalesHistoryCommonException.java new file mode 100644 index 00000000..4329600d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/sales_history/common/exception/SalesHistoryCommonException.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.sales_history.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class SalesHistoryCommonException extends RuntimeException { + private final SalesHistoryErrorCode salesHistoryErrorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.salesHistoryErrorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/sales_history/common/exception/SalesHistoryErrorCode.java b/src/main/java/stanl_2/final_backend/domain/sales_history/common/exception/SalesHistoryErrorCode.java new file mode 100644 index 00000000..384cdbc5 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/sales_history/common/exception/SalesHistoryErrorCode.java @@ -0,0 +1,55 @@ +package stanl_2.final_backend.domain.sales_history.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum SalesHistoryErrorCode { + + /** + * 400(Bad Request) + * 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. + */ + + + + /** + * 401(Unauthorized) + * 비록 HTTP 표준에서는 "미승인(unauthorized)"를 명확히 하고 있지만, + * 의미상 이 응답은 "비인증(unauthenticated)"을 의미합니다. + * 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로를 인증해야 합니다. + */ + + + /** + * 403(Forbidden) + * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. + * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. + */ + + + + /** + * 404(Not Found) + * 서버는 요청받은 리소스를 찾을 수 없습니다. 브라우저에서는 알려지지 않은 URL을 의미합니다. + * 이것은 API에서 종점은 적절하지만 리소스 자체는 존재하지 않음을 의미할 수도 있습니다. + * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. + * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. + */ + MEMBER_NOT_FOUND(404001, HttpStatus.NOT_FOUND, "사원 데이터를 찾지 못했습니다"), + CUSTOMER_NOT_FOUND(404002, HttpStatus.NOT_FOUND, "고객 데이터를 찾지 못했습니다"), + CENTER_NOT_FOUND(404003, HttpStatus.NOT_FOUND, "센터 데이터를 찾지 못했습니다"), + SALES_HISTORY_NOT_FOUND(404004, HttpStatus.NOT_FOUND, "판매내역 데이터를 찾지 못했습니다"), + CONTRACT_NOT_FOUND(404005, HttpStatus.NOT_FOUND, "계약서 데이터를 찾지 못했습니다"), + /** + * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. + */ + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); + + + private final Integer code; + private final HttpStatus httpStatus; + private final String msg; +} diff --git a/src/main/java/stanl_2/final_backend/domain/sales_history/common/exception/SalesHistoryExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/sales_history/common/exception/SalesHistoryExceptionResponse.java new file mode 100644 index 00000000..286f7742 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/sales_history/common/exception/SalesHistoryExceptionResponse.java @@ -0,0 +1,22 @@ +package stanl_2.final_backend.domain.sales_history.common.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class SalesHistoryExceptionResponse { + private final Integer code; + private final String msg; + private final HttpStatus httpStatus; + + public SalesHistoryExceptionResponse(SalesHistoryErrorCode salesHistoryErrorCode) { + this.code = salesHistoryErrorCode.getCode(); + this.msg = salesHistoryErrorCode.getMsg(); + this.httpStatus = salesHistoryErrorCode.getHttpStatus(); + } + + public static SalesHistoryExceptionResponse of(SalesHistoryErrorCode salesHistoryErrorCode) { + return new SalesHistoryExceptionResponse(salesHistoryErrorCode); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/sales_history/common/response/SalesHistoryResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/sales_history/common/response/SalesHistoryResponseMessage.java new file mode 100644 index 00000000..4c5a92ce --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/sales_history/common/response/SalesHistoryResponseMessage.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.domain.sales_history.common.response; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +public class SalesHistoryResponseMessage { + private int httpStatus; + private String msg; + private Object result; +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/sales_history/query/controller/SalesHistoryController.java b/src/main/java/stanl_2/final_backend/domain/sales_history/query/controller/SalesHistoryController.java new file mode 100644 index 00000000..67eb02ea --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/sales_history/query/controller/SalesHistoryController.java @@ -0,0 +1,431 @@ +package stanl_2.final_backend.domain.sales_history.query.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stanl_2.final_backend.domain.product.common.response.ProductResponseMessage; +import stanl_2.final_backend.domain.sales_history.common.response.SalesHistoryResponseMessage; +import stanl_2.final_backend.domain.sales_history.query.dto.*; +import stanl_2.final_backend.domain.sales_history.query.service.SalesHistoryQueryService; + +import java.security.GeneralSecurityException; +import java.security.Principal; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@RestController(value = "querySalesHistoryController") +@RequestMapping("/api/v1/salesHistory") +public class SalesHistoryController { + private final SalesHistoryQueryService salesHistoryQueryService; + + @Autowired + public SalesHistoryController(SalesHistoryQueryService salesHistoryQueryService) { + this.salesHistoryQueryService = salesHistoryQueryService; + } + + @Operation(summary = "판매내역 조회(사원, 관리자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = SalesHistoryResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/employee") + public ResponseEntity getAllSalesHistoryByEmployee(Principal principal, + @PageableDefault(size = 20) Pageable pageable, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder){ + SalesHistorySelectDTO salesHistorySelectDTO = new SalesHistorySelectDTO(); + + salesHistorySelectDTO.setSearcherName(principal.getName()); + + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + Page responseSalesHistory = salesHistoryQueryService.selectAllSalesHistoryByEmployee(salesHistorySelectDTO, pageable); + + return ResponseEntity.ok(SalesHistoryResponseMessage.builder() + .httpStatus(200) + .msg("사원 판매내역 조회 성공") + .result(responseSalesHistory) + .build()); + } + + @Operation(summary = "판매내역 조회(관리자, 담당자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = SalesHistoryResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("") + public ResponseEntity getAllSalesHistory(@PageableDefault(size = 20) Pageable pageable, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder){ + + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + + Page responseSalesHistory = salesHistoryQueryService.selectAllSalesHistory(pageable); + + return ResponseEntity.ok(SalesHistoryResponseMessage.builder() + .httpStatus(200) + .msg("판매내역 조회 성공") + .result(responseSalesHistory) + .build()); + } + + @Operation(summary = "판매내역 상세 조회(전체)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = SalesHistoryResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("{salesHistoryId}") + public ResponseEntity getSalesHistoryDetail(@PathVariable("salesHistoryId") String salesHistoryId){ + + SalesHistorySelectDTO salesHistorySelectDTO = new SalesHistorySelectDTO(); + + salesHistorySelectDTO.setSalesHistoryId(salesHistoryId); + + SalesHistorySelectDTO responseSalesHistory = salesHistoryQueryService.selectSalesHistoryDetail(salesHistorySelectDTO); + + return ResponseEntity.ok(SalesHistoryResponseMessage.builder() + .httpStatus(200) + .msg("판매내역 상세 조회 성공") + .result(responseSalesHistory) + .build()); + } + + @Operation(summary = "판매내역 조회기간별 검색(사원)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = SalesHistoryResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @PostMapping("/employee/search") + public ResponseEntity getSalesHistorySearchByEmployee(@RequestBody SalesHistorySearchDTO salesHistorySearchDTO + ,Principal principal, + @PageableDefault(size = 20) Pageable pageable, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder){ + + salesHistorySearchDTO.setSearcherName(principal.getName()); + + + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + Page responseSalesHistory = salesHistoryQueryService.selectSalesHistorySearchByEmployee(salesHistorySearchDTO, pageable); + + return ResponseEntity.ok(SalesHistoryResponseMessage.builder() + .httpStatus(200) + .msg("판매내역 조회기간별 검색(사원) 성공") + .result(responseSalesHistory) + .build()); + } + + @Operation(summary = "판매내역 조회기간별 검색") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = SalesHistoryResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @PostMapping("/search") + public ResponseEntity getSalesHistoryBySearch(@RequestBody SalesHistorySearchDTO salesHistorySearchDTO, + @PageableDefault(size = 20) Pageable pageable, + @RequestParam(required = false) String sortField, + @RequestParam(required = false) String sortOrder) throws GeneralSecurityException { + + + if (sortField != null && sortOrder != null) { + Sort.Direction direction = sortOrder.equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC; + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(direction, sortField)); + } + Page responseSalesHistory = salesHistoryQueryService.selectSalesHistoryBySearch(salesHistorySearchDTO, pageable); + + return ResponseEntity.ok(SalesHistoryResponseMessage.builder() + .httpStatus(200) + .msg("판매내역 조회기간별 검색 성공") + .result(responseSalesHistory) + .build()); + } + + + @Operation(summary = "사원 통계(실적,수당,매출액) 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = SalesHistoryResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("employee/statistics") + public ResponseEntity getStatisticsByEmployee(Principal principal){ + + SalesHistorySelectDTO salesHistorySelectDTO = new SalesHistorySelectDTO(); + + salesHistorySelectDTO.setSearcherName(principal.getName()); + + SalesHistoryStatisticsDTO responseSalesHistory = salesHistoryQueryService.selectStatisticsByEmployee(salesHistorySelectDTO); + + return ResponseEntity.ok(SalesHistoryResponseMessage.builder() + .httpStatus(200) + .msg("사원 통계(실적,수당,매출액) 조회 성공") + .result(responseSalesHistory) + .build()); + } + + @Operation(summary = "사원 통계(실적,수당,매출액) 조회기간별 검색") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = SalesHistoryResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("employee/statistics/search") + public ResponseEntity getStatisticsSearchByEmployee(@RequestParam Map params + ,Principal principal, + @PageableDefault(size = 20) Pageable pageable){ + + SalesHistorySearchDTO salesHistorySearchDTO = new SalesHistorySearchDTO(); + + salesHistorySearchDTO.setSearcherName(principal.getName()); + salesHistorySearchDTO.setStartDate(params.get("startDate")); + salesHistorySearchDTO.setEndDate(params.get("endDate")); + salesHistorySearchDTO.setPeriod(params.get("period")); + + SalesHistoryStatisticsDTO responseSalesHistory = salesHistoryQueryService.selectStatisticsSearchByEmployee(salesHistorySearchDTO); + + return ResponseEntity.ok(SalesHistoryResponseMessage.builder() + .httpStatus(200) + .msg("사원 통계(실적,수당,매출액) 검색 성공") + .result(responseSalesHistory) + .build()); + } + + @Operation(summary = "사원 통계(실적,수당,매출액) 일자별 검색") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = SalesHistoryResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("employee/statistics/search/daily") + public ResponseEntity getStatisticsSearchByEmployeeDaily(@RequestParam Map params + ,Principal principal, + @PageableDefault(size = 20) Pageable pageable){ + + SalesHistorySearchDTO salesHistorySearchDTO = new SalesHistorySearchDTO(); + + salesHistorySearchDTO.setSearcherName(principal.getName()); + salesHistorySearchDTO.setStartDate(params.get("startDate")); + salesHistorySearchDTO.setEndDate(params.get("endDate")); + salesHistorySearchDTO.setPeriod(params.get("period")); + + List responseSalesHistory = salesHistoryQueryService.selectStatisticsSearchByEmployeeDaily(salesHistorySearchDTO); + + return ResponseEntity.ok(SalesHistoryResponseMessage.builder() + .httpStatus(200) + .msg("사원 통계(실적,수당,매출액) 일자별 검색 성공") + .result(responseSalesHistory) + .build()); + } + + @Operation(summary = "본인 통계(실적,수당,매출액) 조회기간별 검색") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = SalesHistoryResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @PostMapping("statistics/mySearch") + public ResponseEntity getMyStatisticsBySearch(@RequestBody SalesHistoryRankedDataDTO salesHistoryRankedDataDTO, + @PageableDefault(size = 20) Pageable pageable, + Principal principal){ + + + +// salesHistoryRankedDataDTO.setMemberList(memberList); + salesHistoryRankedDataDTO.setMemberId(principal.getName()); + + Page responseSalesHistory = salesHistoryQueryService.selectMyStatisticsBySearch(salesHistoryRankedDataDTO,pageable); + + return ResponseEntity.ok(SalesHistoryResponseMessage.builder() + .httpStatus(200) + .msg("본인 통계(실적,수당,매출액) 검색 성공") + .result(responseSalesHistory) + .build()); + } + + + @Operation(summary = "사원 통계(실적,수당,매출액) 월 별 검색") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = SalesHistoryResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("employee/statistics/search/month") + public ResponseEntity getStatisticsSearchMonthByEmployee(@RequestParam Map params + ,Principal principal, + @PageableDefault(size = 20) Pageable pageable){ + + SalesHistorySearchDTO salesHistorySearchDTO = new SalesHistorySearchDTO(); + + /* 설명. 2024-07 식으로 와야함 !!!!! */ + salesHistorySearchDTO.setSearcherName(principal.getName()); + salesHistorySearchDTO.setStartDate(params.get("startDate")); + salesHistorySearchDTO.setEndDate(params.get("endDate")); + + List responseSalesHistory = salesHistoryQueryService.selectStatisticsSearchMonthByEmployee(salesHistorySearchDTO); + + return ResponseEntity.ok(SalesHistoryResponseMessage.builder() + .httpStatus(200) + .msg("사원 통계(실적,수당,매출액) 월 별 검색 성공") + .result(responseSalesHistory) + .build()); + } + + @Operation(summary = "사원 통계(실적,수당,매출액) 연도 별 검색") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = SalesHistoryResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("employee/statistics/search/year") + public ResponseEntity getStatisticsSearchYearByEmployee(@RequestParam Map params + ,Principal principal, + @PageableDefault(size = 20) Pageable pageable){ + + SalesHistorySearchDTO salesHistorySearchDTO = new SalesHistorySearchDTO(); + + /* 설명. 2024 식으로 와야함 !!!!! */ + salesHistorySearchDTO.setSearcherName(principal.getName()); + salesHistorySearchDTO.setStartDate(params.get("startDate")); + salesHistorySearchDTO.setEndDate(params.get("endDate")); + + List responseSalesHistory = salesHistoryQueryService.selectStatisticsSearchYearByEmployee(salesHistorySearchDTO); + + return ResponseEntity.ok(SalesHistoryResponseMessage.builder() + .httpStatus(200) + .msg("사원 통계(실적,수당,매출액) 연도 별 검색 성공") + .result(responseSalesHistory) + .build()); + } + + @Operation(summary = "사원 별 통계(실적,수당,매출액) 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = SalesHistoryResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @PostMapping("statistics") + public ResponseEntity getStatistics(@RequestBody SalesHistoryRankedDataDTO salesHistoryRankedDataDTO, + @PageableDefault(size = 20) Pageable pageable){ + + + Page responseSalesHistory = salesHistoryQueryService.selectStatistics(salesHistoryRankedDataDTO, pageable); + + return ResponseEntity.ok(SalesHistoryResponseMessage.builder() + .httpStatus(200) + .msg("사원 별 통계(실적,수당,매출액) 성공") + .result(responseSalesHistory) + .build()); + } + + @Operation(summary = "사원 별 통계(실적,수당,매출액) 조회기간별 검색") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = SalesHistoryResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @PostMapping("statistics/search") + public ResponseEntity getStatisticsBySearch(@RequestBody SalesHistoryRankedDataDTO salesHistoryRankedDataDTO, + @PageableDefault(size = 20) Pageable pageable){ + + Page responseSalesHistory = salesHistoryQueryService.selectStatisticsBySearch(salesHistoryRankedDataDTO,pageable); + + return ResponseEntity.ok(SalesHistoryResponseMessage.builder() + .httpStatus(200) + .msg("사원 별 통계(실적,수당,매출액) 검색 성공") + .result(responseSalesHistory) + .build()); + } + + @Operation(summary = "사원 별 통계(실적,수당,매출액) 조회기간별 평균 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = SalesHistoryResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @PostMapping("statistics/average") + public ResponseEntity getStatisticsEmployeeAverageBySearch(@RequestBody SalesHistoryRankedDataDTO salesHistoryRankedDataDTO, + @PageableDefault(size = 20) Pageable pageable){ + + Page responseSalesHistory = salesHistoryQueryService.selectStatisticsAverageBySearch(salesHistoryRankedDataDTO,pageable); + + return ResponseEntity.ok(SalesHistoryResponseMessage.builder() + .httpStatus(200) + .msg("사원 별 통계(실적,수당,매출액) 조회기간별 평균 조회 성공") + .result(responseSalesHistory) + .build()); + } + + @Operation(summary = "판매내역 엑셀 다운") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "판매내역 엑셀 다운 성공", + content = {@Content(schema = @Schema(implementation = SalesHistoryResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @GetMapping("/excel") + public void exporSalesHistory(HttpServletResponse response){ + + salesHistoryQueryService.exportSalesHistoryToExcel(response); + } + + @Operation(summary = "통계(실적,수당,매출액) 최대값 조회기간 별 검색(관리자, 담당자)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = SalesHistoryResponseMessage.class))}), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", + content = @Content(mediaType = "application/json")) + }) + @PostMapping("statistics/best") + public ResponseEntity getStatisticsBestBySearch(@RequestBody SalesHistoryRankedDataDTO salesHistoryRankedDataDTO, + @PageableDefault(size = 20) Pageable pageable){ + + + + Page responseSalesHistory = salesHistoryQueryService.selectStatisticsBestBySearch(salesHistoryRankedDataDTO, pageable); + return ResponseEntity.ok(SalesHistoryResponseMessage.builder() + .httpStatus(200) + .msg("통계(실적,수당,매출액) 최대값 조회기간 별 검색(관리자, 담당자)") + .result(responseSalesHistory) + .build()); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/sales_history/query/dto/SalesHistoryExcelDownload.java b/src/main/java/stanl_2/final_backend/domain/sales_history/query/dto/SalesHistoryExcelDownload.java new file mode 100644 index 00000000..66b7e361 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/sales_history/query/dto/SalesHistoryExcelDownload.java @@ -0,0 +1,33 @@ +package stanl_2.final_backend.domain.sales_history.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import stanl_2.final_backend.global.excel.ExcelColumnName; + +@Getter +@AllArgsConstructor +@Setter +public class SalesHistoryExcelDownload { + + @ExcelColumnName(name = "판매내역번호") + private String salesHistoryId; + @ExcelColumnName(name = "차량대수") + private Integer salesHistoryNumberOfVehicles; + @ExcelColumnName(name = "매출액") + private Integer salesHistoryTotalSales; + @ExcelColumnName(name = "수당") + private Integer salesHistoryIncentive; + @ExcelColumnName(name = "작성일자") + private String createdAt; + @ExcelColumnName(name = "계약서번호") + private String contractId; + @ExcelColumnName(name = "고객번호") + private String customerId; + @ExcelColumnName(name = "제품번호") + private String productId; + @ExcelColumnName(name = "담당자번호") + private String memberId; + @ExcelColumnName(name = "매장번호") + private String centerId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/sales_history/query/dto/SalesHistoryRankedDataDTO.java b/src/main/java/stanl_2/final_backend/domain/sales_history/query/dto/SalesHistoryRankedDataDTO.java new file mode 100644 index 00000000..f24fab21 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/sales_history/query/dto/SalesHistoryRankedDataDTO.java @@ -0,0 +1,32 @@ +package stanl_2.final_backend.domain.sales_history.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.math.BigDecimal; +import java.util.List; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class SalesHistoryRankedDataDTO { + private String memberId; + private BigDecimal totalIncentive; + private BigDecimal totalPerformance; + private BigDecimal totalSales; + private String centerId; + + private List memberList; + private List centerList; + private String startDate; + private String endDate; + private String createdAt; + private String month; + private String year; + private String orderBy; + private String groupBy; + private String period; +} diff --git a/src/main/java/stanl_2/final_backend/domain/sales_history/query/dto/SalesHistorySearchDTO.java b/src/main/java/stanl_2/final_backend/domain/sales_history/query/dto/SalesHistorySearchDTO.java new file mode 100644 index 00000000..360c2d70 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/sales_history/query/dto/SalesHistorySearchDTO.java @@ -0,0 +1,28 @@ +package stanl_2.final_backend.domain.sales_history.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class SalesHistorySearchDTO { + private String searcherName; + private String startDate; + private String endDate; + private List memberList; + private List centerList; + private String orderBy; + private String customerName; + private List customerList; + private List productList; + private List contractList; + private String contractId; + private String productId; + private String period; +} diff --git a/src/main/java/stanl_2/final_backend/domain/sales_history/query/dto/SalesHistorySelectDTO.java b/src/main/java/stanl_2/final_backend/domain/sales_history/query/dto/SalesHistorySelectDTO.java new file mode 100644 index 00000000..c842065d --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/sales_history/query/dto/SalesHistorySelectDTO.java @@ -0,0 +1,31 @@ +package stanl_2.final_backend.domain.sales_history.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class SalesHistorySelectDTO { + private String salesHistoryId; + private Integer salesHistoryNumberOfVehicles; + private Integer salesHistoryTotalSales; + private Integer salesHistoryIncentive; + private String createdAt; + private String deletedAt; + private String active; + private String contractId; + private String customerId; + private String productId; + private String memberId; + private String centerId; + private String searcherName; + private String orderBy; + +} diff --git a/src/main/java/stanl_2/final_backend/domain/sales_history/query/dto/SalesHistoryStatisticsAverageDTO.java b/src/main/java/stanl_2/final_backend/domain/sales_history/query/dto/SalesHistoryStatisticsAverageDTO.java new file mode 100644 index 00000000..3887a904 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/sales_history/query/dto/SalesHistoryStatisticsAverageDTO.java @@ -0,0 +1,21 @@ +package stanl_2.final_backend.domain.sales_history.query.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.math.BigDecimal; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class SalesHistoryStatisticsAverageDTO { + private BigDecimal averageTotalSales; + private BigDecimal averageTotalIncentive; + private BigDecimal averageTotalPerformance; + private String month; + private String year; + private String period; +} diff --git a/src/main/java/stanl_2/final_backend/domain/sales_history/query/dto/SalesHistoryStatisticsDTO.java b/src/main/java/stanl_2/final_backend/domain/sales_history/query/dto/SalesHistoryStatisticsDTO.java new file mode 100644 index 00000000..4c307fb1 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/sales_history/query/dto/SalesHistoryStatisticsDTO.java @@ -0,0 +1,17 @@ +package stanl_2.final_backend.domain.sales_history.query.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class SalesHistoryStatisticsDTO { + private int incentive; + private int performance; + private int totalSales; + private String orderBy; + private String month; + private String year; + private String createdAt; +} diff --git a/src/main/java/stanl_2/final_backend/domain/sales_history/query/repository/SalesHistoryMapper.java b/src/main/java/stanl_2/final_backend/domain/sales_history/query/repository/SalesHistoryMapper.java new file mode 100644 index 00000000..f39849eb --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/sales_history/query/repository/SalesHistoryMapper.java @@ -0,0 +1,84 @@ +package stanl_2.final_backend.domain.sales_history.query.repository; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import stanl_2.final_backend.domain.sales_history.query.dto.*; + +import java.util.List; + +@Mapper +public interface SalesHistoryMapper { + + List findSalesHistoryByEmployee(@Param("size") int size + , @Param("offset") int offset + , @Param("searcherId") String searcherId, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + int findSalesHistoryCountByEmployee(@Param("searcherId") String searcherId); + + List findAllSalesHistory(@Param("size") int size + , @Param("offset") int offset, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + SalesHistorySelectDTO findSalesHistoryDetail(@Param("salesHistorySelectDTO") SalesHistorySelectDTO salesHistorySelectDTO); + + int findSalesHistoryCount(); + + SalesHistoryStatisticsDTO findStatisticsByEmployee(@Param("salesHistorySelectDTO") SalesHistorySelectDTO salesHistorySelectDTO); + + SalesHistoryStatisticsDTO findStatisticsSearchByEmployee(@Param("salesHistorySearchDTO") SalesHistorySearchDTO salesHistorySearchDTO); + + List findStatisticsSearchByEmployeeDaily(@Param("salesHistorySearchDTO") SalesHistorySearchDTO salesHistorySearchDTO); + + List findStatisticsSearchMonthByEmployee(@Param("salesHistorySearchDTO") SalesHistorySearchDTO salesHistorySearchDTO); + + List findStatisticsSearchYearByEmployee(@Param("salesHistorySearchDTO") SalesHistorySearchDTO salesHistorySearchDTO); + + List findAllRank(@Param("salesHistoryRankedDataDTO")SalesHistoryRankedDataDTO salesHistoryRankedDataDTO + , @Param("size") int size + , @Param("offset") int offset); + + int findRankCount(); + + List findStatisticsBySearch(@Param("size") int size + , @Param("offset") int offset, + @Param("salesHistoryRankedDataDTO") SalesHistoryRankedDataDTO salesHistoryRankedDataDTO); + + int findStatisticsBySearchCount(@Param("salesHistoryRankedDataDTO") SalesHistoryRankedDataDTO salesHistoryRankedDataDTO); + + int findStatisticsBySearchCountMonth(@Param("salesHistoryRankedDataDTO") SalesHistoryRankedDataDTO salesHistoryRankedDataDTO); + + List findSalesHistorySearchByEmployee(@Param("size") int size + , @Param("offset") int offset, + @Param("salesHistorySearchDTO") SalesHistorySearchDTO salesHistorySearchDTO, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + int findSalesHistorySearchCountByEmployee(@Param("salesHistorySearchDTO") SalesHistorySearchDTO salesHistorySearchDTO); + + List findSalesHistoryBySearch(@Param("size") int size + , @Param("offset") int offset, + @Param("salesHistorySearchDTO") SalesHistorySearchDTO salesHistorySearchDTO, + @Param("sortField") String sortField, + @Param("sortOrder") String sortOrder); + + int findSalesHistoryCountBySearch(@Param("salesHistorySearchDTO") SalesHistorySearchDTO salesHistorySearchDTO); + + List findStatisticsAverageBySearch(@Param("size") int size + , @Param("offset") int offset + ,@Param("salesHistoryRankedDataDTO") SalesHistoryRankedDataDTO salesHistoryRankedDataDTO); + + List findSalesHistoryForExcel(); + + String findSalesHistoryIdByContractId(@Param("contractId") String contractId); + + List findStatisticsBestBySearch(@Param("size") int size + , @Param("offset") int offset, + @Param("salesHistoryRankedDataDTO") SalesHistoryRankedDataDTO salesHistoryRankedDataDTO); + + List findAllStatisticsBySearch(int size, int offset, SalesHistoryRankedDataDTO salesHistoryRankedDataDTO); +} + + diff --git a/src/main/java/stanl_2/final_backend/domain/sales_history/query/service/SalesHistoryQueryService.java b/src/main/java/stanl_2/final_backend/domain/sales_history/query/service/SalesHistoryQueryService.java new file mode 100644 index 00000000..1c3bb912 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/sales_history/query/service/SalesHistoryQueryService.java @@ -0,0 +1,49 @@ +package stanl_2.final_backend.domain.sales_history.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.sales_history.query.dto.*; + +import java.security.GeneralSecurityException; +import java.util.List; + +public interface SalesHistoryQueryService { + Page selectAllSalesHistoryByEmployee(SalesHistorySelectDTO salesHistorySelectDTO, Pageable pageable); + + SalesHistorySelectDTO selectSalesHistoryDetail(SalesHistorySelectDTO salesHistorySelectDTO); + + Page selectAllSalesHistory(Pageable pageable); + + SalesHistoryStatisticsDTO selectStatisticsByEmployee(SalesHistorySelectDTO salesHistorySelectDTO); + + SalesHistoryStatisticsDTO selectStatisticsSearchByEmployee(SalesHistorySearchDTO salesHistorySearchDTO); + + List selectStatisticsSearchByEmployeeDaily(SalesHistorySearchDTO salesHistorySearchDTO); + + List selectStatisticsSearchMonthByEmployee(SalesHistorySearchDTO salesHistorySearchDTO); + + List selectStatisticsSearchYearByEmployee(SalesHistorySearchDTO salesHistorySearchDTO); + + Page selectStatistics(SalesHistoryRankedDataDTO salesHistoryRankedDataDTO, Pageable pageable); + + Page selectStatisticsBySearch(SalesHistoryRankedDataDTO salesHistoryRankedDataDTO, Pageable pageable); + + Page selectSalesHistorySearchByEmployee(SalesHistorySearchDTO salesHistorySearchDTO, Pageable pageable); + + Page selectSalesHistoryBySearch(SalesHistorySearchDTO salesHistorySearchDTO, Pageable pageable) throws GeneralSecurityException; + + Page selectStatisticsAverageBySearch(SalesHistoryRankedDataDTO salesHistoryRankedDataDTO, Pageable pageable); + + void exportSalesHistoryToExcel(HttpServletResponse response); + + String selectSalesHistoryIdByContractId(String contractId); + + Page selectStatisticsBestBySearch(SalesHistoryRankedDataDTO salesHistoryRankedDataDTO, Pageable pageable); + + @Transactional + Page selectAllStatstics(SalesHistoryRankedDataDTO salesHistoryRankedDataDTO, Pageable pageable); + + Page selectMyStatisticsBySearch(SalesHistoryRankedDataDTO salesHistoryRankedDataDTO, Pageable pageable); +} diff --git a/src/main/java/stanl_2/final_backend/domain/sales_history/query/service/SalesHistoryQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/sales_history/query/service/SalesHistoryQueryServiceImpl.java new file mode 100644 index 00000000..58a585a9 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/sales_history/query/service/SalesHistoryQueryServiceImpl.java @@ -0,0 +1,530 @@ +package stanl_2.final_backend.domain.sales_history.query.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.center.query.service.CenterQueryService; +import stanl_2.final_backend.domain.customer.query.service.CustomerQueryService; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.domain.sales_history.common.exception.SalesHistoryCommonException; +import stanl_2.final_backend.domain.sales_history.common.exception.SalesHistoryErrorCode; +import stanl_2.final_backend.domain.sales_history.query.dto.*; +import stanl_2.final_backend.domain.sales_history.query.repository.SalesHistoryMapper; +import org.springframework.data.redis.core.RedisTemplate; +import stanl_2.final_backend.global.excel.ExcelUtilsV1; + +import java.security.GeneralSecurityException; +import java.time.Duration; +import java.util.List; + +@Service +public class SalesHistoryQueryServiceImpl implements SalesHistoryQueryService { + + private final SalesHistoryMapper salesHistoryMapper; + private final MemberQueryService memberQueryService; + private final AuthQueryService authQueryService; + private final RedisTemplate redisTemplate; + private final CenterQueryService centerQueryService; + private final CustomerQueryService customerQueryService; + private final ExcelUtilsV1 excelUtilsV1; + + @Autowired + public SalesHistoryQueryServiceImpl(SalesHistoryMapper salesHistoryMapper, MemberQueryService memberQueryService, AuthQueryService authQueryService, RedisTemplate redisTemplate, CenterQueryService centerQueryService, CustomerQueryService customerQueryService, ExcelUtilsV1 excelUtilsV1) { + this.salesHistoryMapper = salesHistoryMapper; + this.memberQueryService = memberQueryService; + this.authQueryService = authQueryService; + this.redisTemplate = redisTemplate; + this.centerQueryService = centerQueryService; + this.customerQueryService = customerQueryService; + this.excelUtilsV1 = excelUtilsV1; + } + + @Override + @Transactional(readOnly = true) + public Page selectAllSalesHistoryByEmployee(SalesHistorySelectDTO salesHistorySelectDTO, Pageable pageable) { + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + String searcherId = authQueryService.selectMemberIdByLoginId(salesHistorySelectDTO.getSearcherName()); + + List salesHistoryList = salesHistoryMapper.findSalesHistoryByEmployee(size,offset, searcherId, sortField, sortOrder); + + int total = salesHistoryMapper.findSalesHistoryCountByEmployee(searcherId); + + if(salesHistoryList.isEmpty() || total == 0){ + throw new SalesHistoryCommonException(SalesHistoryErrorCode.SALES_HISTORY_NOT_FOUND); + } + + salesHistoryList.forEach(salesHistory -> { + try { + salesHistory.setMemberId(memberQueryService.selectNameById(salesHistory.getMemberId())); + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.MEMBER_NOT_FOUND); + } + try { + salesHistory.setCustomerId(customerQueryService.selectCustomerNameById(salesHistory.getCustomerId())); + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.CUSTOMER_NOT_FOUND); + } + try { + salesHistory.setCenterId(centerQueryService.selectNameById(salesHistory.getCenterId())); + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.CENTER_NOT_FOUND); + } + }); + + return new PageImpl<>(salesHistoryList, pageable, total); + } + + + @Override + @Transactional(readOnly = true) + public SalesHistorySelectDTO selectSalesHistoryDetail(SalesHistorySelectDTO salesHistorySelectDTO) { + + SalesHistorySelectDTO salesHistoryDetailDTO = salesHistoryMapper.findSalesHistoryDetail(salesHistorySelectDTO); + + if(salesHistoryDetailDTO == null){ + throw new SalesHistoryCommonException(SalesHistoryErrorCode.SALES_HISTORY_NOT_FOUND); + } + + return salesHistoryDetailDTO; + } + + @Override + @Transactional(readOnly = true) + public Page selectSalesHistorySearchByEmployee(SalesHistorySearchDTO salesHistorySearchDTO, Pageable pageable) { + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + salesHistorySearchDTO.setSearcherName(authQueryService.selectMemberIdByLoginId(salesHistorySearchDTO.getSearcherName())); + + List salesHistoryList = salesHistoryMapper.findSalesHistorySearchByEmployee(size,offset, salesHistorySearchDTO, sortField, sortOrder); + + int total = salesHistoryMapper.findSalesHistorySearchCountByEmployee(salesHistorySearchDTO); + + if(salesHistoryList.isEmpty() || total == 0){ + throw new SalesHistoryCommonException(SalesHistoryErrorCode.SALES_HISTORY_NOT_FOUND); + } + + salesHistoryList.forEach(salesHistory -> { + try { + salesHistory.setMemberId(memberQueryService.selectNameById(salesHistory.getMemberId())); + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.MEMBER_NOT_FOUND); + } + try { + salesHistory.setCustomerId(customerQueryService.selectCustomerNameById(salesHistory.getCustomerId())); + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.CUSTOMER_NOT_FOUND); + } + try { + salesHistory.setCenterId(centerQueryService.selectNameById(salesHistory.getCenterId())); + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.CENTER_NOT_FOUND); + } + }); + + return new PageImpl<>(salesHistoryList, pageable, total); + } + + @Override + public Page selectSalesHistoryBySearch(SalesHistorySearchDTO salesHistorySearchDTO, Pageable pageable) throws GeneralSecurityException { + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + if(salesHistorySearchDTO.getCustomerName() != null){ + salesHistorySearchDTO.setCustomerList(customerQueryService.selectCustomerId(salesHistorySearchDTO.getCustomerName())); + } + + List salesHistoryList = salesHistoryMapper.findSalesHistoryBySearch(size,offset, salesHistorySearchDTO, sortField, sortOrder); + + int total = salesHistoryMapper.findSalesHistoryCountBySearch(salesHistorySearchDTO); + + if(salesHistoryList.isEmpty() || total == 0){ + throw new SalesHistoryCommonException(SalesHistoryErrorCode.SALES_HISTORY_NOT_FOUND); + } + + salesHistoryList.forEach(salesHistory -> { + try { + salesHistory.setMemberId(memberQueryService.selectNameById(salesHistory.getMemberId())); + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.MEMBER_NOT_FOUND); + } + try { + salesHistory.setCustomerId(customerQueryService.selectCustomerNameById(salesHistory.getCustomerId())); + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.CUSTOMER_NOT_FOUND); + } + try { + salesHistory.setCenterId(centerQueryService.selectNameById(salesHistory.getCenterId())); + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.CENTER_NOT_FOUND); + } + }); + + return new PageImpl<>(salesHistoryList, pageable, total); + } + + @Override + @Transactional(readOnly = true) + public Page selectAllSalesHistory(Pageable pageable) { + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + + Sort sort = pageable.getSort(); + String sortField = null; + String sortOrder = null; + if (sort.isSorted()) { + sortField = sort.iterator().next().getProperty(); + sortOrder = sort.iterator().next().isAscending() ? "ASC" : "DESC"; + } + + List salesHistoryList = salesHistoryMapper.findAllSalesHistory(size,offset, sortField, sortOrder); + + int total = salesHistoryMapper.findSalesHistoryCount(); + + if(salesHistoryList.isEmpty() || total == 0){ + throw new SalesHistoryCommonException(SalesHistoryErrorCode.SALES_HISTORY_NOT_FOUND); + } + + salesHistoryList.forEach(salesHistory -> { + try { + salesHistory.setMemberId(memberQueryService.selectNameById(salesHistory.getMemberId())); + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.MEMBER_NOT_FOUND); + } + try { + salesHistory.setCustomerId(customerQueryService.selectCustomerNameById(salesHistory.getCustomerId())); + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.CUSTOMER_NOT_FOUND); + } + try { + salesHistory.setCenterId(centerQueryService.selectNameById(salesHistory.getCenterId())); + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.CENTER_NOT_FOUND); + } + }); + + return new PageImpl<>(salesHistoryList, pageable, total); + } + + @Override + @Transactional(readOnly = true) + public SalesHistoryStatisticsDTO selectStatisticsByEmployee(SalesHistorySelectDTO salesHistorySelectDTO) { + + salesHistorySelectDTO.setSearcherName(authQueryService.selectMemberIdByLoginId(salesHistorySelectDTO.getSearcherName())); + + String cacheKey = "salesHistory::statistics::searcherId=" + salesHistorySelectDTO.getSearcherName(); + + SalesHistoryStatisticsDTO salesHistoryStatisticsDTO = (SalesHistoryStatisticsDTO) redisTemplate.opsForValue().get(cacheKey); + + if (salesHistoryStatisticsDTO == null) { + // 캐시에 데이터가 없을 경우 DB에서 조회 + salesHistoryStatisticsDTO = salesHistoryMapper.findStatisticsByEmployee(salesHistorySelectDTO); + + if(salesHistoryStatisticsDTO == null){ + throw new SalesHistoryCommonException(SalesHistoryErrorCode.SALES_HISTORY_NOT_FOUND); + } + + // Redis에 데이터 저장 /만료 시간 설정 + if (salesHistoryStatisticsDTO != null) { + redisTemplate.opsForValue().set(cacheKey, salesHistoryStatisticsDTO, Duration.ofMinutes(10)); // 캐시 10분 유지 + } + } + return salesHistoryStatisticsDTO; + } + + @Override + @Transactional(readOnly = true) + public SalesHistoryStatisticsDTO selectStatisticsSearchByEmployee(SalesHistorySearchDTO salesHistorySearchDTO) { + + salesHistorySearchDTO.setSearcherName(authQueryService.selectMemberIdByLoginId(salesHistorySearchDTO.getSearcherName())); + + SalesHistoryStatisticsDTO salesHistoryStatisticsDTO = salesHistoryMapper.findStatisticsSearchByEmployee(salesHistorySearchDTO); + + if(salesHistoryStatisticsDTO == null){ + throw new SalesHistoryCommonException(SalesHistoryErrorCode.SALES_HISTORY_NOT_FOUND); + } + return salesHistoryStatisticsDTO; + } + + @Override + @Transactional(readOnly = true) + public List selectStatisticsSearchByEmployeeDaily(SalesHistorySearchDTO salesHistorySearchDTO) { + + salesHistorySearchDTO.setSearcherName(authQueryService.selectMemberIdByLoginId(salesHistorySearchDTO.getSearcherName())); + + List salesHistoryStatisticsDTOList = salesHistoryMapper.findStatisticsSearchByEmployeeDaily(salesHistorySearchDTO); + + if(salesHistoryStatisticsDTOList == null || salesHistoryStatisticsDTOList.isEmpty()){ + throw new SalesHistoryCommonException(SalesHistoryErrorCode.SALES_HISTORY_NOT_FOUND); + } + return salesHistoryStatisticsDTOList; + } + + + @Override + @Transactional(readOnly = true) + public List selectStatisticsSearchMonthByEmployee(SalesHistorySearchDTO salesHistorySearchDTO) { + salesHistorySearchDTO.setSearcherName(authQueryService.selectMemberIdByLoginId(salesHistorySearchDTO.getSearcherName())); + + + String parseStartDate = salesHistorySearchDTO.getStartDate(); + String parseEndDate = salesHistorySearchDTO.getEndDate(); + + salesHistorySearchDTO.setStartDate(parseStartDate.substring(0,7)); + salesHistorySearchDTO.setEndDate(parseEndDate.substring(0,7)); + + List salesHistoryStatisticsDTOList = salesHistoryMapper.findStatisticsSearchMonthByEmployee(salesHistorySearchDTO); + + if(salesHistoryStatisticsDTOList == null || salesHistoryStatisticsDTOList.isEmpty()){ + throw new SalesHistoryCommonException(SalesHistoryErrorCode.SALES_HISTORY_NOT_FOUND); + } + return salesHistoryStatisticsDTOList; + } + + @Override + @Transactional(readOnly = true) + public List selectStatisticsSearchYearByEmployee(SalesHistorySearchDTO salesHistorySearchDTO) { + salesHistorySearchDTO.setSearcherName(authQueryService.selectMemberIdByLoginId(salesHistorySearchDTO.getSearcherName())); + + String parseStartDate = salesHistorySearchDTO.getStartDate(); + String parseEndDate = salesHistorySearchDTO.getEndDate(); + + parseStartDate = parseStartDate.substring(0,4); + parseEndDate = parseEndDate.substring(0,4); + + salesHistorySearchDTO.setStartDate(parseStartDate); + salesHistorySearchDTO.setEndDate(parseEndDate); + + List salesHistoryStatisticsDTOList = salesHistoryMapper.findStatisticsSearchYearByEmployee(salesHistorySearchDTO); + + if(salesHistoryStatisticsDTOList == null){ + throw new SalesHistoryCommonException(SalesHistoryErrorCode.SALES_HISTORY_NOT_FOUND); + } + return salesHistoryStatisticsDTOList; + } + + @Override + @Transactional(readOnly = true) + public Page selectStatistics(SalesHistoryRankedDataDTO salesHistoryRankedDataDTO, Pageable pageable) { + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + + List salesHistoryList = salesHistoryMapper.findAllRank(salesHistoryRankedDataDTO, size,offset); + + int total = salesHistoryMapper.findRankCount(); + + if(salesHistoryList.isEmpty() || total == 0){ + throw new SalesHistoryCommonException(SalesHistoryErrorCode.SALES_HISTORY_NOT_FOUND); + } + + salesHistoryList.forEach(salesHistory -> { + try { + salesHistory.setMemberId(memberQueryService.selectNameById(salesHistory.getMemberId())); + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.MEMBER_NOT_FOUND); + } + }); + + return new PageImpl<>(salesHistoryList, pageable, total); + } + + @Override + @Transactional(readOnly = true) + public Page selectStatisticsAverageBySearch(SalesHistoryRankedDataDTO salesHistoryRankedDataDTO, Pageable pageable) { + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + + List salesHistoryStatisticsAverageList = salesHistoryMapper.findStatisticsAverageBySearch(size,offset,salesHistoryRankedDataDTO); + + int total = salesHistoryMapper.findStatisticsBySearchCount(salesHistoryRankedDataDTO); + + if(salesHistoryStatisticsAverageList == null){ + throw new SalesHistoryCommonException(SalesHistoryErrorCode.SALES_HISTORY_NOT_FOUND); + } + + return new PageImpl<>(salesHistoryStatisticsAverageList, pageable, total); + } + + @Override + @Transactional(readOnly = true) + public Page selectStatisticsBySearch(SalesHistoryRankedDataDTO salesHistoryRankedDataDTO, Pageable pageable) { + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + + List salesHistoryList = salesHistoryMapper.findStatisticsBySearch(size,offset, salesHistoryRankedDataDTO); + + int total = salesHistoryMapper.findStatisticsBySearchCount(salesHistoryRankedDataDTO); + + if(salesHistoryList.isEmpty() || total == 0){ + throw new SalesHistoryCommonException(SalesHistoryErrorCode.SALES_HISTORY_NOT_FOUND); + } + + salesHistoryList.forEach(salesHistory -> { + try { + if(salesHistory.getMemberId() != null) { + salesHistory.setMemberId(memberQueryService.selectNameById(salesHistory.getMemberId())); + } + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.MEMBER_NOT_FOUND); + } + try { + if(salesHistory.getCenterId() != null) { + salesHistory.setCenterId(centerQueryService.selectNameById(salesHistory.getCenterId())); + } + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.CENTER_NOT_FOUND); + } + }); + + return new PageImpl<>(salesHistoryList, pageable, total); + } + + @Override + @Transactional + public void exportSalesHistoryToExcel(HttpServletResponse response) { + List salesHistoryList = salesHistoryMapper.findSalesHistoryForExcel(); + + if(salesHistoryList == null) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.SALES_HISTORY_NOT_FOUND); + } + + salesHistoryList.forEach(salesHistory -> { + try { + salesHistory.setMemberId(memberQueryService.selectNameById(salesHistory.getMemberId())); + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.MEMBER_NOT_FOUND); + } + try { + salesHistory.setCustomerId(customerQueryService.selectCustomerNameById(salesHistory.getCustomerId())); + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.CUSTOMER_NOT_FOUND); + } + try { + salesHistory.setCenterId(centerQueryService.selectNameById(salesHistory.getCenterId())); + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.CENTER_NOT_FOUND); + } + }); + + excelUtilsV1.download(SalesHistoryExcelDownload.class, salesHistoryList, "SalesHistoryExcel", response); + + } + + @Override + @Transactional + public String selectSalesHistoryIdByContractId(String contractId) { + + String salesHistoryId = salesHistoryMapper.findSalesHistoryIdByContractId(contractId); + + if(salesHistoryId == null){ + throw new SalesHistoryCommonException(SalesHistoryErrorCode.SALES_HISTORY_NOT_FOUND); + } + + return salesHistoryId; + } + + @Override + @Transactional + public Page selectStatisticsBestBySearch(SalesHistoryRankedDataDTO salesHistoryRankedDataDTO, Pageable pageable) { + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + + List salesHistoryList = salesHistoryMapper.findStatisticsBestBySearch(size,offset, salesHistoryRankedDataDTO); + + int total = salesHistoryMapper.findStatisticsBySearchCount(salesHistoryRankedDataDTO); + + if(salesHistoryList.isEmpty() || total == 0){ + throw new SalesHistoryCommonException(SalesHistoryErrorCode.SALES_HISTORY_NOT_FOUND); + } + + return new PageImpl<>(salesHistoryList, pageable, total); + } + + @Override + @Transactional + public Page selectAllStatstics(SalesHistoryRankedDataDTO salesHistoryRankedDataDTO, Pageable pageable){ + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + + List allStatistics= salesHistoryMapper.findAllStatisticsBySearch(size, offset, salesHistoryRankedDataDTO); + + + + if(allStatistics.isEmpty()){ + throw new SalesHistoryCommonException(SalesHistoryErrorCode.SALES_HISTORY_NOT_FOUND); + } + + return new PageImpl<>(allStatistics, pageable, 0); + } + + @Override + public Page selectMyStatisticsBySearch(SalesHistoryRankedDataDTO salesHistoryRankedDataDTO, Pageable pageable) { + int offset = Math.toIntExact(pageable.getOffset()); + int size = pageable.getPageSize(); + + List memberList = new java.util.ArrayList<>(List.of()); + + memberList.add(authQueryService.selectMemberIdByLoginId(salesHistoryRankedDataDTO.getMemberId())); + + salesHistoryRankedDataDTO.setMemberList(memberList); + + List salesHistoryList = salesHistoryMapper.findStatisticsBySearch(size,offset, salesHistoryRankedDataDTO); + + int total = salesHistoryMapper.findStatisticsBySearchCount(salesHistoryRankedDataDTO); + + if(salesHistoryList.isEmpty() || total == 0){ + throw new SalesHistoryCommonException(SalesHistoryErrorCode.SALES_HISTORY_NOT_FOUND); + } + + salesHistoryList.forEach(salesHistory -> { + try { + if(salesHistory.getMemberId() != null) { + salesHistory.setMemberId(memberQueryService.selectNameById(salesHistory.getMemberId())); + } + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.MEMBER_NOT_FOUND); + } + try { + if(salesHistory.getCenterId() != null) { + salesHistory.setCenterId(centerQueryService.selectNameById(salesHistory.getCenterId())); + } + } catch (Exception e) { + throw new SalesHistoryCommonException(SalesHistoryErrorCode.CENTER_NOT_FOUND); + } + }); + + return new PageImpl<>(salesHistoryList, pageable, total); + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/command/application/controller/ScheduleController.java b/src/main/java/stanl_2/final_backend/domain/schedule/command/application/controller/ScheduleController.java index efd9838e..d1481226 100644 --- a/src/main/java/stanl_2/final_backend/domain/schedule/command/application/controller/ScheduleController.java +++ b/src/main/java/stanl_2/final_backend/domain/schedule/command/application/controller/ScheduleController.java @@ -1,14 +1,21 @@ package stanl_2.final_backend.domain.schedule.command.application.controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import stanl_2.final_backend.domain.schedule.command.application.dto.request.ScheduleModifyRequestDTO; -import stanl_2.final_backend.domain.schedule.command.application.dto.request.ScheduleRegistRequestDTO; -import stanl_2.final_backend.domain.schedule.command.application.dto.response.ScheduleModifyResponseDTO; -import stanl_2.final_backend.domain.schedule.command.application.dto.response.ScheduleRegistResponseDTO; +//import stanl_2.final_backend.domain.alarm.service.AlarmService; +import stanl_2.final_backend.domain.schedule.command.application.dto.ScheduleDeleteDTO; +import stanl_2.final_backend.domain.schedule.command.application.dto.ScheduleModifyDTO; +import stanl_2.final_backend.domain.schedule.command.application.dto.ScheduleRegistDTO; import stanl_2.final_backend.domain.schedule.command.application.service.ScheduleCommandService; -import stanl_2.final_backend.domain.schedule.common.response.ResponseMessage; +import stanl_2.final_backend.domain.schedule.common.response.ScheduleResponseMessage; + +import java.security.Principal; @RestController("commandScheduleController") @RequestMapping("/api/v1/schedule") @@ -17,31 +24,76 @@ public class ScheduleController { private final ScheduleCommandService scheduleCommandService; @Autowired - public ScheduleController(ScheduleCommandService scheduleCommandService) { + public ScheduleController(ScheduleCommandService scheduleCommandService + ) { this.scheduleCommandService = scheduleCommandService; } + @Operation(summary = "일정 등록 api") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "일정 등록 성공", + content = {@Content(schema = @Schema(implementation = ScheduleResponseMessage.class))}) + }) @PostMapping("") - public ResponseEntity registSchedule(@RequestBody ScheduleRegistRequestDTO scheduleRegistRequestDTO){ + public ResponseEntity registSchedule(Principal principal, + @RequestBody ScheduleRegistDTO scheduleRegistDTO){ + + String memberLoginId = principal.getName(); + scheduleRegistDTO.setMemberLoginId(memberLoginId); - ScheduleRegistResponseDTO scheduleRegistResponseDTO = scheduleCommandService.registSchedule(scheduleRegistRequestDTO); + Boolean answer = scheduleCommandService.registSchedule(scheduleRegistDTO); - return ResponseEntity.ok(new ResponseMessage(200,"성공",scheduleRegistResponseDTO)); + return ResponseEntity.ok(ScheduleResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(answer) + .build()); } - @PutMapping("") - public ResponseEntity modifySchedule(@RequestBody ScheduleModifyRequestDTO scheduleModifyRequestDTO){ - ScheduleModifyResponseDTO scheduleModifyResponseDTO = scheduleCommandService.modifySchedule(scheduleModifyRequestDTO); + @Operation(summary = "일정 수정 api") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "일정 수정 성공", + content = {@Content(schema = @Schema(implementation = ScheduleResponseMessage.class))}) + }) + @PutMapping("{scheduleId}") + public ResponseEntity modifySchedule(Principal principal, + @PathVariable String scheduleId, + @RequestBody ScheduleModifyDTO scheduleModifyDTO){ + + String memberLoginId = principal.getName(); + scheduleModifyDTO.setMemberLoginId(memberLoginId); + scheduleModifyDTO.setScheduleId(scheduleId); - return ResponseEntity.ok(new ResponseMessage(200,"성공",scheduleModifyResponseDTO)); + Boolean answer = scheduleCommandService.modifySchedule(scheduleModifyDTO); + + return ResponseEntity.ok(ScheduleResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(answer) + .build()); } - @DeleteMapping("{id}") - public ResponseEntity deleteSchedule(@PathVariable String id){ + @Operation(summary = "일정 삭제 api") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "일정 삭제 성공", + content = {@Content(schema = @Schema(implementation = ScheduleResponseMessage.class))}) + }) + @DeleteMapping("{scheduleId}") + public ResponseEntity deleteSchedule(Principal principal, + @PathVariable String scheduleId){ + + String memberLoginId = principal.getName(); + ScheduleDeleteDTO scheduleDeleteDTO = new ScheduleDeleteDTO(); + scheduleDeleteDTO.setMemberLoginId(memberLoginId); + scheduleDeleteDTO.setScheduleId(scheduleId); - Boolean active = scheduleCommandService.deleteSchedule(id); + Boolean answer = scheduleCommandService.deleteSchedule(scheduleDeleteDTO); - return ResponseEntity.ok(new ResponseMessage(200,"성공",active)); + return ResponseEntity.ok(ScheduleResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(answer) + .build()); } } diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/command/application/dto/ScheduleDeleteDTO.java b/src/main/java/stanl_2/final_backend/domain/schedule/command/application/dto/ScheduleDeleteDTO.java new file mode 100644 index 00000000..ed4f6a6b --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/schedule/command/application/dto/ScheduleDeleteDTO.java @@ -0,0 +1,16 @@ +package stanl_2.final_backend.domain.schedule.command.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class ScheduleDeleteDTO { + + private String scheduleId; + private String memberLoginId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/command/application/dto/request/ScheduleRegistRequestDTO.java b/src/main/java/stanl_2/final_backend/domain/schedule/command/application/dto/ScheduleModifyDTO.java similarity index 63% rename from src/main/java/stanl_2/final_backend/domain/schedule/command/application/dto/request/ScheduleRegistRequestDTO.java rename to src/main/java/stanl_2/final_backend/domain/schedule/command/application/dto/ScheduleModifyDTO.java index 8abc5e95..6d040a03 100644 --- a/src/main/java/stanl_2/final_backend/domain/schedule/command/application/dto/request/ScheduleRegistRequestDTO.java +++ b/src/main/java/stanl_2/final_backend/domain/schedule/command/application/dto/ScheduleModifyDTO.java @@ -1,4 +1,4 @@ -package stanl_2.final_backend.domain.schedule.command.application.dto.request; +package stanl_2.final_backend.domain.schedule.command.application.dto; import lombok.AllArgsConstructor; import lombok.Getter; @@ -9,10 +9,14 @@ @NoArgsConstructor @Getter @Setter -public class ScheduleRegistRequestDTO { +public class ScheduleModifyDTO { + private String scheduleId; private String name; private String content; - private String reservationTime; + private String tag; + private String startAt; + private String endAt; + private String memberLoginId; private String memberId; } diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/command/application/dto/request/ScheduleModifyRequestDTO.java b/src/main/java/stanl_2/final_backend/domain/schedule/command/application/dto/ScheduleRegistDTO.java similarity index 67% rename from src/main/java/stanl_2/final_backend/domain/schedule/command/application/dto/request/ScheduleModifyRequestDTO.java rename to src/main/java/stanl_2/final_backend/domain/schedule/command/application/dto/ScheduleRegistDTO.java index 5c2d98fb..064a99de 100644 --- a/src/main/java/stanl_2/final_backend/domain/schedule/command/application/dto/request/ScheduleModifyRequestDTO.java +++ b/src/main/java/stanl_2/final_backend/domain/schedule/command/application/dto/ScheduleRegistDTO.java @@ -1,4 +1,4 @@ -package stanl_2.final_backend.domain.schedule.command.application.dto.request; +package stanl_2.final_backend.domain.schedule.command.application.dto; import lombok.AllArgsConstructor; import lombok.Getter; @@ -9,11 +9,13 @@ @NoArgsConstructor @Getter @Setter -public class ScheduleModifyRequestDTO { +public class ScheduleRegistDTO { - private String id; private String name; private String content; - private String reservationTime; + private String tag; + private String startAt; + private String endAt; + private String memberLoginId; private String memberId; } diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/command/application/dto/response/ScheduleModifyResponseDTO.java b/src/main/java/stanl_2/final_backend/domain/schedule/command/application/dto/response/ScheduleModifyResponseDTO.java deleted file mode 100644 index a59acea5..00000000 --- a/src/main/java/stanl_2/final_backend/domain/schedule/command/application/dto/response/ScheduleModifyResponseDTO.java +++ /dev/null @@ -1,21 +0,0 @@ -package stanl_2.final_backend.domain.schedule.command.application.dto.response; - -import lombok.*; - -@AllArgsConstructor -@NoArgsConstructor -@Getter -@Setter -@ToString -public class ScheduleModifyResponseDTO { - - private String id; - private String name; - private String content; - private String reservationTime; - private String createdAt; - private String updatedAt; - private String deletedAt; - private Boolean active; - private String memberId; -} diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/command/application/dto/response/ScheduleRegistResponseDTO.java b/src/main/java/stanl_2/final_backend/domain/schedule/command/application/dto/response/ScheduleRegistResponseDTO.java deleted file mode 100644 index 80ec5a98..00000000 --- a/src/main/java/stanl_2/final_backend/domain/schedule/command/application/dto/response/ScheduleRegistResponseDTO.java +++ /dev/null @@ -1,21 +0,0 @@ -package stanl_2.final_backend.domain.schedule.command.application.dto.response; - -import lombok.*; - -@AllArgsConstructor -@NoArgsConstructor -@Getter -@Setter -@ToString -public class ScheduleRegistResponseDTO { - - private String id; - private String name; - private String content; - private String reservationTime; - private String createdAt; - private String updatedAt; - private String deletedAt; - private Boolean active; - private String memberId; -} diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/command/application/service/ScheduleCommandService.java b/src/main/java/stanl_2/final_backend/domain/schedule/command/application/service/ScheduleCommandService.java index a0cca27c..7ee44685 100644 --- a/src/main/java/stanl_2/final_backend/domain/schedule/command/application/service/ScheduleCommandService.java +++ b/src/main/java/stanl_2/final_backend/domain/schedule/command/application/service/ScheduleCommandService.java @@ -1,15 +1,14 @@ package stanl_2.final_backend.domain.schedule.command.application.service; -import stanl_2.final_backend.domain.schedule.command.application.dto.request.ScheduleModifyRequestDTO; -import stanl_2.final_backend.domain.schedule.command.application.dto.request.ScheduleRegistRequestDTO; -import stanl_2.final_backend.domain.schedule.command.application.dto.response.ScheduleModifyResponseDTO; -import stanl_2.final_backend.domain.schedule.command.application.dto.response.ScheduleRegistResponseDTO; +import stanl_2.final_backend.domain.schedule.command.application.dto.ScheduleDeleteDTO; +import stanl_2.final_backend.domain.schedule.command.application.dto.ScheduleModifyDTO; +import stanl_2.final_backend.domain.schedule.command.application.dto.ScheduleRegistDTO; public interface ScheduleCommandService { - ScheduleRegistResponseDTO registSchedule(ScheduleRegistRequestDTO scheduleRegistRequestDTO); + Boolean registSchedule(ScheduleRegistDTO scheduleRegistDTO); - ScheduleModifyResponseDTO modifySchedule(ScheduleModifyRequestDTO scheduleModifyRequestDTO); + Boolean modifySchedule(ScheduleModifyDTO scheduleModifyDTO); - Boolean deleteSchedule(String scheduleId); + Boolean deleteSchedule(ScheduleDeleteDTO scheduleDeleteDTO); } \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/command/domain/aggregate/entity/Schedule.java b/src/main/java/stanl_2/final_backend/domain/schedule/command/domain/aggregate/entity/Schedule.java index 2afc42c2..02815597 100644 --- a/src/main/java/stanl_2/final_backend/domain/schedule/command/domain/aggregate/entity/Schedule.java +++ b/src/main/java/stanl_2/final_backend/domain/schedule/command/domain/aggregate/entity/Schedule.java @@ -10,9 +10,10 @@ import java.sql.Timestamp; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; @Entity -@Table(name="SCHEDULE") +@Table(name="TB_SCHEDULE") @AllArgsConstructor @NoArgsConstructor @Getter @@ -27,7 +28,7 @@ public class Schedule { parameters = @Parameter(name = "prefix", value = "SCH") ) @Column(name = "SCH_ID", nullable = false) - private String id; + private String scheduleId; @Column(name = "SCH_NAME", nullable = false) private String name; @@ -35,17 +36,23 @@ public class Schedule { @Column(name = "SCH_CONT", nullable = false) private String content; - @Column(name = "SCH_RES", nullable = false) - private String reservationTime; + @Column(name = "SCH_TAG", nullable = false) + private String tag = "CONSULTATION"; + + @Column(name = "SCH_SRT_AT", nullable = false) + private String startAt; + + @Column(name = "SCH_END_AT", nullable = false) + private String endAt; @Column(name = "CREATED_AT", nullable = false) - private Timestamp createdAt; + private String createdAt; @Column(name = "UPDATED_AT", nullable = false) - private Timestamp updatedAt; + private String updatedAt; @Column(name = "DELETED_AT") - private Timestamp deletedAt; + private String deletedAt; @Column(name = "ACTIVE", nullable = false) private Boolean active = true; @@ -57,18 +64,18 @@ public class Schedule { // Insert 되기 전에 실행 @PrePersist public void prePersist() { - this.createdAt = getCurrentTimestamp(); + this.createdAt = getCurrentTime(); this.updatedAt = this.createdAt; } // Update 되기 전에 실행 @PreUpdate public void preUpdate() { - this.updatedAt = getCurrentTimestamp(); + this.updatedAt = getCurrentTime(); } - private Timestamp getCurrentTimestamp() { + private String getCurrentTime() { ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); - return Timestamp.from(nowKst.toInstant()); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } } diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/command/domain/repository/ScheduleRepository.java b/src/main/java/stanl_2/final_backend/domain/schedule/command/domain/repository/ScheduleRepository.java index a594f5c5..148cdbff 100644 --- a/src/main/java/stanl_2/final_backend/domain/schedule/command/domain/repository/ScheduleRepository.java +++ b/src/main/java/stanl_2/final_backend/domain/schedule/command/domain/repository/ScheduleRepository.java @@ -4,6 +4,10 @@ import org.springframework.stereotype.Repository; import stanl_2.final_backend.domain.schedule.command.domain.aggregate.entity.Schedule; +import java.util.Optional; + @Repository public interface ScheduleRepository extends JpaRepository { + + Optional findByScheduleId(String scheduleId); } diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/command/domain/service/ScheduleCommandServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/schedule/command/domain/service/ScheduleCommandServiceImpl.java index 58c381ce..1be09962 100644 --- a/src/main/java/stanl_2/final_backend/domain/schedule/command/domain/service/ScheduleCommandServiceImpl.java +++ b/src/main/java/stanl_2/final_backend/domain/schedule/command/domain/service/ScheduleCommandServiceImpl.java @@ -3,82 +3,129 @@ import lombok.extern.slf4j.Slf4j; import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.data.mapping.MappingException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import stanl_2.final_backend.domain.schedule.command.application.dto.request.ScheduleModifyRequestDTO; -import stanl_2.final_backend.domain.schedule.command.application.dto.request.ScheduleRegistRequestDTO; -import stanl_2.final_backend.domain.schedule.command.application.dto.response.ScheduleModifyResponseDTO; -import stanl_2.final_backend.domain.schedule.command.application.dto.response.ScheduleRegistResponseDTO; +import stanl_2.final_backend.domain.member.common.exception.MemberCommonException; +import stanl_2.final_backend.domain.member.common.exception.MemberErrorCode; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.schedule.command.application.dto.ScheduleDeleteDTO; +import stanl_2.final_backend.domain.schedule.command.application.dto.ScheduleModifyDTO; +import stanl_2.final_backend.domain.schedule.command.application.dto.ScheduleRegistDTO; import stanl_2.final_backend.domain.schedule.command.application.service.ScheduleCommandService; import stanl_2.final_backend.domain.schedule.command.domain.aggregate.entity.Schedule; import stanl_2.final_backend.domain.schedule.command.domain.repository.ScheduleRepository; -import stanl_2.final_backend.domain.schedule.common.exception.CommonException; -import stanl_2.final_backend.domain.schedule.common.exception.ErrorCode; +import stanl_2.final_backend.domain.schedule.common.exception.ScheduleCommonException; +import stanl_2.final_backend.domain.schedule.common.exception.ScheduleErrorCode; -import java.sql.Timestamp; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; @Slf4j -@Service("commandScheduleServiceImpl") +@Service public class ScheduleCommandServiceImpl implements ScheduleCommandService { private final ScheduleRepository scheduleRepository; + private final AuthQueryService authQueryService; private final ModelMapper modelMapper; @Autowired - public ScheduleCommandServiceImpl(ScheduleRepository scheduleRepository, ModelMapper modelMapper) { + public ScheduleCommandServiceImpl(ScheduleRepository scheduleRepository, ModelMapper modelMapper, + AuthQueryService authQueryService) { this.scheduleRepository = scheduleRepository; this.modelMapper = modelMapper; + this.authQueryService = authQueryService; } - private Timestamp getCurrentTimestamp() { + private String getCurrentTime() { ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); - return Timestamp.from(nowKst.toInstant()); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } @Override @Transactional - public ScheduleRegistResponseDTO registSchedule(ScheduleRegistRequestDTO scheduleRegistRequestDTO) { + public Boolean registSchedule(ScheduleRegistDTO scheduleRegistDTO) { - Schedule schedule = modelMapper.map(scheduleRegistRequestDTO,Schedule.class); + String memberId = authQueryService.selectMemberIdByLoginId(scheduleRegistDTO.getMemberLoginId()); + scheduleRegistDTO.setMemberId(memberId); - scheduleRepository.save(schedule); + try { + Schedule schedule = modelMapper.map(scheduleRegistDTO, Schedule.class); - ScheduleRegistResponseDTO scheduleRegistResponseDTO = modelMapper.map(schedule, ScheduleRegistResponseDTO.class); + scheduleRepository.save(schedule); - return scheduleRegistResponseDTO; + return true; + } catch (DataIntegrityViolationException e){ + // DB 무결정 제약 조건 (NOT NULL, UNIQUE) 위반 + throw new ScheduleCommonException(ScheduleErrorCode.DATA_INTEGRITY_VIOLATION); + } catch (Exception e) { + // 서버 오류 + throw new ScheduleCommonException(ScheduleErrorCode.INTERNAL_SERVER_ERROR); + } } @Override @Transactional - public ScheduleModifyResponseDTO modifySchedule(ScheduleModifyRequestDTO scheduleModifyRequestDTO) { + public Boolean modifySchedule(ScheduleModifyDTO scheduleModifyDTO) { - Schedule schedule = scheduleRepository.findById(scheduleModifyRequestDTO.getId()) - .orElseThrow(() -> new CommonException(ErrorCode.SCHEDULE_NOT_FOUND)); + String memberId = authQueryService.selectMemberIdByLoginId(scheduleModifyDTO.getMemberLoginId()); + scheduleModifyDTO.setMemberId(memberId); - Schedule updateSchedule = modelMapper.map(scheduleModifyRequestDTO, Schedule.class); - updateSchedule.setCreatedAt(schedule.getCreatedAt()); - updateSchedule.setActive(schedule.getActive()); + Schedule schedule = scheduleRepository.findByScheduleId(scheduleModifyDTO.getScheduleId()) + .orElseThrow(() -> new ScheduleCommonException(ScheduleErrorCode.SCHEDULE_NOT_FOUND)); - scheduleRepository.save(updateSchedule); + if(!scheduleModifyDTO.getMemberId().equals(schedule.getMemberId())){ + // 권한 오ㅋ + throw new ScheduleCommonException(ScheduleErrorCode.AUTHORIZATION_VIOLATION); + } - ScheduleModifyResponseDTO scheduleModifyResponseDTO = modelMapper.map(updateSchedule,ScheduleModifyResponseDTO.class); + try { + Schedule updateSchedule = modelMapper.map(scheduleModifyDTO, Schedule.class); - return scheduleModifyResponseDTO; - } + updateSchedule.setCreatedAt(schedule.getCreatedAt()); + updateSchedule.setActive(schedule.getActive()); + + scheduleRepository.save(updateSchedule); + + return true; + } catch (DataIntegrityViolationException e) { + // 데이터 무결성 위반 예외 처리 + throw new ScheduleCommonException(ScheduleErrorCode.DATA_INTEGRITY_VIOLATION); + } catch (Exception e) { + // 서버 오류 + throw new ScheduleCommonException(ScheduleErrorCode.INTERNAL_SERVER_ERROR); + } +} @Override - public Boolean deleteSchedule(String scheduleId) { + @Transactional + public Boolean deleteSchedule(ScheduleDeleteDTO scheduleDeleteDTO) { - Schedule schedule = scheduleRepository.findById(scheduleId) - .orElseThrow(() -> new CommonException(ErrorCode.SCHEDULE_NOT_FOUND)); + String memberId = authQueryService.selectMemberIdByLoginId(scheduleDeleteDTO.getMemberLoginId()); - schedule.setActive(false); - schedule.setDeletedAt(getCurrentTimestamp()); + Schedule schedule = scheduleRepository.findByScheduleId(scheduleDeleteDTO.getScheduleId()) + .orElseThrow(() -> new ScheduleCommonException(ScheduleErrorCode.SCHEDULE_NOT_FOUND)); - scheduleRepository.save(schedule); + if(!memberId.equals(schedule.getMemberId())){ + // 권한 오류 + throw new ScheduleCommonException(ScheduleErrorCode.AUTHORIZATION_VIOLATION); + } - return true; + schedule.setActive(false); + schedule.setDeletedAt(getCurrentTime()); + + try { + scheduleRepository.save(schedule); + + return true; + } catch (DataIntegrityViolationException e) { + // 데이터 무결성 위반 예외 처리 + throw new ScheduleCommonException(ScheduleErrorCode.DATA_INTEGRITY_VIOLATION); + } catch (Exception e) { + // 서버 오류 + throw new ScheduleCommonException(ScheduleErrorCode.INTERNAL_SERVER_ERROR); + } } } diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/common/exception/CommonException.java b/src/main/java/stanl_2/final_backend/domain/schedule/common/exception/ScheduleCommonException.java similarity index 61% rename from src/main/java/stanl_2/final_backend/domain/schedule/common/exception/CommonException.java rename to src/main/java/stanl_2/final_backend/domain/schedule/common/exception/ScheduleCommonException.java index 870ef028..b5050ec7 100644 --- a/src/main/java/stanl_2/final_backend/domain/schedule/common/exception/CommonException.java +++ b/src/main/java/stanl_2/final_backend/domain/schedule/common/exception/ScheduleCommonException.java @@ -5,12 +5,12 @@ @Getter @RequiredArgsConstructor -public class CommonException extends RuntimeException { - private final ErrorCode errorCode; +public class ScheduleCommonException extends RuntimeException { + private final ScheduleErrorCode scheduleErrorCode; // 에러 발생시 ErroCode 별 메시지 @Override public String getMessage() { - return this.errorCode.getMsg(); + return this.scheduleErrorCode.getMsg(); } } diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/common/exception/ScheduleErrorCode.java b/src/main/java/stanl_2/final_backend/domain/schedule/common/exception/ScheduleErrorCode.java new file mode 100644 index 00000000..533b3f1c --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/schedule/common/exception/ScheduleErrorCode.java @@ -0,0 +1,59 @@ +package stanl_2.final_backend.domain.schedule.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum ScheduleErrorCode { + + /** + * 400(Bad Request) + * 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. + */ + DATA_INTEGRITY_VIOLATION(40001, HttpStatus.BAD_REQUEST, "데이터 무결 위반하였습니다."), + CONSTRAINT_VIOLATION(40002, HttpStatus.BAD_REQUEST, "제약 조건 위반하였습니다."), + + + + /** + * 401(Unauthorized) + * 비록 HTTP 표준에서는 "미승인(unauthorized)"를 명확히 하고 있지만, + * 의미상 이 응답은 "비인증(unauthenticated)"을 의미합니다. + * 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로를 인증해야 합니다. + */ + + + /** + * 403(Forbidden) + * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. + * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. + */ + AUTHORIZATION_VIOLATION(40301, HttpStatus.BAD_REQUEST, "본인의 일정에만 접근 가능합니다."), + + + /** + * 404(Not Found) + * 서버는 요청받은 리소스를 찾을 수 없습니다. 브라우저에서는 알려지지 않은 URL을 의미합니다. + * 이것은 API에서 종점은 적절하지만 리소스 자체는 존재하지 않음을 의미할 수도 있습니다. + * 서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위하여 이 응답을 403 대신에 전송할 수도 있습니다. + * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. + */ + SCHEDULE_NOT_FOUND(40401, HttpStatus.NOT_FOUND, "해당하는 일정표를 찾을 수 없습니다."), + + + /** + * 500(Internal Server Error) + * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. + */ + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."), + MAPPING_ERROR(50001, HttpStatus.INTERNAL_SERVER_ERROR, "ModleMapper 매핑 오류입니다."), + DATA_ACCESS_ERROR(50002, HttpStatus.INTERNAL_SERVER_ERROR, "데이터베이스 접근 중 오류가 발생했습니다."); + + + + private final Integer code; + private final HttpStatus httpStatus; + private final String msg; +} diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/common/exception/ScheduleExceptionResponse.java b/src/main/java/stanl_2/final_backend/domain/schedule/common/exception/ScheduleExceptionResponse.java new file mode 100644 index 00000000..1165f5d0 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/schedule/common/exception/ScheduleExceptionResponse.java @@ -0,0 +1,22 @@ +package stanl_2.final_backend.domain.schedule.common.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class ScheduleExceptionResponse { + private final Integer code; + private final String msg; + private final HttpStatus httpStatus; + + public ScheduleExceptionResponse(ScheduleErrorCode scheduleErrorCode) { + this.code = scheduleErrorCode.getCode(); + this.msg = scheduleErrorCode.getMsg(); + this.httpStatus = scheduleErrorCode.getHttpStatus(); + } + + public static ScheduleExceptionResponse of(ScheduleErrorCode scheduleErrorCode) { + return new ScheduleExceptionResponse(scheduleErrorCode); + } + +} diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/common/response/ResponseMessage.java b/src/main/java/stanl_2/final_backend/domain/schedule/common/response/ScheduleResponseMessage.java similarity index 85% rename from src/main/java/stanl_2/final_backend/domain/schedule/common/response/ResponseMessage.java rename to src/main/java/stanl_2/final_backend/domain/schedule/common/response/ScheduleResponseMessage.java index a3cf0529..ca583c8c 100644 --- a/src/main/java/stanl_2/final_backend/domain/schedule/common/response/ResponseMessage.java +++ b/src/main/java/stanl_2/final_backend/domain/schedule/common/response/ScheduleResponseMessage.java @@ -7,7 +7,7 @@ @Builder @Getter @Setter -public class ResponseMessage { +public class ScheduleResponseMessage { private Integer httpStatus; private String msg; private Object result; diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/query/controller/ScheduleController.java b/src/main/java/stanl_2/final_backend/domain/schedule/query/controller/ScheduleController.java index 971d6f27..987ba48d 100644 --- a/src/main/java/stanl_2/final_backend/domain/schedule/query/controller/ScheduleController.java +++ b/src/main/java/stanl_2/final_backend/domain/schedule/query/controller/ScheduleController.java @@ -1,11 +1,103 @@ package stanl_2.final_backend.domain.schedule.query.controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import stanl_2.final_backend.domain.schedule.common.response.ScheduleResponseMessage; +import stanl_2.final_backend.domain.schedule.query.dto.ScheduleDTO; +import stanl_2.final_backend.domain.schedule.query.dto.ScheduleDetailDTO; +import stanl_2.final_backend.domain.schedule.query.dto.ScheduleYearMonthDTO; +import stanl_2.final_backend.domain.schedule.query.service.ScheduleQueryService; + +import java.security.Principal; +import java.util.List; @RestController("queryScheduleController") -@RequestMapping("/api/v1/center") +@RequestMapping("/api/v1/schedule") public class ScheduleController { + private final ScheduleQueryService scheduleQueryService; + + @Autowired + public ScheduleController(ScheduleQueryService scheduleQueryService) { + this.scheduleQueryService = scheduleQueryService; + } + + @Operation(summary = "일정 전체 조회 api") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "일정 조회 성공", + content = {@Content(schema = @Schema(implementation = ScheduleResponseMessage.class))}) + }) + @GetMapping("") + public ResponseEntity selectAllSchedule(Principal principal){ + + String memberLogindId = principal.getName(); + List schedules = scheduleQueryService.selectAllSchedule(memberLogindId); + + return ResponseEntity.ok(ScheduleResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(schedules) + .build()); + } + + + @Operation(summary = "일정 조건별(년&일) 전체 조회 api") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "일정 조건별(년&일) 전체 조회 성공", + content = {@Content(schema = @Schema(implementation = ScheduleResponseMessage.class))}) + }) + @GetMapping("{year}/{month}") + public ResponseEntity selectMonthSchedule(Principal principal, + @PathVariable("year") String year, + @PathVariable("month") String month){ + + String memberLoginId = principal.getName(); + + ScheduleYearMonthDTO scheduleYearMonthDTO = new ScheduleYearMonthDTO(); + scheduleYearMonthDTO.setMemberLoginId(memberLoginId); + scheduleYearMonthDTO.setYear(year); + scheduleYearMonthDTO.setMonth(month); + + List yearMonthSchedule = scheduleQueryService.selectYearMonthSchedule(scheduleYearMonthDTO); + + return ResponseEntity.ok(ScheduleResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(yearMonthSchedule) + .build()); + } + + + @Operation(summary = "일정 상세 조회 api") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "일정 상세 조회 성공", + content = {@Content(schema = @Schema(implementation = ScheduleResponseMessage.class))}) + }) + @GetMapping("{scheduleId}") + public ResponseEntity selectDetailSchedule(Principal principal, + @PathVariable("scheduleId") String scheduleId){ + + String memberLoginId = principal.getName();; + + ScheduleDetailDTO scheduleDetailDTO = new ScheduleDetailDTO(); + scheduleDetailDTO.setMemberLoginId(memberLoginId); + scheduleDetailDTO.setScheduleId(scheduleId); + + ScheduleDetailDTO responseDetailSchedule = scheduleQueryService.selectDetailSchedule(scheduleDetailDTO); + return ResponseEntity.ok(ScheduleResponseMessage.builder() + .httpStatus(200) + .msg("성공") + .result(responseDetailSchedule) + .build()); + } } diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/query/dto/ScheduleDTO.java b/src/main/java/stanl_2/final_backend/domain/schedule/query/dto/ScheduleDTO.java index 9b24f7eb..89c20a5b 100644 --- a/src/main/java/stanl_2/final_backend/domain/schedule/query/dto/ScheduleDTO.java +++ b/src/main/java/stanl_2/final_backend/domain/schedule/query/dto/ScheduleDTO.java @@ -11,13 +11,11 @@ @ToString public class ScheduleDTO { - private String id; + private String scheduleId; private String name; private String content; - private String reservationTime; - private Timestamp createdAt; - private Timestamp updatedAt; - private Timestamp deletedAt; - private Boolean active; + private String tag; + private String startAt; + private String endAt; private String memberId; } diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/query/dto/ScheduleDayDTO.java b/src/main/java/stanl_2/final_backend/domain/schedule/query/dto/ScheduleDayDTO.java new file mode 100644 index 00000000..ac42ae42 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/schedule/query/dto/ScheduleDayDTO.java @@ -0,0 +1,20 @@ +package stanl_2.final_backend.domain.schedule.query.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@ToString +public class ScheduleDayDTO { + + private String scheduleId; + private String name; + private String tag; + private String startAt; + private String endAt; + private String memberId; + + +} diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/query/dto/ScheduleDetailDTO.java b/src/main/java/stanl_2/final_backend/domain/schedule/query/dto/ScheduleDetailDTO.java new file mode 100644 index 00000000..257bc6e2 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/schedule/query/dto/ScheduleDetailDTO.java @@ -0,0 +1,20 @@ +package stanl_2.final_backend.domain.schedule.query.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@ToString +public class ScheduleDetailDTO { + + private String scheduleId; + private String name; + private String content; + private String tag; + private String startAt; + private String endAt; + private String memberId; + private String memberLoginId; +} diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/query/dto/ScheduleYearMonthDTO.java b/src/main/java/stanl_2/final_backend/domain/schedule/query/dto/ScheduleYearMonthDTO.java new file mode 100644 index 00000000..78fb45ef --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/schedule/query/dto/ScheduleYearMonthDTO.java @@ -0,0 +1,23 @@ +package stanl_2.final_backend.domain.schedule.query.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@ToString +public class ScheduleYearMonthDTO { + + private String scheduleId; + private String name; + private String content; + private String tag; + private String startAt; + private String endAt; + private String memberId; + private String memberLoginId; + + private String year; + private String month; +} diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/query/repository/ScheduleMapper.java b/src/main/java/stanl_2/final_backend/domain/schedule/query/repository/ScheduleMapper.java index 16749291..c0e09889 100644 --- a/src/main/java/stanl_2/final_backend/domain/schedule/query/repository/ScheduleMapper.java +++ b/src/main/java/stanl_2/final_backend/domain/schedule/query/repository/ScheduleMapper.java @@ -1,4 +1,22 @@ package stanl_2.final_backend.domain.schedule.query.repository; +import org.apache.ibatis.annotations.Mapper; +import stanl_2.final_backend.domain.schedule.query.dto.ScheduleDTO; +import stanl_2.final_backend.domain.schedule.query.dto.ScheduleDayDTO; +import stanl_2.final_backend.domain.schedule.query.dto.ScheduleDetailDTO; +import stanl_2.final_backend.domain.schedule.query.dto.ScheduleYearMonthDTO; + +import java.util.List; +import java.util.Map; + +@Mapper public interface ScheduleMapper { + + List findSchedulesByMemberIdAndYearMonth(Map arg); + + ScheduleDetailDTO findScheduleByMemberIdAndScheduleId(Map arg); + + List findSchedulesByMemberIdAndStartAt(Map arg); + + List findAllSchedulesByDay(String currentDay); } diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/query/service/ScheduleQueryService.java b/src/main/java/stanl_2/final_backend/domain/schedule/query/service/ScheduleQueryService.java new file mode 100644 index 00000000..1484886c --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/schedule/query/service/ScheduleQueryService.java @@ -0,0 +1,18 @@ +package stanl_2.final_backend.domain.schedule.query.service; + +import stanl_2.final_backend.domain.schedule.query.dto.ScheduleDTO; +import stanl_2.final_backend.domain.schedule.query.dto.ScheduleDayDTO; +import stanl_2.final_backend.domain.schedule.query.dto.ScheduleDetailDTO; +import stanl_2.final_backend.domain.schedule.query.dto.ScheduleYearMonthDTO; + +import java.util.List; + +public interface ScheduleQueryService { + List selectAllSchedule(String memberLogindId); + + List selectYearMonthSchedule(ScheduleYearMonthDTO scheduleYearMonthDTO); + + ScheduleDetailDTO selectDetailSchedule(ScheduleDetailDTO scheduleDetailDTO); + + List findSchedulesByDate(String currentDay); +} diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/query/service/ScheduleQueryServiceImpl.java b/src/main/java/stanl_2/final_backend/domain/schedule/query/service/ScheduleQueryServiceImpl.java new file mode 100644 index 00000000..82bb904c --- /dev/null +++ b/src/main/java/stanl_2/final_backend/domain/schedule/query/service/ScheduleQueryServiceImpl.java @@ -0,0 +1,114 @@ +package stanl_2.final_backend.domain.schedule.query.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stanl_2.final_backend.domain.member.common.exception.MemberCommonException; +import stanl_2.final_backend.domain.member.common.exception.MemberErrorCode; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.schedule.common.exception.ScheduleCommonException; +import stanl_2.final_backend.domain.schedule.common.exception.ScheduleErrorCode; +import stanl_2.final_backend.domain.schedule.query.dto.ScheduleDTO; +import stanl_2.final_backend.domain.schedule.query.dto.ScheduleDayDTO; +import stanl_2.final_backend.domain.schedule.query.dto.ScheduleDetailDTO; +import stanl_2.final_backend.domain.schedule.query.dto.ScheduleYearMonthDTO; +import stanl_2.final_backend.domain.schedule.query.repository.ScheduleMapper; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +@Service +public class ScheduleQueryServiceImpl implements ScheduleQueryService { + + private final ScheduleMapper scheduleMapper; + private final AuthQueryService authQueryService; + private String getCurrentTime() { + ZonedDateTime nowKst = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + return nowKst.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + + @Autowired + public ScheduleQueryServiceImpl(ScheduleMapper scheduleMapper, AuthQueryService authQueryService) { + this.scheduleMapper = scheduleMapper; + this.authQueryService = authQueryService; + } + + @Override + @Transactional(readOnly = true) + public List selectAllSchedule(String memberLogindId) { + + String memberId = authQueryService.selectMemberIdByLoginId(memberLogindId); + + String currentMonth = getCurrentTime().substring(0,7); + + Map arg = new HashMap<>(); + arg.put("memberId",memberId); + arg.put("month",currentMonth); + + List scheduleList = scheduleMapper.findSchedulesByMemberIdAndStartAt(arg); + + // Mapping 오류 체크 고려하기 + + return scheduleList; + } + + @Override + @Transactional(readOnly = true) + public List selectYearMonthSchedule(ScheduleYearMonthDTO scheduleYearMonthDTO) { + + String memberId = authQueryService.selectMemberIdByLoginId(scheduleYearMonthDTO.getMemberLoginId()); + scheduleYearMonthDTO.setMemberId(memberId); + + String yearMonth = scheduleYearMonthDTO.getYear() + "-" + scheduleYearMonthDTO.getMonth(); + + Map arg = new HashMap<>(); + arg.put("memberId", scheduleYearMonthDTO.getMemberId()); + arg.put("yearMonth", yearMonth); + + List scheduleList = + scheduleMapper.findSchedulesByMemberIdAndYearMonth(arg); + + // Mapping 오류 체크 고려하기 + + return scheduleList; + } + + @Override + @Transactional(readOnly = true) + public ScheduleDetailDTO selectDetailSchedule(ScheduleDetailDTO scheduleDetailDTO) { + + String memberId = authQueryService.selectMemberIdByLoginId(scheduleDetailDTO.getMemberLoginId()); + scheduleDetailDTO.setMemberId(memberId); + + if(scheduleDetailDTO.getScheduleId() == null || scheduleDetailDTO.getScheduleId().trim().isEmpty()){ + throw new ScheduleCommonException(ScheduleErrorCode.SCHEDULE_NOT_FOUND); + } + + Map arg = new HashMap<>(); + arg.put("memberId", scheduleDetailDTO.getMemberId()); + arg.put("scheduleId", scheduleDetailDTO.getScheduleId()); + + ScheduleDetailDTO responseDetailSchedule + = scheduleMapper.findScheduleByMemberIdAndScheduleId(arg); + + // Mapping 오류 체크 고려하기 + + return responseDetailSchedule; + } + + @Override + @Transactional(readOnly = true) + public List findSchedulesByDate(String currentDay) { + + List todaySchedules = scheduleMapper.findAllSchedulesByDay(currentDay); + + return todaySchedules; + } +} diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/query/service/ScheduleService.java b/src/main/java/stanl_2/final_backend/domain/schedule/query/service/ScheduleService.java deleted file mode 100644 index a0b2dd2d..00000000 --- a/src/main/java/stanl_2/final_backend/domain/schedule/query/service/ScheduleService.java +++ /dev/null @@ -1,4 +0,0 @@ -package stanl_2.final_backend.domain.schedule.query.service; - -public interface ScheduleService { -} diff --git a/src/main/java/stanl_2/final_backend/domain/schedule/query/service/ScheduleServiceImple.java b/src/main/java/stanl_2/final_backend/domain/schedule/query/service/ScheduleServiceImple.java deleted file mode 100644 index 8be9faba..00000000 --- a/src/main/java/stanl_2/final_backend/domain/schedule/query/service/ScheduleServiceImple.java +++ /dev/null @@ -1,9 +0,0 @@ -package stanl_2.final_backend.domain.schedule.query.service; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -@Slf4j -@Service("querScheduleServiceImpl") -public class ScheduleServiceImple implements ScheduleService{ -} diff --git a/src/main/java/stanl_2/final_backend/global/config/AppConfig.java b/src/main/java/stanl_2/final_backend/global/config/AppConfig.java index 8795b073..e430a48f 100644 --- a/src/main/java/stanl_2/final_backend/global/config/AppConfig.java +++ b/src/main/java/stanl_2/final_backend/global/config/AppConfig.java @@ -4,6 +4,7 @@ import org.modelmapper.convention.MatchingStrategies; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; @Configuration public class AppConfig { @@ -13,4 +14,9 @@ public ModelMapper modelMapper() { modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT); // 엄격한 매핑 전략 설정 return modelMapper; } + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } } diff --git a/src/main/java/stanl_2/final_backend/global/config/CacheConfig.java b/src/main/java/stanl_2/final_backend/global/config/CacheConfig.java new file mode 100644 index 00000000..6165a1a4 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/global/config/CacheConfig.java @@ -0,0 +1,9 @@ +package stanl_2.final_backend.global.config; + +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableCaching +public class CacheConfig { +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/global/config/DataSourceConfig.java b/src/main/java/stanl_2/final_backend/global/config/DataSourceConfig.java new file mode 100644 index 00000000..aeae0086 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/global/config/DataSourceConfig.java @@ -0,0 +1,69 @@ +package stanl_2.final_backend.global.config; + +import com.zaxxer.hikari.HikariDataSource; +import jakarta.persistence.EntityManagerFactory; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import org.springframework.context.annotation.Primary; +import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.transaction.PlatformTransactionManager; +import stanl_2.final_backend.global.config.datasource.ReplicationRoutingDataSource; + +import javax.sql.DataSource; +import java.util.HashMap; +import java.util.Map; + +@Configuration +public class DataSourceConfig { + @ConfigurationProperties(prefix = "spring.datasource.writer.hikari") + @Bean(name = "writerDataSource") + public DataSource writerDataSource() { + return DataSourceBuilder.create().type(HikariDataSource.class).build(); + } + + @ConfigurationProperties(prefix = "spring.datasource.reader.hikari") + @Bean(name = "readerDataSource") + public DataSource readerDataSource() { + // DataSourceBuilder를 사용해 HikariDataSource 타입의 데이터 소스를 생성합니다. + return DataSourceBuilder.create().type(HikariDataSource.class).build(); + } + + @DependsOn({"writerDataSource", "readerDataSource"}) + @Bean + public DataSource routingDataSource( + @Qualifier("writerDataSource") DataSource writer, + @Qualifier("readerDataSource") DataSource reader) { + + ReplicationRoutingDataSource routingDataSource = new ReplicationRoutingDataSource(); + + Map dataSourceMap = new HashMap<>(); + + dataSourceMap.put("writer", writer); // 쓰기 전용 데이터 소스 + dataSourceMap.put("reader", reader); // 읽기 전용 데이터 소스 + + routingDataSource.setTargetDataSources(dataSourceMap); // 대상 데이터 소스 설정 + routingDataSource.setDefaultTargetDataSource(writer); // 기본 데이터 소스 설정 + routingDataSource.afterPropertiesSet(); + + return routingDataSource; + } + + @DependsOn("routingDataSource") + @Primary // 이 빈을 기본 `DataSource`로 설정 + @Bean + public DataSource dataSource(DataSource routingDataSource) { + return new LazyConnectionDataSourceProxy(routingDataSource); + } + + @Bean + public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory){ + JpaTransactionManager jpaTransactionManager = new JpaTransactionManager(); + jpaTransactionManager.setEntityManagerFactory(entityManagerFactory); + return jpaTransactionManager; + } +} diff --git a/src/main/java/stanl_2/final_backend/global/config/S3Config.java b/src/main/java/stanl_2/final_backend/global/config/S3Config.java new file mode 100644 index 00000000..e5af658a --- /dev/null +++ b/src/main/java/stanl_2/final_backend/global/config/S3Config.java @@ -0,0 +1,28 @@ +package stanl_2.final_backend.global.config; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class S3Config { + @Value("${cloud.aws.credentials.access-key}") + private String accessKey; + @Value("${cloud.aws.credentials.secret-key}") + private String secretKey; + @Value("${cloud.aws.region.static}") + private String region; + + @Bean + public AmazonS3Client amazonS3Client() { + BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + return (AmazonS3Client) AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .build(); + } +} diff --git a/src/main/java/stanl_2/final_backend/global/config/SwaggerConfig.java b/src/main/java/stanl_2/final_backend/global/config/SwaggerConfig.java index 77d6feae..d9365116 100644 --- a/src/main/java/stanl_2/final_backend/global/config/SwaggerConfig.java +++ b/src/main/java/stanl_2/final_backend/global/config/SwaggerConfig.java @@ -1,47 +1,44 @@ package stanl_2.final_backend.global.config; import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; import io.swagger.v3.oas.annotations.info.Info; -import lombok.RequiredArgsConstructor; -import org.springdoc.core.customizers.OpenApiCustomizer; -import org.springdoc.core.models.GroupedOpenApi; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.security.SecurityScheme; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; +@Configuration @OpenAPIDefinition( info = @Info( title = "Motive_BE API 명세서", description = "Motive 프로젝트 API 명세서", version = "v1" - ) + ), + security = {@SecurityRequirement(name = "Bearer Authentication")} +) +@SecurityScheme( + name = "Bearer Authentication", + type = SecuritySchemeType.HTTP, + scheme = "bearer", + bearerFormat = "JWT" ) -@Configuration -@RequiredArgsConstructor public class SwaggerConfig { -// @Bean -// @Profile("!Prod") -// public GroupedOpenApi GroupedOpenApi(){ -// -// String jwtSchemaName = "Bear 토큰 입력"; -// String[] paths = {"../../domain/center/**"}; -// -// return GroupedOpenApi -// .builder() -// .group("영업 매장 관련 API") -// .pathsToMatch(paths) -// .addOpenApiCustomizer(buildSecurityOpenApi()).build(); -// } - -// public OpenApiCustomizer buildSecurityOpenApi() { -// // jwt token을 한번 설정하면 header에 값을 넣어주는 코드 -// return OpenApi -> OpenApi.addSecurityItem(new SecurityRequirement().addList("jwt token")) -// .getComponents().addSecuritySchemes("jwt token", new SecurityScheme() -// .name("Authorization") -// .type(SecurityScheme.Type.HTTP) -// .in(SecurityScheme.In.HEADER) -// .bearerFormat("JWT") -// .scheme("bearer")); -// } + @Bean + public OpenAPI OpenAPI() { + return new OpenAPI() + .components(new Components() + .addSecuritySchemes("Bearer Authentication", + new io.swagger.v3.oas.models.security.SecurityScheme() + .type(io.swagger.v3.oas.models.security.SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + ) + ) + .addSecurityItem(new io.swagger.v3.oas.models.security.SecurityRequirement() + .addList("Bearer Authentication")); + } } diff --git a/src/main/java/stanl_2/final_backend/global/config/WebConfig.java b/src/main/java/stanl_2/final_backend/global/config/WebConfig.java new file mode 100644 index 00000000..74ffdbe8 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/global/config/WebConfig.java @@ -0,0 +1,20 @@ +package stanl_2.final_backend.global.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") // API 경로 매핑 + .allowedOrigins("https://stanl2motive.com") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 허용할 HTTP 메서드 + .allowedHeaders("Content-Type", "Authorization") // 허용할 요청 헤더 + .exposedHeaders("Authorization") // 응답 헤더 노출 + .allowCredentials(true) // 인증 정보 허용 + .maxAge(3600); // Preflight 요청 캐싱 시간 + } +} diff --git a/src/main/java/stanl_2/final_backend/global/config/datasource/ReplicationRoutingDataSource.java b/src/main/java/stanl_2/final_backend/global/config/datasource/ReplicationRoutingDataSource.java new file mode 100644 index 00000000..16edcc83 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/global/config/datasource/ReplicationRoutingDataSource.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.global.config.datasource; + +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; + +import static org.springframework.transaction.support.TransactionSynchronizationManager.isCurrentTransactionReadOnly; + +public class ReplicationRoutingDataSource extends AbstractRoutingDataSource { + @Override + public Object determineCurrentLookupKey() { + String dataSourceName = isCurrentTransactionReadOnly() ? "reader" : "writer"; + return dataSourceName; + + } +} diff --git a/src/main/java/stanl_2/final_backend/global/dataloader/Initializer.java b/src/main/java/stanl_2/final_backend/global/dataloader/Initializer.java new file mode 100644 index 00000000..8351a49f --- /dev/null +++ b/src/main/java/stanl_2/final_backend/global/dataloader/Initializer.java @@ -0,0 +1,1312 @@ +package stanl_2.final_backend.global.dataloader; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Component; +import org.springframework.util.StreamUtils; +import org.springframework.web.multipart.MultipartFile; +import stanl_2.final_backend.domain.career.command.domain.aggregate.entity.Career; +import stanl_2.final_backend.domain.career.command.domain.repository.CareerRepository; +import stanl_2.final_backend.domain.center.command.application.dto.request.CenterRegistDTO; +import stanl_2.final_backend.domain.center.command.application.service.CenterCommandService; +import stanl_2.final_backend.domain.center.command.domain.repository.CenterRepository; +import stanl_2.final_backend.domain.certification.command.domain.aggregate.entity.Certification; +import stanl_2.final_backend.domain.certification.command.domain.repository.CertificationRepository; +import stanl_2.final_backend.domain.customer.command.application.dto.CustomerRegistDTO; +import stanl_2.final_backend.domain.customer.command.application.service.CustomerCommandService; +import stanl_2.final_backend.domain.customer.command.domain.repository.CustomerRepository; +import stanl_2.final_backend.domain.education.command.domain.aggregate.entity.Education; +import stanl_2.final_backend.domain.education.command.domain.repository.EducationRepository; +import stanl_2.final_backend.domain.family.command.domain.aggregate.entity.Family; +import stanl_2.final_backend.domain.family.command.domain.repository.FamilyRepository; +import stanl_2.final_backend.domain.member.command.application.dto.GrantDTO; +import stanl_2.final_backend.domain.member.command.application.dto.SignupDTO; +import stanl_2.final_backend.domain.member.command.application.service.AuthCommandService; +import stanl_2.final_backend.domain.member.command.domain.aggregate.entity.Member; +import stanl_2.final_backend.domain.member.command.domain.repository.MemberRepository; +import stanl_2.final_backend.domain.notices.command.domain.aggragate.entity.Notice; +import stanl_2.final_backend.domain.notices.command.domain.repository.NoticeRepository; +import stanl_2.final_backend.domain.organization.command.domain.aggregate.entity.Organization; +import stanl_2.final_backend.domain.organization.command.domain.repository.OrganizationRepository; +import stanl_2.final_backend.domain.problem.command.domain.aggregate.entity.Problem; +import stanl_2.final_backend.domain.problem.command.domain.aggregate.repository.ProblemRepository; +import stanl_2.final_backend.domain.product.command.application.domain.aggregate.entity.Product; +import stanl_2.final_backend.domain.product.command.application.domain.aggregate.entity.ProductOption; +import stanl_2.final_backend.domain.product.command.application.domain.repository.ProductOptionRepository; +import stanl_2.final_backend.domain.product.command.application.domain.repository.ProductRepository; +import stanl_2.final_backend.domain.promotion.command.domain.aggregate.entity.Promotion; +import stanl_2.final_backend.domain.promotion.command.domain.aggregate.repository.PromotionRepository; +import stanl_2.final_backend.domain.schedule.command.application.dto.ScheduleRegistDTO; +import stanl_2.final_backend.domain.schedule.command.application.service.ScheduleCommandService; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +@Slf4j +@Component +@RequiredArgsConstructor +public class Initializer implements ApplicationRunner { + + private final AuthCommandService authCommandService; + private final NoticeRepository noticeRepository; + private final MemberRepository memberRepository; + private final CareerRepository careerRepository; + private final CertificationRepository certificationRepository; + private final EducationRepository educationRepository; + private final FamilyRepository familyRepository; + private final OrganizationRepository organizationRepository; + private final CustomerCommandService customerCommandService; + private final CustomerRepository customerRepository; + private final CenterCommandService centerCommandService; + private final CenterRepository centerRepository; + private final ProductRepository productRepository; + private final ProductOptionRepository productOptionRepository; + + private final PromotionRepository promotionRepository; + private final ProblemRepository problemRepository; + + @Override + public void run(ApplicationArguments args) throws Exception { + + if (noticeRepository.count() == 0) { + List notices = Arrays.asList( + new Notice(null, "신차 출시 기념 이벤트", null, "NORMAL", "공지사항

신차 출시를 기념하여 대규모 이벤트를 진행합니다. 많은 참여 부탁드립니다.

", null, null, null, true, "도유정", null), + new Notice(null, "연말 프로모션 안내", null, "STRATEGY", "공지사항

2024년 연말 프로모션에 대한 상세 안내입니다. 다양한 혜택이 준비되어 있으니 확인해주세요.

", null, null, null, true, "도유정", null), + new Notice(null, "고객 만족도 조사 결과 공유", null, "GOAL", "공지사항

2024년 고객 만족도 조사 결과를 공유드립니다. 앞으로도 최선을 다하겠습니다.

", null, null, null, true, "안수환", null), + new Notice(null, "전국 영업팀 실적 발표", null, "NORMAL", "공지사항

2024년 전국 영업팀 실적 발표 및 시상식 일정 안내입니다.

", null, null, null, true, "성지윤", null), + new Notice(null, "신규 고객 유치 프로모션", null, "STRATEGY", "공지사항

신규 고객 유치를 위한 특별 프로모션에 대한 안내입니다.

", null, null, null, true, "양시우", null), + new Notice(null, "분기별 영업 목표 설정 회의", null, "GOAL", "공지사항

다음 분기의 영업 목표 설정을 위한 회의 일정을 공지드립니다.

", null, null, null, true, "율도현", null), + new Notice(null, "신규 서비스 도입 안내", null, "NORMAL", "공지사항

새로운 서비스를 도입합니다. 상세 내용은 본 공지를 통해 확인해주세요.

", null, null, null, true, "하정현", null), + new Notice(null, "자동차 판매 기술 교육", null, "STRATEGY", "공지사항

영업사원을 위한 최신 자동차 판매 기술 교육을 실시합니다.

", null, null, null, true, "도유정", null), + new Notice(null, "우수 영업 사원 시상식", null, "GOAL", "공지사항

2024년 우수 영업 사원 시상식 일정 및 장소를 안내드립니다.

", null, null, null, true, "표수경", null), + new Notice(null, "연말 고객 감사 이벤트", null, "NORMAL", "공지사항

연말을 맞아 고객 감사 이벤트를 준비하였습니다. 많은 참여 부탁드립니다.

", null, null, null, true, "도유정", null), + new Notice(null, "팀워크 향상을 위한 워크샵", null, "STRATEGY", "공지사항

영업팀 팀워크 강화를 위한 워크샵 일정을 공지드립니다. 많은 참여 부탁드립니다.

", null, null, null, true, "현하린", null), + new Notice(null, "고객 관리 시스템 업데이트", null, "NORMAL", "공지사항

새로운 고객 관리 시스템이 도입됩니다. 상세한 사용 방법은 추가 공지를 통해 안내드립니다.

", null, null, null, true, "규예서", null), + new Notice(null, "2024년 상반기 판매 전략 회의", null, "GOAL", "공지사항

2024년 상반기 판매 목표 및 전략 수립을 위한 회의 일정을 공유합니다.

", null, null, null, true, "천하은", null), + new Notice(null, "신규 고객 관리 방안", null, "STRATEGY", "공지사항

신규 고객 관리를 위한 구체적인 방안과 목표를 안내드립니다.

", null, null, null, true, "심태솔", null), + new Notice(null, "2024년 상반기 영업 실적 발표", null, "NORMAL", "공지사항

상반기 영업 실적 및 하반기 계획에 대해 공유드립니다.

", null, null, null, true, "양민지", null), + new Notice(null, "영업 효율화 방안 공유", null, "STRATEGY", "공지사항

영업 업무를 보다 효율적으로 수행하기 위한 방안을 논의합니다.

", null, null, null, true, "유하율", null), + new Notice(null, "서비스 품질 향상 계획 발표", null, "GOAL", "공지사항

서비스 품질 향상을 위한 세부 계획과 목표를 발표합니다.

", null, null, null, true, "봉서우", null), + new Notice(null, "자동차 서비스 부문 고객 설문 결과", null, "NORMAL", "공지사항

2024년 상반기 고객 설문 조사 결과를 공유합니다. 항상 고객의 목소리에 귀 기울이겠습니다.

", null, null, null, true, "도재현", null), + new Notice(null, "고객 감사 이벤트 안내", null, "STRATEGY", "공지사항

고객 감사의 마음을 담아 특별 이벤트를 준비했습니다.

", null, null, null, true, "필승환", null), + new Notice(null, "다음 분기 영업 목표 설정", null, "GOAL", "공지사항

다음 분기 목표 설정 및 세부 전략을 논의합니다.

", null, null, null, true, "한정환", null), + new Notice(null, "신규 고객 초대 프로모션", null, "STRATEGY", "공지사항

신규 고객을 초대하기 위한 특별 프로모션을 진행합니다. 많은 관심 부탁드립니다.

", null, null, null, true, "운예린", null), + new Notice(null, "전국 영업팀 회의 결과 보고", null, "GOAL", "공지사항

2024년 전국 영업팀 회의 결과를 공유드립니다.

", null, null, null, true, "서채우", null), + new Notice(null, "고객 만족도 개선 프로젝트", null, "NORMAL", "공지사항

고객 만족도를 높이기 위한 개선 프로젝트를 시작합니다. 자세한 내용은 공지를 확인해주세요.

", null, null, null, true, "안수환", null), + new Notice(null, "영업 성과 분석 회의", null, "STRATEGY", "공지사항

영업 성과를 분석하고 개선 방향을 논의하는 회의가 예정되어 있습니다.

", null, null, null, true, "성지윤", null), + new Notice(null, "신제품 발표회 개최", null, "GOAL", "공지사항

새로운 제품에 대한 발표회가 개최됩니다. 많은 관심 부탁드립니다.

", null, null, null, true, "양시우", null), + new Notice(null, "하반기 목표 수립 워크샵", null, "NORMAL", "공지사항

하반기 목표를 수립하기 위한 워크샵 일정이 확정되었습니다.

", null, null, null, true, "율도현", null), + new Notice(null, "최신 판매 기술 교육 일정", null, "STRATEGY", "공지사항

최신 판매 기술을 배우는 교육 프로그램이 준비되었습니다.

", null, null, null, true, "하정현", null), + new Notice(null, "전사 협력 강화를 위한 간담회", null, "GOAL", "공지사항

협력 강화를 위해 간담회를 개최합니다. 많은 참석 바랍니다.

", null, null, null, true, "평예원", null), + new Notice(null, "고객 관리 시스템 업그레이드 안내", null, "NORMAL", "공지사항

고객 관리 시스템이 새롭게 업그레이드됩니다. 사용법은 공지사항을 확인해주세요.

", null, null, null, true, "표수경", null), + new Notice(null, "상반기 실적 우수자 발표", null, "GOAL", "공지사항

2024년 상반기 실적이 우수한 사원들을 발표합니다.

", null, null, null, true, "조예원", null), + new Notice(null, "영업 효율성을 위한 가이드라인", null, "STRATEGY", "공지사항

영업 업무 효율성을 높이기 위한 가이드라인이 발표되었습니다.

", null, null, null, true, "현하린", null), + new Notice(null, "고객 대상 특별 감사 이벤트", null, "NORMAL", "공지사항

고객 감사의 마음을 담아 특별 이벤트를 준비했습니다.

", null, null, null, true, "규예서", null), + new Notice(null, "하반기 영업 목표 공유", null, "GOAL", "공지사항

2024년 하반기 영업 목표와 계획을 공유드립니다.

", null, null, null, true, "천하은", null), + new Notice(null, "신규 고객 서비스 정책 발표", null, "STRATEGY", "공지사항

신규 고객을 위한 서비스 정책이 발표되었습니다.

", null, null, null, true, "심태솔", null), + new Notice(null, "우수 영업 팀 시상식 일정", null, "NORMAL", "공지사항

우수한 실적을 기록한 영업 팀을 시상하는 행사가 열립니다.

", null, null, null, true, "양민지", null), + new Notice(null, "상반기 영업 실적 발표", null, "GOAL", "공지사항

상반기 영업 실적에 대한 보고와 분석이 진행됩니다.

", null, null, null, true, "유하율", null), + new Notice(null, "서비스 품질 향상 캠페인", null, "NORMAL", "공지사항

서비스 품질 향상을 위한 캠페인이 시작됩니다.

", null, null, null, true, "봉서우", null), + new Notice(null, "차세대 자동차 기술 세미나", null, "STRATEGY", "공지사항

최신 자동차 기술에 대해 배우는 세미나가 개최됩니다.

", null, null, null, true, "도재현", null), + new Notice(null, "우수 사원 감사 프로그램", null, "GOAL", "공지사항

우수한 실적을 기록한 사원을 대상으로 감사 프로그램이 진행됩니다.

", null, null, null, true, "필승환", null), + new Notice(null, "영업 효율화 방안 발표", null, "NORMAL", "공지사항

영업 효율화를 위한 새로운 방안이 발표되었습니다.

", null, null, null, true, "한정환", null), + new Notice(null, "하반기 실적 우수자 발표", null, "NORMAL", " 공지사항 body>

2024년 하반기 영업 실적 우수 사원 발표

2024년 하반기 동안 탁월한 영업 실적을 기록한 우수 사원을 발표합니다.

모든 영업 사원 여러분들의 노고에 진심으로 감사드리며, 아래와 같이 우수 사원을 선정하였습니다.  

우수 사원 명단

운예린: 매출 목표 초과 달성 및 신규 고객 유치 기여

안수환: 기존 고객 관리 강화 및 계약 연장율 최상위

율도현: 지역 매출 1위 및 신차 판매 부문 실적 최상위

성지윤: 고객 만족도 1위 및 판매 후 관리 우수

 이번 우수 사원으로 선정된 분들께는 특별 포상과 함께 감사의 마음을 전합니다.

 앞으로도 모든 분들이 함께 성장하고 발전할 수 있는 환경을 만들어 가겠습니다. 

시상식 일정

일시: 2024년 12월 20일 (수) 오후 3시

장소:</strong> 본사 대강당

참석 대상:전 직원 

 많은 참석 부탁드리며, 선정된 사원들께 다시 한번 축하의 말씀을 드립니다.

※ 문의사항은 인사팀으로 연락 부탁드립니다.

", null, null, null, true, "신하늘", null) + ); + for (Notice notice : notices) { + noticeRepository.save(notice); + } + log.info("Notice 데이터가 초기화되었습니다."); + } else { + log.info("Notice 테이블에 이미 데이터가 존재합니다."); + } + + if (promotionRepository.count() == 0) { + List promotions = Arrays.asList( + new Promotion(null, "신규 프로모션 안내", "프로모션

새로운 프로모션이 시작됩니다. 많은 관심 부탁드립니다.

", null, null, null, true, "봉서우", null), + new Promotion(null, "2024년 상반기 이벤트","프로모션

상반기 동안 진행되는 특별 이벤트를 안내드립니다.

", null, null, null, true, "서채우", null), + new Promotion(null, "고객 감사 프로모션", "프로모션

고객님께 감사드리며 특별 혜택을 제공합니다.

", null, null, null, true, "안수환", null), + new Promotion(null, "신차 출시 이벤트", "프로모션

신차 출시를 기념하여 다양한 혜택을 제공합니다.

", null, null, null, true, "봉서우", null), + new Promotion(null, "여름 시즌 프로모션", "프로모션

여름을 맞아 특별 할인 이벤트가 진행됩니다.

", null, null, null, true, "양시우", null), + new Promotion(null, "봄맞이 할인 프로모션", "프로모션

봄을 맞아 다양한 할인 혜택을 제공합니다.

", null, null, null, true, "봉서우", null), + new Promotion(null, "고객 추천 이벤트", "프로모션

고객 추천 이벤트에 참여하고 다양한 혜택을 받아보세요.

", null, null, null, true, "하정현", null), + new Promotion(null, "한정 판매 프로모션", "프로모션

한정 판매 차량에 대한 특별 할인 혜택을 안내드립니다.

", null, null, null, true, "봉서우", null), + new Promotion(null, "고객 감사 특별 할인", "프로모션

고객님을 위한 특별 할인을 제공합니다.

", null, null, null, true, "한정환", null), + new Promotion(null, "신규 고객 환영 이벤트", "프로모션

신규 고객님을 위한 환영 이벤트를 진행합니다.

", null, null, null, true, "현하린", null), + new Promotion(null, "장기 고객 감사 행사", "프로모션

오랜 기간 함께해주신 고객님들을 위한 감사 행사를 준비했습니다.

", null, null, null, true, "규예서", null), + new Promotion(null, "신규 서비스 체험단 모집", "프로모션

신규 서비스 체험단을 모집합니다. 체험 후기를 공유해 주세요.

", null, null, null, true, "천하은", null), + new Promotion(null, "계절별 프로모션 가이드", "프로모션

각 계절에 맞는 프로모션 가이드를 확인해 보세요.

", null, null, null, true, "한정환", null), + new Promotion(null, "고객만족도 조사 이벤트", "프로모션

고객 만족도 조사 참여 시 특별한 혜택을 드립니다.

", null, null, null, true, "양민지", null) + ); + for (Promotion promotion : promotions) { + promotionRepository.save(promotion); + } + log.info("Promotion 데이터가 초기화되었습니다."); + } else { + log.info("Promotion 테이블에 이미 데이터가 존재합니다."); + } + + if (problemRepository.count() == 0) { + List problems = Arrays.asList( + new Problem(null, "차량 발화 문제", "문제사항

소렌토 차량 관련 발화 문제가 빈번하게 발생하고 있습니다.

리콜 조치 고려 부탁 드립니다. ", null, null, null, true, null,"율도현","PRO_000000001","PROGRESS", null), + new Problem(null, "제품 교체 관련 문제", "문제사항

제 담당 고객님이 스팅어 부품 교체 관련 정보 제공 부탁 드립니다.

", null, null, null, true, "율도현","봉서우","PRO_000000020","PROGRESS", null), + new Problem(null, "브레이크 소음 문제", "문제사항

K5 차량 브레이크에서 지속적인 소음이 발생하고 있습니다.

원인 확인 및 조치 부탁드립니다.", null, null, null, true, "안수환","남유나", "PRO_000000002", "PROGRESS", null), + new Problem(null, "엔진 과열 문제", "문제사항

스포티지 차량에서 엔진 과열 문제가 보고되었습니다.

긴급 점검 요청드립니다.", null, null, null, true, "차시현", "전은호", "PRO_000000006", "PROGRESS", null), + new Problem(null, "타이어 마모 문제", "문제사항

타이어가 비정상적으로 빠르게 마모되고 있습니다.

교체 및 점검 필요합니다.", null, null, null, true, "운예린","고윤정", "PRO_0000000012", "PROGRESS", null), + new Problem(null, "에어컨 작동 불량", "문제사항

스팅어 차량의 에어컨이 작동하지 않습니다.

점검 요청드립니다.", null, null, null, true, "공유영","이재용", "PRO_000000009", "PROGRESS", null), + new Problem(null, "핸들 떨림 문제", "문제사항

핸들 떨림 현상이 지속적으로 발생하고 있습니다.

점검 필요합니다.", null, null, null, true, "전예슬", "봉채영", "PRO_000000006", "PROGRESS", null), + new Problem(null, "오일 누출 문제", "문제사항

엔진 오일이 누출되고 있습니다.

긴급 점검 바랍니다.", null, null, null, true, "천하은","이재용", "PRO_000000007", "PROGRESS", null), + new Problem(null, "소음 발생 문제", "문제사항

차량 주행 중 소음이 심하게 발생하고 있습니다.

원인 분석 요청드립니다.", null, null, null, true, "이시우","목다희", "PRO_000000008", "PROGRESS", null), + new Problem(null, "연비 저하 문제", "문제사항

차량 연비가 급격히 저하되었습니다.

점검 요청드립니다.", null, null, null, true,"성지윤", "유하율", "PRO_000000009", "PROGRESS", null), + new Problem(null, "내비게이션 오류", "문제사항

내비게이션이 정확한 경로를 안내하지 않습니다.

업데이트 필요합니다.", null, null, null, true, "서채우","채예슬", "PRO_000000010", "PROGRESS", null), + new Problem(null, "리어램프 불량", "문제사항

리어램프가 정상적으로 작동하지 않습니다.

교체 요청드립니다.", null, null, null, true, "용하은","곽민아", "PRO_00000004", "PROGRESS", null), + new Problem(null, "디젤 연료 문제", "문제사항

디젤 차량 연료 공급이 원활하지 않습니다.

점검 요청드립니다.", null, null, null, true, "하정현","고윤정", "PRO_00000003", "PROGRESS", null), + new Problem(null, "도어 잠김 문제", "문제사항

운전석 도어 잠금이 해제되지 않습니다.

점검 바랍니다.", null, null, null, true,"문지완", "은주영", "PRO_000000013", "PROGRESS", null), + new Problem(null, "엔진 소음 문제", "문제사항

엔진에서 비정상적인 소음이 발생합니다.

점검 요청드립니다.", null, null, null, true, "문지완","서지윤", "PRO_000000014", "PROGRESS", null), + new Problem(null, "서스펜션 문제", "문제사항

서스펜션이 제 기능을 하지 못하고 있습니다.

수리 요청드립니다.", null, null, null, true,"염승환", "봉소라", "PRO_000000015", "PROGRESS", null), + new Problem(null, "라디에이터 누수 문제", "문제사항

라디에이터에서 누수가 발견되었습니다.

점검 바랍니다.", null, null, null, true, "운은환","황수아", "PRO_000000016", "PROGRESS", null), + new Problem(null, "엔진 경고등 점등", "문제사항

엔진 경고등이 점등된 상태입니다.

점검 필요합니다.", null, null, null, true, "황수아","서지윤", "PRO_000000017", "PROGRESS", null), + new Problem(null, "연료 소비 과다", "문제사항

연료 소비가 비정상적으로 많습니다.

점검 바랍니다.", null, null, null, true, "익도환","남유나", "PRO_000000018", "PROGRESS", null), + new Problem(null, "차량 떨림 문제", "문제사항

차량 주행 중 심한 떨림이 발생합니다.

점검 요청드립니다.", null, null, null, true,"도유정", "채예슬", "PRO_000000019", "PROGRESS", null), + new Problem(null, "내비게이션 오류", "문제사항

내비게이션이 정확한 경로를 안내하지 않습니다.

업데이트 필요합니다.", null, null, null, true, "도유정","채예슬", "PRO_000000010", "PROGRESS", null), + new Problem(null, "오일 누출 문제", "문제사항

엔진 오일이 누출되고 있습니다.

긴급 점검 바랍니다.", null, null, null, true, "도유정","이재용", "PRO_000000007", "PROGRESS", null) + ); + for (Problem problem : problems) { + problemRepository.save(problem); + } + log.info("Problem 데이터가 초기화되었습니다."); + } else { + log.info("Problem 테이블에 이미 데이터가 존재합니다."); + } + + + // 우리 계정1 + createOrUpdateMember( + "M000000000", + "pass", + "신하늘", + "god1@stanl.com", + 0, + "MALE", + "000000-0000000", + "010-0000-0000", + "서울 동작구 보라매로 87", + "시스템 관리자", + "Associate", + "REGULAR", + "FULFILLED", + "한국은행", + "000-0000-000000-00000", + "CEN_000000000", + "ORG_000000000", + "GOD", + loadImage("god.png") + ); + + // 우리 계정2 + createOrUpdateMember( + "M999999999", + "pass", + "신하늘", + "god2@stanl.com", + 0, + "MALE", + "000000-0000000", + "010-0000-0000", + "서울 동작구 보라매로 87", + "시스템 관리자", + "Associate", + "REGULAR", + "FULFILLED", + "한국은행", + "000-0000-000000-00000", + "CEN_000000000", + "ORG_000000000", + "GOD", + loadImage("god.png") + ); + + // 심사위원 1 계정 + createOrUpdateMember( + "M000000001", + "pass1", + "고윤정", + "Yunn29@stanl.com", + 31, + "MALE", + "951109-1000000", + "010-0000-0000", + "서울 동작구 보라매로 87", + "INTERN", + "Associate", + "TEMPORARY", + "FULFILLED", + "한국은행", + "000-0000-000000-00000", + "CEN_000000001", + "ORG_000000001", + "EMPLOYEE", + loadImage("god.png") + ); + + // 심사위원 2 계정 + createOrUpdateMember( + "M000000002", + "pass2", + "차은우", + "enUU22@stanl.com", + 43, + "MALE", + "871204-1000000", + "010-1234-2321", + "서울 동작구 보라매로 87", + "ASSISTANT", + "Associate", + "TEMPORARY", + "FULFILLED", + "한국은행", + "000-0200-000100-00030", + "CEN_000000001", + "ORG_000000001", + "ADMIN", + loadImage("god.png") + ); + + // 심사위원 3 계정 + createOrUpdateMember( + "M000000003", + "pass3", + "윤세연", + "sey1@stanl.com", + 54, + "FEMALE", + "800912-2000000", + "010-3231-4573", + "서울 동작구 보라매로 87", + "EXECUTIVE", + "Master", + "TEMPORARY", + "FULFILLED", + "한국은행", + "000-0000-000000-00000", + "CEN_000000001", + "ORG_000000001", + "DIRECTOR", + loadImage("god.png") + ); + + + createOrUpdateMember( + "M000000004", + "pass4", + "이재용", + "gdragon11@stanl.com", + 77, + "MALE", + "760122-1000000", + "010-5830-2842", + "서울 동작구 보라매로 87", + "CEO", + "Doctoral", + "TEMPORARY", + "FULFILLED", + "한국은행", + "000-0000-000000-00000", + "CEN_000000001", + "ORG_000000001", + "GOD", + loadImage("default.png") + ); + + + Random random = new Random(); + String[] positions = {"INTERN", "STAFF", "ASSISTANT", "MANAGER", "SENIOR", "EXECUTIVE", "DIRECTOR", "CEO"}; + String[] grade = {"High School", "Associate", "Bachelor", "Master", "Doctoral"}; + String[] jobTypes = {"REGULAR", "TEMPORARY"}; + String[] militaryStatus = {"FULFILLED", "EXEMPTION", "UNFULFILLED"}; + String[] genders = {"MALE", "FEMALE"}; + String[] roles = {"EMPLOYEE", "ADMIN", "DIRECTOR"}; + String[] lastNames = { + "김", "이", "박", "최", "정", "강", "조", "유", "윤", "장", "임", "기", "방", "하", "도", "한", "손", "송", "오", "조", "서", "배", "홍", "류", "신", "권", "곽", + "황", "안", "전", "문", "탁", "모", "남", "우", "차", "백", "표", "양", "변", "설", "염", "석", "심", "함", "노", "채", "진", "민", "엄", "원", "천", "방", "공", + "현", "나", "제", "고", "성", "라", "마", "탁", "하", "사", "여", "용", "호", "범", "소", "운", "계", "도", "서", + "주", "두", "해", "율", "민", "익", "선", "학", "판", "예", "형", "양", "천", "어", "현", "종", "운", "필", "탁", "중", + "후", "은", "치", "간", "일", "규", "화", "순", "평", "다", "목", "택", "봉" + }; + + String[] firstNames = { + "민수", "지훈", "서연", "예준", "하은", "도현", "지원", "유진", "현우", "수아", "수빈", "주희", "지아", "준호", "혜린", "지민", "은지", "시우", "다영", "태현", "연우", + "가은", "민준", "서준", "예진", "윤서", "지우", "승현", "시현", "다현", "태민", "소윤", "해준", "유정", "현서", "윤하", "수현", "혜수", "가연", "지호", "정우", + "다빈", "채영", "우진", "민아", "성민", "윤호", "지훈", "하린", "승우", "지윤", "소현", "예슬", "아린", "주원", "희준", "은채", "서아", "주영", "도윤", "정민", + "하윤", "나연", "규현", "수영", "시윤", "도경", "서윤", "지율", "혜진", "민혁", "태훈", "유나", "재민", "세연", "은서", "재현", "다윤", "연서", "예서", "하율", + "준영", "현진", "승민", "재윤", "희연", "시영", "수진", "서우", "태연", "준서", "수영", "나영", "다희", "채린", "윤채", "다훈", "민지", "현민", "선우", "하경", + "지안", "수빈", "은호", "아윤", "재희", "태영", "정현", "예원", "은혜", "소라", "다은", "우빈", "예린", "서희", "유빈", "하진", "선호", "은우", "예빈", "혜연", + "지혁", "다훈", "채희", "지완", "민호", "다온", "수경", "은빈", "채원", "하연", "정빈", "나희", "소희", "시후", "태후", "민후", "서영", "정윤", "채윤", "도은", + "가빈", "나영", "현우", "세빈", "도현", "혜빈", "준혁", "은성", "다솔", "유영", "태솔", "희영", "채우", "소연", "나윤", "수환", "정환", "승환", "도환", "은환" + }; + + String[] addresses = { + "서울특별시 강남구 테헤란로", + "부산광역시 해운대구 센텀중앙로", + "대구광역시 수성구 범어로", + "인천광역시 남동구 미래로", + "광주광역시 서구 상무대로", + "대전광역시 유성구 대덕대로", + "울산광역시 남구 번영로", + "경기도 성남시 분당구 판교로", + "강원도 춘천시 중앙로", + "충청북도 청주시 상당구 상당로" + }; + + + // 회원 및 역할 생성 로직 + for (int i = 5; i <= 102; i++) { + int centerId = random.nextInt(10) + 1; + int orgId = random.nextInt(10) + 5; + String sex = genders[(i+1) % 2]; + String name = lastNames[random.nextInt(lastNames.length)] + firstNames[random.nextInt(firstNames.length)]; + String address = addresses[random.nextInt(addresses.length)]; + createOrUpdateMember( + String.format("M%09d", i), + "pass" + i, + name, + "user" + i + "@example.com", + 20 + random.nextInt(30), // Random age between 20-49 + sex, + String.format("123456-1%06d", 100000 + random.nextInt(900000)), + String.format("010-1234-%04d", i), + address, + positions[random.nextInt(positions.length)], // Random position + grade[random.nextInt(grade.length)], + jobTypes[random.nextInt(jobTypes.length)], // Random job type + militaryStatus[random.nextInt(militaryStatus.length)], // Random military status + "Bank" + centerId, + "123456789" + i, + String.format("CEN_%09d", centerId), + String.format("ORG_%09d", orgId), + roles[random.nextInt(roles.length)], // Random role + loadImage("default.png") + ); + } + + + + + // 영업 관련 경력 + String[] salesCareers = { + "영업 대표", + "지역 영업 관리자", + "계정 관리자", + "영업 컨설턴트", + "사업 개발 관리자", + "영업 코디네이터", + "영업 지역 관리자", + "영업 엔지니어", + "내부 영업 대표", + "주요 계정 관리자", + "전국 영업 관리자", + "영업 이사", + "소매 영업 사원", + "제약 영업 대표", + "자동차 영업 컨설턴트", + "영업 분석가", + "현장 영업 대표", + "선임 영업 임원", + "채널 영업 관리자", + "영업 운영 관리자", + "기술 영업 대표", + "영업 교육 담당자", + "디지털 영업 전문가", + "광고 영업 임원", + "미디어 영업 컨설턴트", + "B2B 영업 전문가", + "전자상거래 영업 관리자", + "기업 영업 관리자", + "리드 생성 전문가", + "수출 영업 관리자", + "프랜차이즈 영업 컨설턴트", + "보험 영업 대리인", + "금융 영업 상담사", + "소프트웨어 영업 대표", + "SaaS 영업 관리자", + "제품 영업 전문가", + "영업 계정 관리자", + "영업 개발 대표", + "부동산 영업 대리인", + "건설 영업 임원", + "헬스케어 영업 컨설턴트", + "산업 영업 대표", + "도매 영업 관리자", + "영업 관계 관리자", + "클라이언트 참여 전문가", + "영업 협상가", + "영업 리드 코디네이터", + "럭셔리 상품 영업 상담사", + "호스피탈리티 영업 임원", + "영업 지역 계정 임원" + }; + + // Career(경력) 저장 + if(careerRepository.count() == 0){ + for (int i = 1; i <= 100; i++) { + String memberId = String.format("MEM_%09d", i); + for (int j = 0; j < 4; j++) { + Career newCareer = new Career(); + newCareer.setEmplDate(getRandomEmploymentDate()); + newCareer.setResignDate(getRandomResignationDate(LocalDate.parse(newCareer.getEmplDate(), DateTimeFormatter.ofPattern("yyyy-MM-dd")))); + newCareer.setName(salesCareers[random.nextInt(salesCareers.length)]); + newCareer.setMemberId(memberId); + careerRepository.save(newCareer); + } + } + }else{ + log.info("Career(경력) 테이블에 데이터가 존재합니다."); + } + + String[][] salesCertifications = { + {"영업관리사", "한국영업협회"}, + {"마케팅관리사", "대한마케팅협회"}, + {"국제판매전문가", "국제세일즈인증원"}, + {"고객관계관리(CRM) 자격증", "CRM연구소"}, + {"광고판매전문가", "한국광고협회"}, + {"유통관리사", "대한상공회의소"}, + {"물류관리사", "한국물류협회"}, + {"세일즈포스 인증 전문가", "Salesforce"}, + {"디지털마케팅 전문가 자격증", "한국디지털마케팅협회"}, + {"상담판매 자격증", "한국상담판매협회"}, + {"고객서비스전문가", "한국CS관리협회"}, + {"리테일 영업 자격증", "대한리테일협회"}, + {"보험판매 자격증", "대한보험협회"}, + {"제약영업 자격증", "한국제약협회"}, + {"B2B 영업전문가", "한국B2B영업협회"}, + {"자동차판매전문가", "한국자동차판매연합회"}, + {"부동산판매 자격증", "대한부동산협회"}, + {"프랜차이즈 관리 자격증", "프랜차이즈산업협회"}, + {"광고 및 프로모션 자격증", "한국프로모션협회"}, + {"국제 무역영업 자격증", "한국무역협회"}, + {"비즈니스 협상 자격증", "한국협상전문가협회"}, + {"제품관리 전문가", "한국제품관리연구소"}, + {"판매심리학 자격증", "한국심리학회"}, + {"금융상품판매 자격증", "한국금융협회"}, + {"의료기기 영업 자격증", "대한의료기기산업협회"}, + {"공급망 관리 자격증", "한국공급망관리협회"}, + {"소비자행동 분석 자격증", "대한소비자행동분석협회"}, + {"브랜드 관리 자격증", "한국브랜드관리협회"}, + {"전자상거래 마케팅 자격증", "대한전자상거래협회"}, + {"기업 대기업 영업 자격증", "대한기업영업협회"}, + {"퍼포먼스 마케팅 자격증", "한국퍼포먼스마케팅협회"}, + {"클라이언트 관계 관리 자격증", "한국클라이언트관리협회"}, + {"RFP(제안서 작성) 전문가", "한국RFP협회"}, + {"SaaS 판매 전문가", "한국SaaS협회"}, + {"클라우드 솔루션 판매 자격증", "한국클라우드산업협회"}, + {"헬스케어 판매 전문가", "대한헬스케어산업협회"}, + {"텔레마케팅 자격증", "한국텔레마케팅협회"}, + {"제품 발표 및 데모 자격증", "대한제품발표협회"}, + {"전시회 및 이벤트 영업 자격증", "한국전시산업협회"}, + {"기술 영업 전문가", "대한기술영업협회"}, + {"대리점 관리 자격증", "한국대리점관리협회"}, + {"국제 시장 개발 자격증", "국제시장개발연구소"}, + {"영업 전략 기획 자격증", "한국영업전략기획협회"}, + {"신사업 개발 자격증", "대한신사업개발협회"}, + {"리더십 및 팀 관리 자격증", "한국리더십센터"}, + {"상담 영업 전문가", "한국상담영업연구소"}, + {"호스피탈리티 및 관광 영업 자격증", "한국관광협회"}, + {"에너지 산업 영업 전문가", "대한에너지산업협회"}, + {"IT 영업 전문가", "한국IT영업협회"}, + {"소셜 미디어 마케팅 자격증", "대한소셜미디어마케팅협회"} + }; + + // Certification(자격증) 저장 + if(certificationRepository.count() == 0){ + for (int i = 1; i <= 100; i++) { + String memberId = String.format("MEM_%09d", i); + for (int j = 0; j < 4; j++) { + Certification newCertification = new Certification(); + newCertification.setAcquisitionDate(getRandomEmploymentDate()); + int n = random.nextInt(salesCareers.length); + newCertification.setAgency(salesCertifications[n][1]); + newCertification.setName(salesCertifications[n][0]); + newCertification.setScore(String.valueOf(random.nextInt(101))); + newCertification.setMemberId(memberId); + certificationRepository.save(newCertification); + } + } + } else { + log.info("Certification(자격증) 테이블에 데이터가 존재합니다."); + } + + // 전공 + String[] salesMajors = { + "마케팅학", + "광고홍보학", + "경영학", + "국제경영학", + "판매관리학", + "고객관계관리학", + "유통물류학", + "상업교육", + "비즈니스 커뮤니케이션", + "디지털 마케팅", + "국제무역학", + "브랜드 관리학", + "소비자 행동 분석학", + "비즈니스 전략학", + "리테일 매니지먼트", + "판매 및 협상학", + "프랜차이즈 관리학", + "제품 관리학", + "광고 및 프로모션학", + "세일즈 엔지니어링", + "부동산 판매학", + "서비스 마케팅", + "헬스케어 영업학", + "전자상거래학", + "금융 영업학", + "기술 영업학", + "공급망 관리학", + "퍼포먼스 마케팅", + "B2B 영업학", + "에너지 산업 영업학", + "커뮤니케이션학", + "경제학", + "사회학", + "심리학", + "경영정보학", + "홍보학", + "미디어학", + "IT 비즈니스", + "국제관계학", + "고객 서비스 관리학", + "인적 자원 관리학", + "행동 과학", + "비즈니스 분석학", + "창업학", + "리더십 학", + "데이터 분석학", + "디자인 씽킹", + "공공 관계학", + "행동 경제학", + "프로젝트 관리학" + }; + + // 대학 + String[] universities = { + "서울대학교", "연세대학교", "고려대학교", "성균관대학교", "한양대학교", + "서강대학교", "중앙대학교", "경희대학교", "이화여자대학교", "한국외국어대학교", + "건국대학교", "동국대학교", "서울시립대학교", "숙명여자대학교", "숭실대학교", + "광운대학교", "명지대학교", "국민대학교", "세종대학교", "가톨릭대학교", + "홍익대학교", "단국대학교", "아주대학교", "인하대학교", "한국항공대학교", + "경기대학교", "가천대학교", "서울과학기술대학교", "한성대학교", "서울여자대학교", + "백석대학교", "상명대학교", "한남대학교", "동아대학교", "부산대학교", + "울산대학교", "부경대학교", "조선대학교", "전남대학교", "전북대학교", + "제주대학교", "포항공과대학교", "한동대학교", "울산과학기술원", "경북대학교", + "계명대학교", "영남대학교", "대구대학교", "대구가톨릭대학교", "안동대학교", + "청주대학교", "충북대학교", "충남대학교", "한서대학교", "공주대학교", + "강원대학교", "춘천교육대학교", "한국교통대학교", "목포대학교", "목포해양대학교", + "순천대학교", "순천향대학교", "원광대학교", "남서울대학교", "상지대학교", + "강릉원주대학교", "한국해양대학교", "창원대학교", "인제대학교", "동의대학교", + "부산외국어대학교", "신라대학교", "경남대학교", "창신대학교", "고신대학교", + "광주대학교", "호남대학교", "동신대학교", "전주대학교", "배재대학교", + "목원대학교", "백석문화대학교", "한밭대학교", "세명대학교", "중부대학교", + "한국산업기술대학교", "경운대학교", "대진대학교", "한라대학교", "제주국제대학교", + "협성대학교", "평택대학교", "가야대학교", "강남대학교", "건양대학교", + "경동대학교", "경민대학교", "경북과학대학교", "경인여자대학교", "경인교육대학교" + }; + + // Education(학력) 저장 + if(educationRepository.count()==0){ + for (int i = 1; i < 100; i++) { + String memberId = String.format("MEM_%09d", i); + Education newEducation = new Education(); + newEducation.setEntranceDate(getRandomEmploymentDate()); + newEducation.setGraduationDate(getRandomResignationDate(LocalDate.parse(newEducation.getEntranceDate(), DateTimeFormatter.ofPattern("yyyy-MM-dd")))); + newEducation.setMajor(salesMajors[random.nextInt(salesMajors.length)]); + newEducation.setName(universities[random.nextInt(universities.length)]); + newEducation.setScore(String.format("%.2f", 2.0 + (Math.random() * (4.5 - 2.0)))); + newEducation.setMemId(memberId); + educationRepository.save(newEducation); + } + } else{ + log.info("Education(학력) 테이블에 이미 값이 존재합니다."); + } + + // 관계 + String[] familyRelations = { + "아버지", "어머니", "형", "누나", "남동생", + "여동생", "할아버지", "할머니", "외할아버지", "외할머니", + "삼촌", "이모", "고모", "작은아버지", "작은어머니", + "큰아버지", "큰어머니", "사촌형", "사촌누나", "사촌동생", + "조카", "손자", "손녀", "시아버지", "시어머니", + "장인", "장모", "매형", "매제", "형부", + "올케", "처남", "처형", "형수", "제수", + "아들", "딸", "손자", "손녀" + }; + + + LocalDate randomBirthDate = LocalDate.of( + ThreadLocalRandom.current().nextInt(1980, 2024), // 1980년부터 2023년까지의 무작위 연도 + ThreadLocalRandom.current().nextInt(1, 13), // 1월부터 12월까지의 무작위 월 + ThreadLocalRandom.current().nextInt(1, 29) // 1일부터 28일까지의 무작위 일 (안전하게 28일까지 설정) + ); + + String randomPhone = String.format("010-%04d-%04d", + (int) (Math.random() * 10000), + (int) (Math.random() * 10000) + ); + + // Family(가족) 저장 + if(familyRepository.count() == 0){ + for (int i = 1; i <= 100; i++) { + String memberId = String.format("MEM_%09d", i); + for (int j = 0; j < 4; j++) { + // 주민등록번호 생성 + String birthDateStr = randomBirthDate.format(DateTimeFormatter.ofPattern("yyMMdd")); // YYMMDD 형식 + String birthDateStr2 = randomBirthDate.format(DateTimeFormatter.ofPattern("yyyy년 MM월 dd일")); // YYMMDD 형식 + String genderDigit = Math.random() < 0.5 ? "1" : "2"; // 성별은 1(남) 또는 2(여) + String randomNumbers = String.format("%04d", (int) (Math.random() * 10000)); // 임의의 4자리 번호 + String checkDigit = String.format("%02d", (int) (Math.random() * 10)); // 검증용 마지막 자리는 0~9 + + // 주민등록번호 만들기 + String identNo = birthDateStr + "-" + genderDigit + randomNumbers + checkDigit; + + Family newFamily = new Family(); + newFamily.setName(lastNames[random.nextInt(lastNames.length)] + firstNames[random.nextInt(firstNames.length)]); + newFamily.setRelation(familyRelations[random.nextInt(familyRelations.length)]); + newFamily.setSex(Math.random() < 0.5 ? "MALE" : "FEMALE"); + newFamily.setBirth(birthDateStr2); + newFamily.setPhone(randomPhone); + newFamily.setDie(Math.random() < 0.1 ? true : false); + newFamily.setDisability(Math.random() < 0.1 ? true : false); + newFamily.setIdentNo(identNo); + newFamily.setMemId(memberId); + + familyRepository.save(newFamily); + } + } + } else{ + log.info("family(가족) 테이블에 이미 데이터가 존재합니다."); + } + + + // 부서 저장 + if(organizationRepository.count() == 0){ + Organization org1 = new Organization("ORG_000000001", "서울 지사", null); + organizationRepository.save(org1); + Organization org2 = new Organization("ORG_000000002", "부산 지사", null); + organizationRepository.save(org2); + Organization org3 = new Organization("ORG_000000003", "인천 지사", null); + organizationRepository.save(org3); + Organization org4 = new Organization("ORG_000000004", "대전 지사", null); + organizationRepository.save(org4); + Organization org5 = new Organization("ORG_000000005", "영업부(서울 1팀)", "ORG_000000001"); + organizationRepository.save(org5); + Organization org6 = new Organization("ORG_000000006", "영업부(서울 2팀)", "ORG_000000001"); + organizationRepository.save(org6); + Organization org7 = new Organization("ORG_000000007", "영업부(부산 1팀)", "ORG_000000002"); + organizationRepository.save(org7); + Organization org8 = new Organization("ORG_000000008", "영업부(부산 2팀)", "ORG_000000002"); + organizationRepository.save(org8); + Organization org9 = new Organization("ORG_000000009", "영업부(인천 1팀)", "ORG_000000003"); + organizationRepository.save(org9); + Organization org10 = new Organization("ORG_000000010", "영업부(인천 2팀)", "ORG_000000003"); + organizationRepository.save(org10); + Organization org11 = new Organization("ORG_000000011", "영업부(대전 1팀)", "ORG_000000004"); + organizationRepository.save(org11); + Organization org12 = new Organization("ORG_000000012", "영업부(대전 2팀)", "ORG_000000004"); + organizationRepository.save(org12); + Organization org13 = new Organization("ORG_000000013", "마케팅부", "ORG_000000001"); + organizationRepository.save(org13); + Organization org14 = new Organization("ORG_000000014", "기술부", "ORG_000000001"); + organizationRepository.save(org14); + } else{ + log.info("organization(조직도) 테이블에 이미 데이터가 존재합니다."); + } + + + // 고객 인당 100명 씩 + if(customerRepository.count() == 0){ + for (int i = 1; i <= 4; i++) { + String memberId = String.format("MEM_%09d", i); + for (int j = 0; j < 100; j++) { + String sex = genders[i % 2]; + String name = lastNames[random.nextInt(lastNames.length)] + firstNames[random.nextInt(firstNames.length)]; + int randomAge = ThreadLocalRandom.current().nextInt(20, 71); + String randomName = "user" + ThreadLocalRandom.current().nextInt(1000, 10000); + String randomEmail = randomName + "@example.com"; + + CustomerRegistDTO newCustomer = new CustomerRegistDTO(); + newCustomer.setName(name); + newCustomer.setAge(randomAge); + newCustomer.setPhone(randomPhone); + newCustomer.setEmergePhone(randomPhone); + newCustomer.setEmail(randomEmail); + newCustomer.setSex(sex); + newCustomer.setMemberId(memberId); + customerCommandService.registerCustomerInfo(newCustomer); + } + } + } else { + log.info("customer(고객) 테이블에 이미 데이터가 존재합니다."); + } + + + String[][] carDealershipsInfo = { + {"서울 중앙 영업점", "서울특별시 중구 세종대로 123"}, + {"부산 서면 영업점", "부산광역시 부산진구 중앙대로 456"}, + {"인천 남동구 영업점", "인천광역시 남동구 인주대로 789"}, + {"대전 둔산 영업점", "대전광역시 서구 둔산로 101"}, + {"광주 광산구 영업점", "광주광역시 광산구 상무대로 202"}, + {"대구 동구 영업점", "대구광역시 동구 아양로 303"}, + {"울산 중구 영업점", "울산광역시 중구 태화로 404"}, + {"수원 영통구 영업점", "경기도 수원시 영통구 매탄로 505"}, + {"성남 분당 영업점", "경기도 성남시 분당구 정자일로 606"}, + {"제주 서귀포 영업점", "제주특별자치도 서귀포시 태평로 707"} + }; + + // Center 매장 등록 + if(centerRepository.count() == 0){ + CenterRegistDTO newCenter1 = new CenterRegistDTO("서울 중앙 영업점", "서울특별시 중구 세종대로 123", "02-123-4567", 25, "09:00 - 18:00", null); + centerCommandService.registCenter(newCenter1, loadImage("default.png")); + CenterRegistDTO newCenter2 = new CenterRegistDTO("부산 서면 영업점", "부산광역시 부산진구 중앙대로 456", "051-123-4567", 20, "09:00 - 18:00", null); + centerCommandService.registCenter(newCenter2, loadImage("default.png")); + CenterRegistDTO newCenter3 = new CenterRegistDTO("인천 남동구 영업점", "인천광역시 남동구 인주대로 789", "032-123-4567", 18, "09:00 - 18:00", null); + centerCommandService.registCenter(newCenter3, loadImage("default.png")); + CenterRegistDTO newCenter4 = new CenterRegistDTO("대전 둔산 영업점", "대전광역시 서구 둔산로 101", "042-123-4567", 15, "09:00 - 18:00", null); + centerCommandService.registCenter(newCenter4, loadImage("default.png")); + CenterRegistDTO newCenter5 = new CenterRegistDTO("광주 광산구 영업점", "광주광역시 광산구 상무대로 202", "062-123-4567", 17, "09:00 - 18:00", null); + centerCommandService.registCenter(newCenter5, loadImage("default.png")); + CenterRegistDTO newCenter6 = new CenterRegistDTO("대구 동구 영업점", "대구광역시 동구 아양로 303", "053-123-4567", 22, "09:00 - 18:00", null); + centerCommandService.registCenter(newCenter6, loadImage("default.png")); + CenterRegistDTO newCenter7 = new CenterRegistDTO("울산 중구 영업점", "울산광역시 중구 태화로 404", "052-123-4567", 13, "09:00 - 18:00", null); + centerCommandService.registCenter(newCenter7, loadImage("default.png")); + CenterRegistDTO newCenter8 = new CenterRegistDTO("수원 영통구 영업점", "경기도 수원시 영통구 매탄로 505", "031-123-4567", 19, "09:00 - 18:00", null); + centerCommandService.registCenter(newCenter8, loadImage("default.png")); + CenterRegistDTO newCenter9 = new CenterRegistDTO("성남 분당 영업점", "경기도 성남시 분당구 정자일로 606", "031-234-5678", 21, "09:00 - 18:00", null); + centerCommandService.registCenter(newCenter9, loadImage("default.png")); + CenterRegistDTO newCenter10 = new CenterRegistDTO("제주 서귀포 영업점", "제주특별자치도 서귀포시 태평로 707", "064-123-4567", 12, "09:00 - 18:00", null); + centerCommandService.registCenter(newCenter10, loadImage("default.png")); + } else { + log.info("center(매장) 테이블에 데이터가 이미 존재합니다."); + } + + + // Product 제품 등록 + if(productRepository.count() == 0){ + Product newProduct1 = new Product("KIA15-S001", 20000000, "기아 소렌토 2015", 10); + productRepository.save(newProduct1); + + Product newProduct2 = new Product("KIA15-S002", 18000000, "기아 스포티지 2015", 8); + productRepository.save(newProduct2); + + Product newProduct3 = new Product("KIA16-S003", 22000000, "기아 소렌토 2016", 12); + productRepository.save(newProduct3); + + Product newProduct4 = new Product("KIA16-S004", 19500000, "기아 옵티마 2016", 5); + productRepository.save(newProduct4); + + Product newProduct5 = new Product("KIA17-S005", 25000000, "기아 스팅어 2017", 7); + productRepository.save(newProduct5); + + Product newProduct6 = new Product("KIA17-S006", 21000000, "기아 스포티지 2017", 9); + productRepository.save(newProduct6); + + Product newProduct7 = new Product("KIA18-S007", 23000000, "기아 소렌토 2018", 6); + productRepository.save(newProduct7); + + Product newProduct8 = new Product("KIA18-S008", 20000000, "기아 리오 2018", 11); + productRepository.save(newProduct8); + + Product newProduct9 = new Product("KIA19-S009", 27000000, "기아 스팅어 2019", 4); + productRepository.save(newProduct9); + + Product newProduct10 = new Product("KIA19-S010", 21500000, "기아 포르테 2019", 3); + productRepository.save(newProduct10); + + Product newProduct11 = new Product("KIA20-S011", 29000000, "기아 텔루라이드 2020", 10); + productRepository.save(newProduct11); + + Product newProduct12 = new Product("KIA20-S012", 25000000, "기아 옵티마 2020", 7); + productRepository.save(newProduct12); + + Product newProduct13 = new Product("KIA21-S013", 30000000, "기아 소렌토 2021", 12); + productRepository.save(newProduct13); + + Product newProduct14 = new Product("KIA21-S014", 27000000, "기아 카니발 2021", 5); + productRepository.save(newProduct14); + + Product newProduct15 = new Product("KIA22-S015", 32000000, "기아 EV6 2022", 6); + productRepository.save(newProduct15); + + Product newProduct16 = new Product("KIA22-S016", 28000000, "기아 셀토스 2022", 8); + productRepository.save(newProduct16); + + Product newProduct17 = new Product("KIA23-S017", 33000000, "기아 소렌토 2023", 9); + productRepository.save(newProduct17); + + Product newProduct18 = new Product("KIA23-S018", 29000000, "기아 스포티지 2023", 4); + productRepository.save(newProduct18); + + Product newProduct19 = new Product("KIA24-S019", 35000000, "기아 EV9 2024", 3); + productRepository.save(newProduct19); + + Product newProduct20 = new Product("KIA24-S020", 31000000, "기아 스팅어 2024", 5); + productRepository.save(newProduct20); + + Product newProduct21 = new Product("KIA15-S021", 17000000, "기아 리오 2015", 6); + productRepository.save(newProduct21); + + Product newProduct22 = new Product("KIA16-S022", 21000000, "기아 포르테 2016", 4); + productRepository.save(newProduct22); + + Product newProduct23 = new Product("KIA17-S023", 24000000, "기아 옵티마 2017", 8); + productRepository.save(newProduct23); + + Product newProduct24 = new Product("KIA18-S024", 22500000, "기아 소울 2018", 7); + productRepository.save(newProduct24); + + Product newProduct25 = new Product("KIA19-S025", 28000000, "기아 텔루라이드 2019", 5); + productRepository.save(newProduct25); + + Product newProduct26 = new Product("KIA20-S026", 26000000, "기아 니로 2020", 9); + productRepository.save(newProduct26); + + Product newProduct27 = new Product("KIA21-S027", 29500000, "기아 카니발 2021", 3); + productRepository.save(newProduct27); + + Product newProduct28 = new Product("KIA22-S028", 31000000, "기아 소렌토 2022", 10); + productRepository.save(newProduct28); + + Product newProduct29 = new Product("KIA23-S029", 34000000, "기아 EV6 2023", 6); + productRepository.save(newProduct29); + + Product newProduct30 = new Product("KIA24-S030", 32000000, "기아 셀토스 2024", 4); + productRepository.save(newProduct30); + + Product newProduct31 = new Product("KIA15-S031", 18500000, "기아 소울 2015", 7); + productRepository.save(newProduct31); + + Product newProduct32 = new Product("KIA16-S032", 22000000, "기아 소렌토 2016", 6); + productRepository.save(newProduct32); + + Product newProduct33 = new Product("KIA17-S033", 25000000, "기아 포르테 2017", 5); + productRepository.save(newProduct33); + + Product newProduct34 = new Product("KIA18-S034", 23000000, "기아 리오 2018", 8); + productRepository.save(newProduct34); + + Product newProduct35 = new Product("KIA19-S035", 27000000, "기아 스포티지 2019", 4); + productRepository.save(newProduct35); + + Product newProduct36 = new Product("KIA20-S036", 29500000, "기아 텔루라이드 2020", 9); + productRepository.save(newProduct36); + + Product newProduct37 = new Product("KIA21-S037", 28000000, "기아 셀토스 2021", 6); + productRepository.save(newProduct37); + + Product newProduct38 = new Product("KIA22-S038", 31500000, "기아 스포티지 2022", 10); + productRepository.save(newProduct38); + + Product newProduct39 = new Product("KIA23-S039", 33000000, "기아 스팅어 2023", 7); + productRepository.save(newProduct39); + + Product newProduct40 = new Product("KIA24-S040", 35500000, "기아 EV9 2024", 3); + productRepository.save(newProduct40); + + Product newProduct41 = new Product("KIA15-S041", 19500000, "기아 옵티마 2015", 6); + productRepository.save(newProduct41); + + Product newProduct42 = new Product("KIA16-S042", 22500000, "기아 소울 2016", 5); + productRepository.save(newProduct42); + + Product newProduct43 = new Product("KIA17-S043", 25500000, "기아 니로 2017", 9); + productRepository.save(newProduct43); + + Product newProduct44 = new Product("KIA18-S044", 24000000, "기아 포르테 2018", 4); + productRepository.save(newProduct44); + + Product newProduct45 = new Product("KIA19-S045", 28500000, "기아 텔루라이드 2019", 8); + productRepository.save(newProduct45); + + Product newProduct46 = new Product("KIA20-S046", 26500000, "기아 소렌토 2020", 7); + productRepository.save(newProduct46); + + Product newProduct47 = new Product("KIA21-S047", 30000000, "기아 스팅어 2021", 3); + productRepository.save(newProduct47); + + Product newProduct48 = new Product("KIA22-S048", 32500000, "기아 EV6 2022", 6); + productRepository.save(newProduct48); + + Product newProduct49 = new Product("KIA23-S049", 34000000, "기아 카니발 2023", 10); + productRepository.save(newProduct49); + + Product newProduct50 = new Product("KIA24-S050", 36000000, "기아 EV9 2024", 5); + productRepository.save(newProduct50); + } else { + log.info("Product(제품) 테이블에 이미 데이터가 존재합니다."); + } + + // 제품 옵션 등록 + if(productOptionRepository.count() == 0){ + ProductOption newProductOption1 = new ProductOption('K', 'N', 'A', 'A', 'L', '4', '2', 'A', 'P', 'A', 'U', '1', '0', '1', 'B', 'W', '1', '1', '1', '1', '1', '0', '1', '1', '1'); + productOptionRepository.save(newProductOption1); + + ProductOption newProductOption2 = new ProductOption('K', 'N', 'H', 'B', 'L', '4', '4', 'B', 'R', 'B', 'Z', '1', '1', '0', 'G', 'B', '0', '1', '0', '1', '0', '1', '0', '0', '1'); + productOptionRepository.save(newProductOption2); + + ProductOption newProductOption3 = new ProductOption('K', 'M', 'H', 'C', 'M', '6', '3', 'C', 'P', 'C', 'A', '0', '1', '1', 'R', 'G', '1', '1', '1', '0', '1', '1', '0', '1', '0'); + productOptionRepository.save(newProductOption3); + + ProductOption newProductOption4 = new ProductOption('K', 'N', 'J', 'D', 'L', '2', '4', 'A', 'R', 'D', 'C', '1', '0', '0', 'B', 'R', '0', '0', '0', '1', '0', '0', '1', '0', '1'); + productOptionRepository.save(newProductOption4); + + ProductOption newProductOption5 = new ProductOption('K', 'M', 'F', 'B', 'S', '5', '2', 'D', 'O', 'B', 'A', '1', '1', '1', 'Y', 'S', '0', '1', '1', '1', '0', '0', '0', '1', '0'); + productOptionRepository.save(newProductOption5); + + ProductOption newProductOption6 = new ProductOption('K', 'N', 'A', 'F', 'L', '3', '3', 'F', 'B', 'Y', 'S', '2', '0', '1', 'T', 'B', '1', '1', '0', '0', '0', '1', '1', '1', '1'); + productOptionRepository.save(newProductOption6); + + ProductOption newProductOption7 = new ProductOption('K', 'M', 'F', 'A', 'J', '4', '2', 'D', 'S', 'C', 'V', '1', '0', '0', 'A', 'Q', '1', '1', '1', '0', '0', '0', '1', '0', '1'); + productOptionRepository.save(newProductOption7); + + ProductOption newProductOption8 = new ProductOption('K', 'N', 'A', 'B', 'F', '5', '4', 'S', 'M', 'A', 'R', '0', '1', '0', 'T', 'S', '1', '1', '1', '0', '0', '1', '0', '1', '1'); + productOptionRepository.save(newProductOption8); + + ProductOption newProductOption9 = new ProductOption('K', 'M', 'B', 'R', 'K', '2', '3', 'C', 'F', 'G', 'W', '1', '0', '0', 'A', 'F', '1', '1', '0', '1', '0', '0', '1', '0', '1'); + productOptionRepository.save(newProductOption9); + + ProductOption newProductOption10 = new ProductOption('K', 'N', 'F', 'Y', 'L', '4', '3', 'B', 'Y', 'C', 'Z', '1', '1', '1', 'X', 'S', '0', '1', '0', '0', '1', '1', '1', '0', '1'); + productOptionRepository.save(newProductOption10); + + ProductOption newProductOption11 = new ProductOption('K', 'M', 'E', 'Z', 'S', '3', '2', 'F', 'M', 'A', 'B', '0', '1', '0', 'A', 'N', '1', '0', '1', '0', '0', '1', '1', '0', '1'); + productOptionRepository.save(newProductOption11); + + ProductOption newProductOption12 = new ProductOption('K', 'N', 'A', 'F', 'J', '2', '3', 'F', 'R', 'B', 'S', '1', '0', '1', 'Y', 'G', '1', '1', '0', '1', '1', '1', '0', '0', '0'); + productOptionRepository.save(newProductOption12); + + ProductOption newProductOption13 = new ProductOption('K', 'M', 'D', 'T', 'K', '5', '4', 'S', 'A', 'V', 'L', '0', '1', '1', 'B', 'M', '0', '1', '1', '0', '0', '0', '1', '1', '1'); + productOptionRepository.save(newProductOption13); + + ProductOption newProductOption14 = new ProductOption('K', 'M', 'A', 'Z', 'P', '4', '2', 'D', 'P', 'S', 'B', '1', '1', '1', 'Y', 'Q', '1', '0', '0', '1', '1', '0', '0', '0', '1'); + productOptionRepository.save(newProductOption14); + + ProductOption newProductOption15 = new ProductOption('K', 'M', 'F', 'W', 'J', '6', '3', 'B', 'R', 'V', 'Z', '0', '0', '1', 'B', 'R', '1', '1', '0', '0', '1', '1', '0', '1', '0'); + productOptionRepository.save(newProductOption15); + + ProductOption newProductOption16 = new ProductOption('K', 'N', 'A', 'C', 'L', '3', '5', 'S', 'Q', 'M', 'R', '1', '1', '0', 'Y', 'B', '0', '1', '1', '0', '0', '0', '0', '0', '1'); + productOptionRepository.save(newProductOption16); + + ProductOption newProductOption17 = new ProductOption('K', 'M', 'F', 'A', 'J', '4', '2', 'D', 'S', 'C', 'V', '1', '0', '1', 'A', 'Q', '1', '1', '0', '0', '1', '0', '1', '1', '0'); + productOptionRepository.save(newProductOption17); + + ProductOption newProductOption18 = new ProductOption('K', 'N', 'F', 'B', 'S', '3', '3', 'S', 'M', 'A', 'R', '0', '1', '0', 'T', 'S', '1', '1', '0', '1', '0', '1', '0', '1', '1'); + productOptionRepository.save(newProductOption18); + + ProductOption newProductOption19 = new ProductOption('K', 'M', 'F', 'C', 'T', '5', '3', 'F', 'Q', 'C', 'Z', '0', '1', '1', 'B', 'R', '0', '1', '1', '1', '0', '1', '0', '0', '1'); + productOptionRepository.save(newProductOption19); + + ProductOption newProductOption20 = new ProductOption('K', 'N', 'F', 'A', 'P', '4', '4', 'D', 'P', 'S', 'B', '1', '0', '0', 'Y', 'Q', '0', '1', '0', '1', '0', '0', '1', '1', '0'); + productOptionRepository.save(newProductOption20); + + ProductOption newProductOption21 = new ProductOption('K', 'M', 'F', 'B', 'L', '3', '2', 'F', 'R', 'M', 'A', '1', '0', '1', 'G', 'Y', '0', '1', '0', '1', '1', '0', '0', '1', '1'); + productOptionRepository.save(newProductOption21); + + ProductOption newProductOption22 = new ProductOption('K', 'N', 'F', 'A', 'J', '5', '3', 'D', 'S', 'C', 'W', '0', '1', '1', 'A', 'F', '1', '0', '1', '1', '0', '0', '0', '0', '1'); + productOptionRepository.save(newProductOption22); + + ProductOption newProductOption23 = new ProductOption('K', 'M', 'F', 'C', 'S', '4', '3', 'B', 'Q', 'A', 'Z', '1', '0', '0', 'Y', 'R', '1', '0', '1', '0', '1', '1', '1', '0', '0'); + productOptionRepository.save(newProductOption23); + + ProductOption newProductOption24 = new ProductOption('K', 'N', 'F', 'D', 'T', '2', '4', 'F', 'P', 'B', 'A', '1', '1', '0', 'Y', 'Q', '0', '1', '1', '1', '0', '1', '1', '0', '1'); + productOptionRepository.save(newProductOption24); + + ProductOption newProductOption25 = new ProductOption('K', 'M', 'F', 'B', 'J', '3', '5', 'S', 'M', 'Z', 'Y', '0', '0', '1', 'A', 'T', '1', '1', '0', '1', '0', '0', '0', '0', '1'); + productOptionRepository.save(newProductOption25); + + ProductOption newProductOption26 = new ProductOption('K', 'N', 'F', 'A', 'L', '4', '2', 'D', 'S', 'C', 'V', '1', '0', '1', 'G', 'Y', '0', '1', '1', '1', '1', '1', '0', '0', '1'); + productOptionRepository.save(newProductOption26); + + ProductOption newProductOption27 = new ProductOption('K', 'M', 'F', 'C', 'S', '2', '3', 'F', 'Q', 'B', 'A', '0', '1', '0', 'T', 'R', '0', '1', '1', '0', '0', '1', '1', '1', '0'); + productOptionRepository.save(newProductOption27); + + ProductOption newProductOption28 = new ProductOption('K', 'N', 'F', 'B', 'M', '5', '4', 'B', 'R', 'D', 'S', '1', '0', '1', 'X', 'Q', '1', '1', '0', '0', '1', '0', '0', '1', '1'); + productOptionRepository.save(newProductOption28); + + ProductOption newProductOption29 = new ProductOption('K', 'M', 'F', 'A', 'J', '3', '2', 'C', 'P', 'Z', 'V', '0', '1', '1', 'G', 'S', '1', '0', '0', '1', '0', '1', '0', '1', '0'); + productOptionRepository.save(newProductOption29); + + ProductOption newProductOption30 = new ProductOption('K', 'N', 'F', 'C', 'L', '6', '4', 'S', 'Q', 'M', 'Z', '1', '0', '0', 'A', 'T', '1', '1', '0', '1', '1', '0', '0', '1', '1'); + productOptionRepository.save(newProductOption30); + + ProductOption newProductOption31 = new ProductOption('K', 'M', 'F', 'A', 'S', '2', '3', 'F', 'M', 'B', 'R', '0', '1', '1', 'Y', 'Q', '1', '1', '0', '0', '1', '0', '1', '1', '0'); + productOptionRepository.save(newProductOption31); + + ProductOption newProductOption32 = new ProductOption('K', 'N', 'F', 'B', 'P', '4', '2', 'S', 'R', 'A', 'Q', '1', '0', '0', 'T', 'S', '0', '1', '1', '1', '0', '0', '1', '1', '0'); + productOptionRepository.save(newProductOption32); + + ProductOption newProductOption33 = new ProductOption('K', 'M', 'F', 'C', 'L', '3', '5', 'B', 'S', 'V', 'Z', '1', '0', '1', 'A', 'R', '1', '0', '0', '0', '1', '0', '0', '1', '0'); + productOptionRepository.save(newProductOption33); + + ProductOption newProductOption34 = new ProductOption('K', 'N', 'F', 'A', 'J', '2', '3', 'D', 'P', 'W', 'R', '0', '1', '0', 'G', 'Y', '1', '0', '1', '1', '0', '1', '0', '1', '0'); + productOptionRepository.save(newProductOption34); + + ProductOption newProductOption35 = new ProductOption('K', 'M', 'F', 'B', 'S', '4', '2', 'C', 'F', 'Z', 'V', '1', '0', '0', 'T', 'R', '0', '1', '0', '1', '0', '1', '0', '1', '1'); + productOptionRepository.save(newProductOption35); + + ProductOption newProductOption36 = new ProductOption('K', 'N', 'F', 'C', 'L', '3', '3', 'S', 'Q', 'M', 'Y', '1', '1', '0', 'A', 'T', '1', '0', '0', '1', '0', '1', '1', '1', '0'); + productOptionRepository.save(newProductOption36); + + ProductOption newProductOption37 = new ProductOption('K', 'M', 'F', 'A', 'T', '5', '4', 'F', 'P', 'S', 'A', '0', '1', '1', 'G', 'R', '1', '1', '0', '1', '0', '1', '0', '0', '0'); + productOptionRepository.save(newProductOption37); + + ProductOption newProductOption38 = new ProductOption('K', 'N', 'F', 'B', 'L', '2', '3', 'C', 'M', 'B', 'Z', '1', '1', '0', 'Y', 'P', '0', '0', '1', '1', '1', '1', '0', '0', '0'); + productOptionRepository.save(newProductOption38); + + ProductOption newProductOption39 = new ProductOption('K', 'M', 'F', 'C', 'S', '4', '2', 'D', 'Q', 'A', 'R', '1', '0', '1', 'T', 'Y', '0', '1', '0', '1', '1', '0', '0', '0', '1'); + productOptionRepository.save(newProductOption39); + + ProductOption newProductOption40 = new ProductOption('K', 'N', 'F', 'A', 'J', '3', '5', 'S', 'M', 'B', 'A', '0', '1', '0', 'Y', 'S', '1', '0', '1', '1', '1', '0', '0', '1', '0'); + productOptionRepository.save(newProductOption40); + + ProductOption newProductOption41 = new ProductOption('K', 'M', 'F', 'B', 'T', '5', '3', 'D', 'F', 'S', 'Q', '1', '1', '0', 'A', 'M', '0', '1', '0', '0', '1', '1', '1', '0', '1'); + productOptionRepository.save(newProductOption41); + + ProductOption newProductOption42 = new ProductOption('K', 'N', 'F', 'C', 'L', '4', '2', 'B', 'R', 'M', 'Z', '1', '0', '1', 'Y', 'S', '0', '1', '1', '1', '0', '1', '0', '1', '0'); + productOptionRepository.save(newProductOption42); + + ProductOption newProductOption43 = new ProductOption('K', 'M', 'F', 'A', 'S', '3', '5', 'S', 'Q', 'B', 'C', '0', '1', '1', 'G', 'P', '1', '0', '0', '1', '1', '0', '0', '1', '0'); + productOptionRepository.save(newProductOption43); + + ProductOption newProductOption44 = new ProductOption('K', 'N', 'F', 'B', 'M', '6', '4', 'F', 'R', 'L', 'Y', '1', '0', '1', 'B', 'T', '0', '1', '1', '1', '0', '0', '1', '1', '0'); + productOptionRepository.save(newProductOption44); + + ProductOption newProductOption45 = new ProductOption('K', 'M', 'F', 'C', 'L', '5', '3', 'D', 'M', 'B', 'R', '0', '1', '0', 'Y', 'P', '1', '0', '1', '1', '1', '1', '0', '1', '0'); + productOptionRepository.save(newProductOption45); + + ProductOption newProductOption46 = new ProductOption('K', 'N', 'F', 'A', 'T', '4', '2', 'S', 'Q', 'P', 'W', '0', '1', '1', 'T', 'S', '1', '1', '0', '1', '0', '0', '0', '0', '1'); + productOptionRepository.save(newProductOption46); + + ProductOption newProductOption47 = new ProductOption('K', 'M', 'F', 'B', 'J', '3', '5', 'C', 'M', 'Z', 'R', '0', '1', '0', 'Y', 'Q', '1', '0', '1', '0', '0', '0', '1', '1', '1'); + productOptionRepository.save(newProductOption47); + + ProductOption newProductOption48 = new ProductOption('K', 'N', 'F', 'C', 'S', '5', '3', 'D', 'F', 'P', 'A', '1', '0', '1', 'A', 'T', '0', '1', '0', '1', '0', '1', '1', '0', '0'); + productOptionRepository.save(newProductOption48); + + ProductOption newProductOption49 = new ProductOption('K', 'M', 'F', 'A', 'R', '2', '4', 'C', 'Q', 'B', 'M', '1', '0', '1', 'Y', 'P', '0', '1', '0', '0', '1', '1', '0', '0', '1'); + productOptionRepository.save(newProductOption49); + + ProductOption newProductOption50 = new ProductOption('K', 'M', 'F', 'L', 'P', '3', '4', 'C', 'F', 'A', 'R', '1', '0', '1', 'G', 'Y', '0', '1', '0', '1', '1', '0', '0', '1', '0'); + productOptionRepository.save(newProductOption50); + } else { + log.info("ProductOption(제품 옵션) 테이블에 이미 데이터가 존재합니다."); + } + + + + + } + + private String getRandomEmploymentDate() { + Random random = new Random(); + + // Year range: 2015 - 2021 + int year = 2015 + random.nextInt(7); + int month = 1 + random.nextInt(12); + int day = 1 + random.nextInt(28); + + LocalDate employmentDate = LocalDate.of(year, month, day); + return employmentDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + } + + private String getRandomResignationDate(LocalDate employmentDate) { + Random random = new Random(); + + // Maximum duration for resignation: 0 to 365 days after employment + int daysToAdd = random.nextInt(366); // Add between 0 and 365 days + + LocalDate resignationDate = employmentDate.plusDays(daysToAdd); + return resignationDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + } + + private MultipartFile loadImage(String fileName) throws IOException { + // 리소스 디렉토리에서 파일 로드 + ClassPathResource resource = new ClassPathResource("image/" + fileName); + + // 파일 내용을 byte 배열로 읽기 + byte[] fileContent = StreamUtils.copyToByteArray(resource.getInputStream()); + + // MultipartFile 익명 구현체 생성 + return new MultipartFile() { + @Override + public String getName() { + return fileName; + } + + @Override + public String getOriginalFilename() { + return fileName; + } + + @Override + public String getContentType() { + try { + // 파일의 MIME 타입 결정 + return Files.probeContentType(resource.getFile().toPath()); + } catch (IOException e) { + return "application/octet-stream"; // 기본 MIME 타입 + } + } + + @Override + public boolean isEmpty() { + return fileContent.length == 0; + } + + @Override + public long getSize() { + return fileContent.length; + } + + @Override + public byte[] getBytes() throws IOException { + return fileContent; + } + + @Override + public InputStream getInputStream() throws IOException { + return resource.getInputStream(); + } + + @Override + public void transferTo(File dest) throws IOException, IllegalStateException { + Files.write(dest.toPath(), fileContent); + } + }; + } + + + private void createOrUpdateMember(String loginId, + String password, + String name, + String email, + int age, + String sex, + String identNo, + String phone, + String address, + String position, + String grade, + String jobType, + String military, + String bankName, + String account, + String centerId, + String organizationId, + String role, + MultipartFile imageUrl) throws Exception { + + // db에 정보가 있는지 확인 + Member existingMember = memberRepository.findByLoginId(loginId); + if (existingMember == null) { + // Create the user + SignupDTO signupDTO = new SignupDTO(); + signupDTO.setLoginId(loginId); + signupDTO.setPassword(password); + signupDTO.setName(name); + signupDTO.setEmail(email); + signupDTO.setAge(age); + signupDTO.setSex(sex); + signupDTO.setIdentNo(identNo); + signupDTO.setPhone(phone); + signupDTO.setAddress(address); + signupDTO.setPosition(position); + signupDTO.setGrade(grade); + signupDTO.setJobType(jobType); + signupDTO.setMilitary(military); + signupDTO.setBankName(bankName); + signupDTO.setAccount(account); + signupDTO.setCenterId(centerId); + signupDTO.setOrganizationId(organizationId); + + authCommandService.signup(signupDTO, imageUrl); + + // Grant role to the user + GrantDTO grantDTO = new GrantDTO(); + grantDTO.setLoginId(loginId); + grantDTO.setRole(role); + authCommandService.grantAuthority(grantDTO); + + log.info("{} 유저를 {} 역할로 생성합니다.", loginId, role); + } else { + log.info("{} 유저 정보가 이미 존재합니다.", loginId); + } + } + + + private void createOrUpdateNotice (String loginId, + String password, + String name, + String email, + int age, + String sex, + String identNo, + String phone, + String address, + String position, + String grade, + String jobType, + String military, + String bankName, + String account, + String centerId, + String organizationId, + String role, + MultipartFile imageUrl) throws Exception { + + // db에 정보가 있는지 확인 + Member existingMember = memberRepository.findByLoginId(loginId); + if (existingMember == null) { + // Create the user + SignupDTO signupDTO = new SignupDTO(); + signupDTO.setLoginId(loginId); + signupDTO.setPassword(password); + signupDTO.setName(name); + signupDTO.setEmail(email); + signupDTO.setAge(age); + signupDTO.setSex(sex); + signupDTO.setIdentNo(identNo); + signupDTO.setPhone(phone); + signupDTO.setAddress(address); + signupDTO.setPosition(position); + signupDTO.setGrade(grade); + signupDTO.setJobType(jobType); + signupDTO.setMilitary(military); + signupDTO.setBankName(bankName); + signupDTO.setAccount(account); + signupDTO.setCenterId(centerId); + signupDTO.setOrganizationId(organizationId); + + authCommandService.signup(signupDTO, imageUrl); + + // Grant role to the user + GrantDTO grantDTO = new GrantDTO(); + grantDTO.setLoginId(loginId); + grantDTO.setRole(role); + authCommandService.grantAuthority(grantDTO); + + log.info("{} 유저를 {} 역할로 생성합니다.", loginId, role); + } else { + log.info("{} 유저 정보가 이미 존재합니다.", loginId); + } + } +} + diff --git a/src/main/java/stanl_2/final_backend/global/excel/ExcelColumnName.java b/src/main/java/stanl_2/final_backend/global/excel/ExcelColumnName.java new file mode 100644 index 00000000..17fd2905 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/global/excel/ExcelColumnName.java @@ -0,0 +1,13 @@ +package stanl_2.final_backend.global.excel; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExcelColumnName { + + String name() default ""; +} diff --git a/src/main/java/stanl_2/final_backend/global/excel/ExcelSupportV1.java b/src/main/java/stanl_2/final_backend/global/excel/ExcelSupportV1.java new file mode 100644 index 00000000..35741c31 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/global/excel/ExcelSupportV1.java @@ -0,0 +1,10 @@ +package stanl_2.final_backend.global.excel; + +import jakarta.servlet.http.HttpServletResponse; + +import java.util.List; + +public interface ExcelSupportV1 { + // 메타데이터 타입, 메타 데이터, 파일이름, 응답 + void download(Class clazz, List data, String fileName, HttpServletResponse response); +} diff --git a/src/main/java/stanl_2/final_backend/global/excel/ExcelUtilsV1.java b/src/main/java/stanl_2/final_backend/global/excel/ExcelUtilsV1.java new file mode 100644 index 00000000..e69bfd30 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/global/excel/ExcelUtilsV1.java @@ -0,0 +1,154 @@ +package stanl_2.final_backend.global.excel; + +import com.sun.nio.sctp.IllegalReceiveException; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.streaming.SXSSFSheet; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class ExcelUtilsV1 implements ExcelSupportV1{ + + private static final Integer MAX_ROW = 5000; + + // 메타데이터 타입, 메타 데이터, 파일이름, 응답 + @Override + public void download(Class clazz, List data, String fileName, HttpServletResponse response){ + + try(SXSSFWorkbook workbook = new SXSSFWorkbook()){ + + Integer loop = 1; + Integer listSize = data.size(); + + // 하나의 sheet당 10000개의 데이터를 그려줌 + for (Integer start = 0; start < listSize; start+= MAX_ROW) { + + Integer nextPage = MAX_ROW * loop; + if(nextPage > listSize){ + nextPage = listSize - 1; + } + + List datas = new ArrayList<>(data.subList(start, nextPage)); + getWorkBook(clazz, workbook, start, findHeaderNames(clazz), datas, listSize); + datas.clear(); + loop++; + } + + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader("Content-Disposition", "attachment; filename=" + fileName + ".xlsx"); + + ServletOutputStream outputStream = response.getOutputStream(); + workbook.write(outputStream); + outputStream.flush(); + outputStream.close(); + } catch(IOException | IllegalAccessException e){ + log.error("Excel 다운로드 Error 메세지: {}", e.getMessage()); + throw new RuntimeException(e); + } + + } + + private SXSSFWorkbook getWorkBook(Class clazz, SXSSFWorkbook workbook, Integer start, + List headerNames, List datas, Integer listSize) + throws IllegalAccessException, IOException{ + + // 각 sheet당 MAX_ROW개씩 생성 + String sheetName = "Sheet" + (start / MAX_ROW + 1); + + Sheet sheet = ObjectUtils.isEmpty(workbook.getSheet(sheetName)) + ? workbook.createSheet(sheetName) : workbook.getSheet(sheetName); + sheet.setDefaultColumnWidth(10); // 디폴트 너비 설정 +// sheet.setDefaultRowHeight((short) 500); // 디폴트 높이 설정 + + Row row = null; + Cell cell = null; + Integer rowNo = start % listSize; // 0, 10000, 20000, 30000 -> 10000(maxSize)씩 증가 + + row = sheet.createRow(0); + createHeaders(workbook, row, cell, headerNames); // sheet의 헤더 생성 + createBody(clazz, datas, sheet, row, cell, start); // sheet의 body 생성 + + // 주기적으로 flush 진행 + if (rowNo % MAX_ROW == 0){ + ((SXSSFSheet) sheet).flushRows(MAX_ROW); + } + + return workbook; + } + + private void createBody(Class clazz, List datas, Sheet sheet, Row row, Cell cell, Integer start) + throws IllegalAccessException, IOException{ + + Integer startRow = 0; + + for (Object o : datas){ + List fields = findFieldValue(clazz, o); + row = sheet.createRow(++startRow); + for (Integer i = 0, fieldSize = fields.size() ; i < fieldSize; i++) { + cell = row.createCell(i); + cell.setCellValue(String.valueOf(fields.get(i))); + } + + // 주기적인 flush 진행 + if (start % MAX_ROW == 0){ + ((SXSSFSheet) sheet).flushRows(MAX_ROW); + } + } + } + + // 데이터의 값을 추출하는 메소드 + private List findFieldValue(Class clazz, Object o) throws IllegalAccessException { + List result = new ArrayList<>(); + for (Field field: clazz.getDeclaredFields()){ + field.setAccessible(true); + result.add(field.get(o)); + } + + return result; + } + + private void createHeaders(SXSSFWorkbook workbook, Row row, Cell cell, List headerNames) { + + // header의 폰트 스타일 + Font font = workbook.createFont(); + font.setBold(true); // 글자 굵게 + + // header의 cell 스타일 + CellStyle headerCellStyle = workbook.createCellStyle(); + headerCellStyle.setAlignment(HorizontalAlignment.CENTER); // 가로 가운데 정렬 + headerCellStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 세로 가운데 정렬 + + // cell 테두리 설정 + headerCellStyle.setBorderLeft(BorderStyle.MEDIUM); + headerCellStyle.setBorderRight(BorderStyle.MEDIUM); + headerCellStyle.setBorderTop(BorderStyle.MEDIUM); + headerCellStyle.setBorderBottom(BorderStyle.MEDIUM); + headerCellStyle.setFont(font); + + for (Integer i = 0, size = headerNames.size() ; i < size; i++) { + cell = row.createCell(i); + cell.setCellStyle(headerCellStyle); + cell.setCellValue(headerNames.get(i)); + } + } + + // 엑셀의 헤더 이름을 찾는 로직 + private List findHeaderNames(Class clazz) { + return Arrays.stream(clazz.getDeclaredFields()) + .filter(field -> field.isAnnotationPresent(ExcelColumnName.class)) + .map(field -> field.getAnnotation(ExcelColumnName.class).name()) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/stanl_2/final_backend/global/exception/CommonException.java b/src/main/java/stanl_2/final_backend/global/exception/CommonException.java deleted file mode 100644 index 2ab808fc..00000000 --- a/src/main/java/stanl_2/final_backend/global/exception/CommonException.java +++ /dev/null @@ -1,21 +0,0 @@ -package stanl_2.final_backend.global.exception; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; - -@Getter -public class CommonException extends RuntimeException { - private final ErrorCode errorCode; - - @Autowired - public CommonException(ErrorCode errorCode) { - this.errorCode = errorCode; - } - - // 에러 발생시 ErroCode 별 메시지 - @Override - public String getMessage() { - return this.errorCode.getMsg(); - } -} diff --git a/src/main/java/stanl_2/final_backend/global/exception/GlobalCommonException.java b/src/main/java/stanl_2/final_backend/global/exception/GlobalCommonException.java new file mode 100644 index 00000000..352f04e8 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/global/exception/GlobalCommonException.java @@ -0,0 +1,17 @@ +package stanl_2.final_backend.global.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class GlobalCommonException extends RuntimeException { + + private final GlobalErrorCode errorCode; + + // 에러 발생시 ErroCode 별 메시지 + @Override + public String getMessage() { + return this.errorCode.getMsg(); + } +} diff --git a/src/main/java/stanl_2/final_backend/global/exception/ErrorCode.java b/src/main/java/stanl_2/final_backend/global/exception/GlobalErrorCode.java similarity index 82% rename from src/main/java/stanl_2/final_backend/global/exception/ErrorCode.java rename to src/main/java/stanl_2/final_backend/global/exception/GlobalErrorCode.java index 42bfc05c..dc8a2cb3 100644 --- a/src/main/java/stanl_2/final_backend/global/exception/ErrorCode.java +++ b/src/main/java/stanl_2/final_backend/global/exception/GlobalErrorCode.java @@ -6,7 +6,7 @@ @Getter @AllArgsConstructor -public enum ErrorCode { +public enum GlobalErrorCode { /** * 400(Bad Request) @@ -21,6 +21,7 @@ public enum ErrorCode { "데이터 무결성 위반입니다. 필수 값이 누락되었거나 유효하지 않습니다."), REGISTER_FAIL(40006, HttpStatus.BAD_REQUEST, "회원 가입 실패!"), VALIDATION_FAIL(40007, HttpStatus.BAD_REQUEST, "유효성 검사 실패"), + NUMBER_VALIDATION_FAIL(40008, HttpStatus.BAD_REQUEST, "인증번호 인증 실패"), /** @@ -31,14 +32,15 @@ public enum ErrorCode { */ LOGIN_FAILURE(40100, HttpStatus.UNAUTHORIZED, "로그인에 실패했습니다"), INVALID_TOKEN_ERROR(40101, HttpStatus.UNAUTHORIZED, "유효하지 않은 토큰입니다."), - NOT_FOUND_CSRF_TOKEN(40102, HttpStatus.UNAUTHORIZED, "CSRF 토큰이 요류입니다."), + JWT_EXPIRED(40102, HttpStatus.UNAUTHORIZED, "만료된 토큰입니다."), + /** * 403(Forbidden) * 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. * 예를들어 그들은 미승인이어서 서버는 거절을 위한 적절한 응답을 보냅니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다. */ - FORBIDDEN_ROLE(40300, HttpStatus.FORBIDDEN, "권한이 존재하지 않습니다."), + UNAUTHORIZED(40300, HttpStatus.FORBIDDEN, "접근 권한이 존재하지 않습니다."), /** @@ -49,13 +51,15 @@ public enum ErrorCode { * 이 응답 코드는 웹에서 반복적으로 발생하기 때문에 가장 유명할지도 모릅니다. */ USERDETAILS_NOT_FOUND(40400, HttpStatus.NOT_FOUND, "User Details를 찾을 수 없습니다."), - + USER_NOT_FOUND(40401, HttpStatus.NOT_FOUND, "유저 정보가 없습니다."), + AUTHORITIES_NOT_FOUND(40402, HttpStatus.NOT_FOUND, "유저 권한을 찾을 수 없습니다."), /** * 500(Internal Server Error) * 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다. */ - INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."); + INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."), + FAIL_LOG_SAVE(50001, HttpStatus.INTERNAL_SERVER_ERROR, "로그내용을 저장할 수 없습니다."); private final Integer code; diff --git a/src/main/java/stanl_2/final_backend/global/exception/GlobalExceptionHandler.java b/src/main/java/stanl_2/final_backend/global/exception/GlobalExceptionHandler.java index 4642e8d7..ff95018f 100644 --- a/src/main/java/stanl_2/final_backend/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/stanl_2/final_backend/global/exception/GlobalExceptionHandler.java @@ -1,8 +1,14 @@ package stanl_2.final_backend.global.exception; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.JwtException; +import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MissingServletRequestParameterException; @@ -10,69 +16,164 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.servlet.NoHandlerFoundException; +import stanl_2.final_backend.domain.log.command.aggregate.Log; +import stanl_2.final_backend.domain.log.command.repository.LogRepository; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; @Slf4j -// 모든 rest컨트롤러에서 발생하는 예외 처리 @RestControllerAdvice(basePackages = "stanl_2.final_backend") public class GlobalExceptionHandler { - // 지원되지 않는 HTTP 메소드를 사용할 때 발생하는 예외 - @ExceptionHandler(value = {NoHandlerFoundException.class, HttpRequestMethodNotSupportedException.class}) - public ResponseEntity handleNoPageFoundException(Exception e) { - log.error("handleNoPageFoundException() in GlobalExceptionHandler throw NoHandlerFoundException : {}" - , e.getMessage()); - ExceptionResponse response = new ExceptionResponse(new CommonException(ErrorCode.WRONG_ENTRY_POINT).getErrorCode()); - return new ResponseEntity<>(response, response.getHttpStatus()); + private final LogRepository logRepository; + private final MemberQueryService memberQueryService; + + @Autowired + public GlobalExceptionHandler(LogRepository logRepository, + MemberQueryService memberQueryService) { + this.logRepository = logRepository; + this.memberQueryService = memberQueryService; } - // 메소드의 인자 타입이 일치하지 않을 때 발생하는 예외 - @ExceptionHandler(value = {MethodArgumentTypeMismatchException.class}) - public ResponseEntity handleArgumentNotValidException(MethodArgumentTypeMismatchException e) { - log.error("handleArgumentNotValidException() in GlobalExceptionHandler throw MethodArgumentTypeMismatchException : {}" - , e.getMessage()); - ExceptionResponse response = new ExceptionResponse(new CommonException(ErrorCode.INVALID_PARAMETER_FORMAT).getErrorCode()); - return new ResponseEntity<>(response, response.getHttpStatus()); + @ExceptionHandler({NoHandlerFoundException.class, HttpRequestMethodNotSupportedException.class}) + public ResponseEntity handleNoPageFoundException(Exception e, HttpServletRequest request) { + log.error("지원되지 않는 요청: {}", e.getMessage()); + return createErrorResponse(GlobalErrorCode.WRONG_ENTRY_POINT, e, request); } - // 필수 파라미터가 누락되었을 때 발생하는 예외 - @ExceptionHandler(value = {MissingServletRequestParameterException.class}) - public ResponseEntity handleArgumentNotValidException(MissingServletRequestParameterException e) { - log.error("handleArgumentNotValidException() in GlobalExceptionHandler throw MethodArgumentNotValidException : {}" - , e.getMessage()); - ExceptionResponse response = new ExceptionResponse(new CommonException(ErrorCode.MISSING_REQUEST_PARAMETER).getErrorCode()); - return new ResponseEntity<>(response, response.getHttpStatus()); + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public ResponseEntity handleArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) { + log.error("인자 타입 불일치: {}", e.getMessage()); + return createErrorResponse(GlobalErrorCode.INVALID_PARAMETER_FORMAT, e, request); } - // 사용자 정의 예외 처리 - @ExceptionHandler(value = {CommonException.class}) - public ResponseEntity handleCustomException(CommonException e) { - log.error("handleCustomException() in GlobalExceptionHandler: {}", e.getMessage()); - ExceptionResponse response = new ExceptionResponse(e.getErrorCode()); + @ExceptionHandler(MissingServletRequestParameterException.class) + public ResponseEntity handleMissingRequestParameterException(MissingServletRequestParameterException e, HttpServletRequest request) { + log.error("필수 파라미터 누락: {}", e.getMessage()); + return createErrorResponse(GlobalErrorCode.MISSING_REQUEST_PARAMETER, e, request); + } - return new ResponseEntity<>(response, response.getHttpStatus()); + @ExceptionHandler(GlobalCommonException.class) + public ResponseEntity> handleCustomException(GlobalCommonException e, HttpServletRequest request) { + log.error("사용자 예외 처리: {}", e.getMessage()); + saveErrorLog(e.getErrorCode().getMsg(), e, request); + Map response = new HashMap<>(); + response.put("message", e.getErrorCode().getMsg()); + response.put("code", e.getErrorCode().getCode()); + return new ResponseEntity<>(response, e.getErrorCode().getHttpStatus()); } - // 서버 내부 오류시 작동 - @ExceptionHandler(value = {Exception.class}) - public ResponseEntity handleServerException(Exception e) { - log.info("occurred exception in handleServerError = {}", e.getMessage()); - e.printStackTrace(); - ExceptionResponse response = new ExceptionResponse(new CommonException(ErrorCode.INTERNAL_SERVER_ERROR).getErrorCode()); - return new ResponseEntity<>(response, response.getHttpStatus()); + @ExceptionHandler(Exception.class) + public ResponseEntity handleServerException(Exception e, HttpServletRequest request) { + log.error("서버 내부 오류 발생: {}", e.getMessage()); + return createErrorResponse(GlobalErrorCode.INTERNAL_SERVER_ERROR, e, request); } - // 데이터 무결성 위반 예외 처리기 추가 - @ExceptionHandler(value = {DataIntegrityViolationException.class}) - public ResponseEntity handleDataIntegrityViolationException(DataIntegrityViolationException e) { - log.error("handleDataIntegrityViolationException() in GlobalExceptionHandler : {}", e.getMessage()); - ExceptionResponse response = new ExceptionResponse(new CommonException(ErrorCode.DATA_INTEGRITY_VIOLATION).getErrorCode()); - return new ResponseEntity<>(response, response.getHttpStatus()); + @ExceptionHandler(DataIntegrityViolationException.class) + public ResponseEntity handleDataIntegrityViolationException(DataIntegrityViolationException e, HttpServletRequest request) { + log.error("데이터 무결성 위반: {}", e.getMessage()); + return createErrorResponse(GlobalErrorCode.DATA_INTEGRITY_VIOLATION, e, request); } - @ExceptionHandler(value = {MethodArgumentNotValidException.class}) - public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) { log.error("유효성 검사 실패: {}", e.getMessage()); - ExceptionResponse response = new ExceptionResponse(new CommonException(ErrorCode.VALIDATION_FAIL).getErrorCode()); - return new ResponseEntity<>(response, response.getHttpStatus()); + return createErrorResponse(GlobalErrorCode.VALIDATION_FAIL, e, request); + } + + @ExceptionHandler(ExpiredJwtException.class) + public ResponseEntity handleExpiredJwtException(ExpiredJwtException e, HttpServletRequest request) { + log.error("만료된 JWT 토큰: {}", e.getMessage()); + return createErrorResponse(GlobalErrorCode.JWT_EXPIRED, e, request); + } + + @ExceptionHandler(JwtException.class) + public ResponseEntity handleJwtException(JwtException e, HttpServletRequest request) { + log.error("유효하지 않은 JWT 토큰: {}", e.getMessage()); + return createErrorResponse(GlobalErrorCode.INVALID_TOKEN_ERROR, e, request); + } + + private ResponseEntity createErrorResponse(GlobalErrorCode errorCode, Exception e, HttpServletRequest request) { + saveErrorLog(errorCode.getMsg(), e, request); + GlobalExceptionResponse response = new GlobalExceptionResponse(new GlobalCommonException(errorCode).getErrorCode()); + return ResponseEntity.status(response.getHttpStatus()).body(response); + } + + private void saveErrorLog(String message, Exception e, HttpServletRequest request) { + try { + Log logEntry = new Log(); + + // 에러 정보 + logEntry.setStatus("ERROR"); + logEntry.setErrorMessage(e.getMessage()); + + // 요청 정보 + logEntry.setUri(safeValue(request.getRequestURI())); + logEntry.setMethod(safeValue(request.getMethod())); + logEntry.setQueryString(safeValue(request.getQueryString())); + + // 유저 정보 + logEntry.setSessionId(safeValue(request.getRequestedSessionId())); + logEntry.setUserAgent(safeValue(request.getHeader("User-Agent"))); + + String loginId = "anonymousUser"; + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication != null && authentication.isAuthenticated() && + !"anonymousUser".equals(authentication.getPrincipal()) && + !authentication.getPrincipal().toString().startsWith("stanl_2") + ) { + loginId = authentication.getPrincipal().toString(); + } + + logEntry.setLoginId(loginId); + + // 네트워크 정보 + logEntry.setIpAddress(safeValue(getClientIp(request))); + logEntry.setHostName(safeValue(request.getRemoteHost())); + logEntry.setRemotePort(request.getRemotePort()); + + // Transaction ID + String transactionId = UUID.randomUUID().toString(); + logEntry.setTransactionId(transactionId); + + logRepository.save(logEntry); + + // 임원 일시 메일 전송 + String pos = memberQueryService.selectMemberInfo(loginId).getPosition(); + + if("DIRECTOR".equals(pos) || "CEO".equals(pos)){ + memberQueryService.sendErrorMail(loginId, logEntry); + } + + } catch (Exception ex) { + log.error("로그 저장 중 오류 발생: {}", ex.getMessage(), ex); + } + } + + private String getClientIp(HttpServletRequest request) { + String[] headers = { + "X-Forwarded-For", + "Proxy-Client-IP", + "WL-Proxy-Client-IP", + "HTTP_CLIENT_IP", + "HTTP_X_FORWARDED_FOR" + }; + + for (String header : headers) { + String ip = request.getHeader(header); + if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) { + return ip.split(",")[0]; + } + } + return request.getRemoteAddr(); + } + + private String safeValue(String value) { + return value != null ? value : "N/A"; } } diff --git a/src/main/java/stanl_2/final_backend/global/exception/ExceptionResponse.java b/src/main/java/stanl_2/final_backend/global/exception/GlobalExceptionResponse.java similarity index 61% rename from src/main/java/stanl_2/final_backend/global/exception/ExceptionResponse.java rename to src/main/java/stanl_2/final_backend/global/exception/GlobalExceptionResponse.java index ba35b908..f8c78d1d 100644 --- a/src/main/java/stanl_2/final_backend/global/exception/ExceptionResponse.java +++ b/src/main/java/stanl_2/final_backend/global/exception/GlobalExceptionResponse.java @@ -4,19 +4,19 @@ import org.springframework.http.HttpStatus; @Getter -public class ExceptionResponse { +public class GlobalExceptionResponse { private final Integer code; private final String msg; private final HttpStatus httpStatus; - public ExceptionResponse(ErrorCode errorCode) { + public GlobalExceptionResponse(GlobalErrorCode errorCode) { this.code = errorCode.getCode(); this.msg = errorCode.getMsg(); this.httpStatus = errorCode.getHttpStatus(); } - public static ExceptionResponse of(ErrorCode errorCode) { - return new ExceptionResponse(errorCode); + public static GlobalExceptionResponse of(GlobalErrorCode errorCode) { + return new GlobalExceptionResponse(errorCode); } } diff --git a/src/main/java/stanl_2/final_backend/global/mail/MailConfig.java b/src/main/java/stanl_2/final_backend/global/mail/MailConfig.java new file mode 100644 index 00000000..ace508e5 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/global/mail/MailConfig.java @@ -0,0 +1,42 @@ +package stanl_2.final_backend.global.mail; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import java.util.Properties; + +@Configuration +public class MailConfig { + + @Value("${spring.mail.host.port.username}") + private String email; + + @Value("${spring.mail.host.port.password}") + private String password; + + @Bean + public JavaMailSender mailSender() { + + JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); + mailSender.setHost("smtp.naver.com"); + mailSender.setPort(465); + mailSender.setUsername(email); + mailSender.setPassword(password); + + Properties javaMailProperties = new Properties(); + javaMailProperties.put("mail.transport.protocol", "smtp"); + javaMailProperties.put("mail.smtp.auth", "true");//smtp 서버에 인증이 필요 + javaMailProperties.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");//SSL 소켓 팩토리 클래스 사용 + javaMailProperties.put("mail.smtp.starttls.enable", "true");//STARTTLS(TLS를 시작하는 명령)를 사용하여 암호화된 통신을 활성화 + javaMailProperties.put("mail.debug", "false"); + javaMailProperties.put("mail.smtp.ssl.trust", "smtp.naver.com");//smtp 서버의 ssl 인증서를 신뢰 + javaMailProperties.put("mail.smtp.ssl.protocols", "TLSv1.2");//사용할 ssl 프로토콜 버젼 + + mailSender.setJavaMailProperties(javaMailProperties); + + return mailSender; + } +} diff --git a/src/main/java/stanl_2/final_backend/global/mail/MailService.java b/src/main/java/stanl_2/final_backend/global/mail/MailService.java new file mode 100644 index 00000000..34a74dbe --- /dev/null +++ b/src/main/java/stanl_2/final_backend/global/mail/MailService.java @@ -0,0 +1,14 @@ +package stanl_2.final_backend.global.mail; + +import jakarta.mail.MessagingException; +import stanl_2.final_backend.domain.log.command.aggregate.Log; + +public interface MailService { + + /* 메일 전송 */ + void sendEmail(String toEmail) throws MessagingException; + + void sendPwdEmail(String decrypt, StringBuilder password) throws MessagingException; + + void sendErrorEmail(String email, String loginId, String name, String position, Log logEntry) throws MessagingException; +} diff --git a/src/main/java/stanl_2/final_backend/global/mail/MailServiceImpl.java b/src/main/java/stanl_2/final_backend/global/mail/MailServiceImpl.java new file mode 100644 index 00000000..bf73ed55 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/global/mail/MailServiceImpl.java @@ -0,0 +1,202 @@ +package stanl_2.final_backend.global.mail; + +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.context.Context; +import org.thymeleaf.templatemode.TemplateMode; +import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver; +import stanl_2.final_backend.domain.log.command.aggregate.Log; +import stanl_2.final_backend.global.redis.RedisService; + +import java.security.SecureRandom; + +@Slf4j +@Service(value = "MailService") +public class MailServiceImpl implements MailService { + + private final JavaMailSender mailSender; + private final RedisService redisService; + private static final String CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + @Value("${spring.mail.host.port.username}") + private String configEmail; + + @Autowired + public MailServiceImpl(JavaMailSender mailSender, + RedisService redisService) { + this.mailSender = mailSender; + this.redisService = redisService; + } + + /* 6자리 난수 생성 */ + private String createCode() { + SecureRandom random = new SecureRandom(); + StringBuilder sb = new StringBuilder(6); + + for (int i = 0; i < 6; i++) { + int randomIndex = random.nextInt(CHARACTERS.length()); + sb.append(CHARACTERS.charAt(randomIndex)); + } + return sb.toString(); + } + + /* Thymeleaf 기반의 html 파일에 값 넣고 연결하는 메소드 */ + private String setContext(String code) { + + // thymeleaf 기반의 html 파일에 값을 넣고 연결 + Context context = new Context(); + + // templateengine과 classloadertemplateresolver를 활용하여 resource/template에 위치한 mail.html 연결 + TemplateEngine templateEngine = new TemplateEngine(); + ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver(); + + // 매개변수 저장 + context.setVariable("code", code); + + templateResolver.setPrefix("templates/"); + templateResolver.setSuffix(".html"); + templateResolver.setTemplateMode(TemplateMode.HTML); + templateResolver.setCacheable(false); + + templateEngine.setTemplateResolver(templateResolver); + + return templateEngine.process("mail", context); + } + + private String setPwdContext(String newPwd) { + + // thymeleaf 기반의 html 파일에 값을 넣고 연결 + Context context = new Context(); + // templateengine과 classloadertemplateresolver를 활용하여 resource/template에 위치한 mail.html 연결 + TemplateEngine templateEngine = new TemplateEngine(); + ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver(); + + // 매개변수 저장 + context.setVariable("tempPassword", newPwd); + + templateResolver.setPrefix("templates/"); + templateResolver.setSuffix(".html"); + templateResolver.setTemplateMode(TemplateMode.HTML); + templateResolver.setCacheable(false); + + templateEngine.setTemplateResolver(templateResolver); + + return templateEngine.process("pwdMail", context); + } + + private String setErrorContext(String loginId, String name, String position, Log logEntry) { + // Thymeleaf Context 생성 + Context context = new Context(); + TemplateEngine templateEngine = new TemplateEngine(); + ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver(); + + if("DIRECTOR".equals(position)){ + position = "임원"; + } else { + position = "대표이사"; + } + // 매개변수 저장 + context.setVariable("loginId", loginId); + context.setVariable("name", name); + context.setVariable("position", position); + context.setVariable("status", logEntry.getStatus()); + context.setVariable("errorMessage", logEntry.getErrorMessage()); + context.setVariable("uri", logEntry.getUri()); + context.setVariable("method", logEntry.getMethod()); + context.setVariable("queryString", logEntry.getQueryString()); + context.setVariable("userAgent", logEntry.getUserAgent()); + context.setVariable("ipAddress", logEntry.getIpAddress()); + context.setVariable("hostName", logEntry.getHostName()); + context.setVariable("remotePort", logEntry.getRemotePort()); + + // Thymeleaf 템플릿 설정 + templateResolver.setPrefix("templates/"); // 템플릿 디렉토리 설정 + templateResolver.setSuffix(".html"); // 파일 확장자 설정 + templateResolver.setTemplateMode(TemplateMode.HTML); // 템플릿 모드 설정 + templateResolver.setCacheable(false); // 캐싱 비활성화 + + // 템플릿 엔진에 리졸버 설정 + templateEngine.setTemplateResolver(templateResolver); + + // `errorMail.html` 파일 처리 및 반환 + return templateEngine.process("errorMail", context); + } + + private MimeMessage createEmailForm(String email) throws MessagingException { + + String authCode = createCode(); + + // MimeMessage에 코드, 송신 이메일, 내용 보관 + MimeMessage message = mailSender.createMimeMessage(); + message.addRecipients(MimeMessage.RecipientType.TO, email); + message.setSubject("STANL2 본인 확인"); + message.setFrom(configEmail); + message.setText(setContext(authCode), "utf-8", "html"); + + // redis에 인증번호, 이메일 저장 + redisService.setKeyWithTTL(email, authCode, 60 * 5L); + + return message; + } + + private MimeMessage createNewPwdEmailForm(String email, String newPwd) throws MessagingException { + + // MimeMessage에 코드, 송신 이메일, 내용 보관 + MimeMessage message = mailSender.createMimeMessage(); + message.addRecipients(MimeMessage.RecipientType.TO, email); + message.setSubject("STANL2 본인 확인"); + message.setFrom(configEmail); + message.setText(setPwdContext(newPwd), "utf-8", "html"); + + return message; + } + + private MimeMessage createErrorEmailForm(String email, String loginId, String name, String position, Log logEntry) throws MessagingException { + // MimeMessage 객체 생성 + MimeMessage message = mailSender.createMimeMessage(); + + // 이메일 수신자, 제목 설정 + message.addRecipients(MimeMessage.RecipientType.TO, email); + message.setSubject("🚨 STANL2 시스템 에러 알림"); + message.setFrom(configEmail); + message.setText(setErrorContext(loginId, name, position, logEntry), "utf-8", "html"); + + return message; + } + + /* 만든 메일 전송 */ + @Override + public void sendEmail(String toEmail) throws MessagingException { + + // redis에 해당 이메일이 있으면 db에서 삭제 + if(redisService.getKey(toEmail) != null) { + redisService.clearMailCache(toEmail); + } + + MimeMessage emailForm = createEmailForm(toEmail); + + mailSender.send(emailForm); + } + + @Override + public void sendPwdEmail(String email, StringBuilder password) throws MessagingException { + + MimeMessage emailForm = createNewPwdEmailForm(email, password.toString()); + + mailSender.send(emailForm); + } + + @Override + public void sendErrorEmail(String email, String loginId, String name, String position, Log logEntry) throws MessagingException { + + MimeMessage emailForm = createErrorEmailForm(email, loginId, name, position, logEntry); + + mailSender.send(emailForm); + } +} diff --git a/src/main/java/stanl_2/final_backend/global/redis/RedisConfig.java b/src/main/java/stanl_2/final_backend/global/redis/RedisConfig.java new file mode 100644 index 00000000..43c962d8 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/global/redis/RedisConfig.java @@ -0,0 +1,44 @@ +package stanl_2.final_backend.global.redis; + +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.EventListener; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; + +@Configuration +public class RedisConfig { + + private final RedisConnectionFactory redisConnectionFactory; + + public RedisConfig(RedisConnectionFactory redisConnectionFactory) { + this.redisConnectionFactory = redisConnectionFactory; + } + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + // Key는 String으로 직렬화 + template.setKeySerializer(new StringRedisSerializer()); + // Value는 JSON으로 직렬화 + template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); + + template.afterPropertiesSet(); + return template; + } + + @EventListener(ApplicationReadyEvent.class) + public void testRedisConnection() { + try { + redisConnectionFactory.getConnection().ping(); + System.out.println("✅ Successfully connected to Redis"); + } catch (Exception e) { + System.err.println("❌ Failed to connect to Redis: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/global/redis/RedisService.java b/src/main/java/stanl_2/final_backend/global/redis/RedisService.java new file mode 100644 index 00000000..485bd70e --- /dev/null +++ b/src/main/java/stanl_2/final_backend/global/redis/RedisService.java @@ -0,0 +1,72 @@ +package stanl_2.final_backend.global.redis; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.util.Set; + +@Service +public class RedisService { + + private final RedisTemplate redisTemplate; + + public RedisService(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + // 키와 값을 저장하며 TTL 설정 + public void setKeyWithTTL(String key, Object value, long ttlInSeconds) { + redisTemplate.opsForValue().set(key, value, Duration.ofSeconds(ttlInSeconds)); + } + + // 키 조회 + public Object getKey(String key) { + return redisTemplate.opsForValue().get(key); + } + + // TTL 설정 (기존 키에 대해) + public boolean setTTL(String key, long ttlInSeconds) { + return redisTemplate.expire(key, Duration.ofSeconds(ttlInSeconds)); + } + + public void clearNoticeCache() { + Set keys = redisTemplate.keys("NoticeCache*"); + if (keys != null && !keys.isEmpty()) { + redisTemplate.delete(keys); // Delete all matching keys + System.out.println("Deleted NoticeCache keys: " + keys); + } else { + System.out.println("No keys found for pattern 'NoticeCache*'."); + } + } + + public void clearPromotionCache() { + Set keys = redisTemplate.keys("PromotionCache*"); + if (keys != null && !keys.isEmpty()) { + redisTemplate.delete(keys); // Delete all matching keys + System.out.println("Deleted PromotionCache keys: " + keys); + } else { + System.out.println("No keys found for pattern 'PromotionCache*'."); + } + } + + public void clearProblemCache() { + Set keys = redisTemplate.keys("ProblemCache*"); + if (keys != null && !keys.isEmpty()) { + redisTemplate.delete(keys); // Delete all matching keys + System.out.println("Deleted ProblemCache keys: " + keys); + } else { + System.out.println("No keys found for pattern 'ProblemCache*'."); + } + } + + public void clearMailCache(String key) { + // + System.out.println("1캐시 삭제!!"); + System.out.println(redisTemplate.delete(key)); + + + } + +} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/global/common/response/ResponseMessage.java b/src/main/java/stanl_2/final_backend/global/response/GlobalResponseMessage.java similarity index 65% rename from src/main/java/stanl_2/final_backend/global/common/response/ResponseMessage.java rename to src/main/java/stanl_2/final_backend/global/response/GlobalResponseMessage.java index 65519f61..5b60ef96 100644 --- a/src/main/java/stanl_2/final_backend/global/common/response/ResponseMessage.java +++ b/src/main/java/stanl_2/final_backend/global/response/GlobalResponseMessage.java @@ -1,4 +1,4 @@ -package stanl_2.final_backend.global.common.response; +package stanl_2.final_backend.global.response; import lombok.*; @@ -7,7 +7,7 @@ @Builder @Getter @Setter -public class ResponseMessage { +public class GlobalResponseMessage { private int httpStatus; private String msg; private Object result; diff --git a/src/main/java/stanl_2/final_backend/global/security/config/DevSecurityConfig.java b/src/main/java/stanl_2/final_backend/global/security/config/DevSecurityConfig.java new file mode 100644 index 00000000..d52a8664 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/global/security/config/DevSecurityConfig.java @@ -0,0 +1,113 @@ +package stanl_2.final_backend.global.security.config; + +import lombok.extern.slf4j.Slf4j; +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.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.password.CompromisedPasswordChecker; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.password.HaveIBeenPwnedRestApiPasswordChecker; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import stanl_2.final_backend.domain.log.command.repository.LogRepository; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.global.exception.GlobalCommonException; +import stanl_2.final_backend.global.exception.GlobalErrorCode; +import stanl_2.final_backend.global.security.filter.JWTTokenValidatorFilter; + +import java.util.Arrays; +import java.util.Collections; + +import static org.springframework.security.config.Customizer.withDefaults; + +@Slf4j +@Configuration +@Profile("dev") +public class DevSecurityConfig { + + @Value("${jwt.secret-key}") + private String jwtSecretKey; + + @Value("${jwt.header}") + private String jwtHeader; + + private final LogRepository logRepository; + private final MemberQueryService memberQueryService; + + @Autowired + public DevSecurityConfig(LogRepository logRepository, + MemberQueryService memberQueryService) { + this.logRepository = logRepository; + this.memberQueryService = memberQueryService; + } + + @Bean + SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { + + http.csrf(csrfConfig -> csrfConfig.disable()); + http.cors(corsConfig -> corsConfig.configurationSource(corsConfigurationSource())) + .authorizeHttpRequests(auth -> auth + // 인증 없이 접근 가능한 API 설정 + .anyRequest().permitAll()) + .addFilterBefore(new JWTTokenValidatorFilter(jwtSecretKey, logRepository, memberQueryService), UsernamePasswordAuthenticationFilter.class) + .requiresChannel(rcc -> rcc.anyRequest().requiresInsecure()) + .exceptionHandling(ex -> ex + .authenticationEntryPoint((request, response, authException) -> { + log.error("Authentication error: {}", authException.getMessage()); + throw new GlobalCommonException(GlobalErrorCode.LOGIN_FAILURE); + }) + .accessDeniedHandler((request, response, accessDeniedException) -> { + log.error("Access denied: {}", accessDeniedException.getMessage()); + throw new GlobalCommonException(GlobalErrorCode.UNAUTHORIZED); + })); + + http.formLogin(withDefaults()); + http.httpBasic(withDefaults()); + return http.build(); + } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowedOrigins(Collections.singletonList("http://localhost:5173")); + config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); + config.setAllowCredentials(true); + config.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "X-XSRF-TOKEN")); + config.setExposedHeaders(Collections.singletonList("Authorization")); + config.setMaxAge(3600L); + + return request -> config; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + } + + @Bean + public CompromisedPasswordChecker compromisedPasswordChecker() { + return new HaveIBeenPwnedRestApiPasswordChecker(); + } + + @Bean + public AuthenticationManager authenticationManager(UserDetailsService userDetailsService){ + + DevUsernamePwdAuthenticationProvide authenticationProvider = + new DevUsernamePwdAuthenticationProvide(userDetailsService); + + ProviderManager providerManager = new ProviderManager(authenticationProvider); + + providerManager.setEraseCredentialsAfterAuthentication(false); + + return providerManager; + } +} diff --git a/src/main/java/stanl_2/final_backend/global/security/config/DevUsernamePwdAuthenticationProvide.java b/src/main/java/stanl_2/final_backend/global/security/config/DevUsernamePwdAuthenticationProvide.java new file mode 100644 index 00000000..25145132 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/global/security/config/DevUsernamePwdAuthenticationProvide.java @@ -0,0 +1,41 @@ +package stanl_2.final_backend.global.security.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@Profile("dev") +public class DevUsernamePwdAuthenticationProvide implements AuthenticationProvider { + + private final UserDetailsService userDetailsService; + + @Autowired + public DevUsernamePwdAuthenticationProvide(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + String username = authentication.getName(); + String pwd = authentication.getCredentials().toString(); + + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + + return new UsernamePasswordAuthenticationToken(userDetails, pwd, userDetails.getAuthorities()); + } + + @Override + public boolean supports(Class authentication) { + return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); + } + +} diff --git a/src/main/java/stanl_2/final_backend/global/security/config/ProdSecurityConfig.java b/src/main/java/stanl_2/final_backend/global/security/config/ProdSecurityConfig.java index e55f400f..a1518063 100644 --- a/src/main/java/stanl_2/final_backend/global/security/config/ProdSecurityConfig.java +++ b/src/main/java/stanl_2/final_backend/global/security/config/ProdSecurityConfig.java @@ -1,6 +1,8 @@ package stanl_2.final_backend.global.security.config; -import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +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; @@ -8,20 +10,19 @@ import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.password.CompromisedPasswordChecker; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.password.HaveIBeenPwnedRestApiPasswordChecker; -import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; -import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; -import stanl_2.final_backend.global.security.constants.ApplicationConstants; -import stanl_2.final_backend.global.security.filter.CsrfCookieFilter; -import stanl_2.final_backend.global.security.filter.JWTTokenGeneratorFilter; +import stanl_2.final_backend.domain.log.command.repository.LogRepository; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.global.exception.GlobalCommonException; +import stanl_2.final_backend.global.exception.GlobalErrorCode; import stanl_2.final_backend.global.security.filter.JWTTokenValidatorFilter; import java.util.Arrays; @@ -29,88 +30,85 @@ import static org.springframework.security.config.Customizer.withDefaults; +@Slf4j @Configuration @Profile("prod") public class ProdSecurityConfig { - private final ApplicationConstants applicationConstants; + @Value("${jwt.secret-key}") + private String jwtSecretKey; - public ProdSecurityConfig(ApplicationConstants applicationConstants) { - this.applicationConstants = applicationConstants; + private final LogRepository logRepository; + private final MemberQueryService memberQueryService; + + @Autowired + public ProdSecurityConfig(LogRepository logRepository, + MemberQueryService memberQueryService) { + this.logRepository = logRepository; + this.memberQueryService = memberQueryService; } @Bean - SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { - // CSRF 토큰 요청 속성을 사용하여 토큰 값을 헤더나 매개변수 값으로 해결하는 로직을 포함 - CsrfTokenRequestAttributeHandler csrfTokenRequestAttributeHandler = new CsrfTokenRequestAttributeHandler(); - http.csrf(csrfConfig -> csrfConfig.disable()); - http.sessionManagement(sessionConfig -> sessionConfig.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .cors(corsConfig -> corsConfig.configurationSource(new CorsConfigurationSource() { - @Override - public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { - CorsConfiguration config = new CorsConfiguration(); - config.setAllowedOrigins(Collections.singletonList("http://localhost:5173")); - config.setAllowedMethods(Collections.singletonList("*")); // 모든 유형의 http 메소드 트래픽 허용 - config.setAllowCredentials(true); // UI에서 백엔드로 user 자격 증명이나 기타 적용 가능한 쿠키 수락 - config.setAllowedHeaders(Collections.singletonList("*")); // 모든 종류의 헤더를 수락해도 괜찮다. - config.setExposedHeaders(Arrays.asList("Authorization")); // 헤터를 사용하여 JWT 토큰값 전송 - config.setMaxAge(3600L); // 1시간 - return config; - } - })) -// .csrf(csrfConfig -> csrfConfig.csrfTokenRequestHandler(csrfTokenRequestAttributeHandler) -// // 아래 API들에 대해서는 CSRF 보호를 무시하도록 지시(공개) -// .ignoringRequestMatchers("/api/v1/auth/signup", "/api/v1/auth/signin") -// // 로그인 작업 후 처음으로 CSRF 토큰을 생성하는데만 도움을 준다. -// .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())) - // 로그인 시 사용(jwt 생성)2 -// .addFilterAfter(new JWTTokenGeneratorFilter(applicationConstants), BasicAuthenticationFilter.class) - // 다른 api 접근시 사용(인증)1 -// .addFilterBefore(new JWTTokenValidatorFilter(applicationConstants), BasicAuthenticationFilter.class) - // csrf 보호 필터3 -// .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class) - // 데이터 파싱 필터(파싱해서 request로 )4 -// .addFilterAfter(new TokenFilter(applicationConstants), JWTTokenGeneratorFilter.class) + SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception { + CsrfTokenRequestAttributeHandler csrfTokenRequestAttributeHandler = new CsrfTokenRequestAttributeHandler(); + http.cors(corsConfig -> corsConfig.configurationSource(corsConfigurationSource())) + .csrf(csrf -> csrf.disable()) + // 필터 순서: JWT 검증 -> CSRF + .addFilterBefore(new JWTTokenValidatorFilter(jwtSecretKey, logRepository, memberQueryService), UsernamePasswordAuthenticationFilter.class) .requiresChannel(rcc -> rcc.anyRequest().requiresInsecure()) - .authorizeHttpRequests((requests -> requests - // 모두 접근 가능 - .requestMatchers("/api/v1/auth/signup", "/api/v1/auth/signin").permitAll() - .anyRequest().permitAll())); -// .anyRequest().authenticated())); + // 인증 및 권한 예외를 처리 + .exceptionHandling(ex -> ex + .authenticationEntryPoint((request, response, authException) -> { + log.error("Authentication error: {}", authException.getMessage()); + throw new GlobalCommonException(GlobalErrorCode.LOGIN_FAILURE); + }) + .accessDeniedHandler((request, response, accessDeniedException) -> { + log.error("Access denied: {}", accessDeniedException.getMessage()); + throw new GlobalCommonException(GlobalErrorCode.UNAUTHORIZED); + })); http.formLogin(withDefaults()); http.httpBasic(withDefaults()); + + // 접근 제어 + RequestMatcherConfig.configureRequestMatchers(http); + return http.build(); } + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowedOrigins(Collections.singletonList("https://stanl2motive.com")); + config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); + config.setAllowCredentials(true); + config.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "X-XSRF-TOKEN")); + config.setExposedHeaders(Collections.singletonList("Authorization")); + config.setMaxAge(3600L); + + return request -> config; + } @Bean public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } - /** - * 사용자 비밀 번호가 유출 되었는지 확인하는 메소드 - * From Spring Security 6.3부터 도입 - * @return - * */ @Bean public CompromisedPasswordChecker compromisedPasswordChecker() { return new HaveIBeenPwnedRestApiPasswordChecker(); } - // 인증 메커니즘을 시작하는 역할 @Bean - public AuthenticationManager authenticationManager(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder){ - // 인증 제공자 객체 + public AuthenticationManager authenticationManager(UserDetailsService userDetailsService){ + ProdUsernamePwdAuthenticationProvider authenticationProvider = - new ProdUsernamePwdAuthenticationProvider(userDetailsService, passwordEncoder); + new ProdUsernamePwdAuthenticationProvider(userDetailsService, passwordEncoder()); ProviderManager providerManager = new ProviderManager(authenticationProvider); - // provider manager는 Authentication 객체 내부의 비밀번호를 지우지 못하게 설정(유효성 검사) + providerManager.setEraseCredentialsAfterAuthentication(false); return providerManager; } - } diff --git a/src/main/java/stanl_2/final_backend/global/security/config/ProdUsernamePwdAuthenticationProvider.java b/src/main/java/stanl_2/final_backend/global/security/config/ProdUsernamePwdAuthenticationProvider.java index 49790dcb..f7b95157 100644 --- a/src/main/java/stanl_2/final_backend/global/security/config/ProdUsernamePwdAuthenticationProvider.java +++ b/src/main/java/stanl_2/final_backend/global/security/config/ProdUsernamePwdAuthenticationProvider.java @@ -1,7 +1,7 @@ package stanl_2.final_backend.global.security.config; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Profile; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -11,18 +11,22 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; -import stanl_2.final_backend.global.exception.CommonException; -import stanl_2.final_backend.global.exception.ErrorCode; +import stanl_2.final_backend.global.exception.GlobalCommonException; +import stanl_2.final_backend.global.exception.GlobalErrorCode; @Slf4j @Component @Profile("prod") -@RequiredArgsConstructor public class ProdUsernamePwdAuthenticationProvider implements AuthenticationProvider { private final UserDetailsService userDetailsService; private final PasswordEncoder passwordEncoder; + @Autowired + public ProdUsernamePwdAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) { + this.userDetailsService = userDetailsService; + this.passwordEncoder = passwordEncoder; + } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { @@ -30,15 +34,18 @@ public Authentication authenticate(Authentication authentication) throws Authent String pwd = authentication.getCredentials().toString(); UserDetails userDetails = userDetailsService.loadUserByUsername(username); - log.info("1"); + if (userDetails == null) { + log.error("현재 null인 userdetail의 username: {}", username); + throw new GlobalCommonException(GlobalErrorCode.USER_NOT_FOUND); + } + if(passwordEncoder.matches(pwd, userDetails.getPassword())) { return new UsernamePasswordAuthenticationToken(userDetails, pwd, userDetails.getAuthorities()); - }else{ - throw new CommonException(ErrorCode.LOGIN_FAILURE); + } else { + throw new GlobalCommonException(GlobalErrorCode.LOGIN_FAILURE); } } - // 추후에 여기에 OAuth2.0 추가 가능 할 듯 @Override public boolean supports(Class authentication) { return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); diff --git a/src/main/java/stanl_2/final_backend/global/security/config/RequestMatcherConfig.java b/src/main/java/stanl_2/final_backend/global/security/config/RequestMatcherConfig.java new file mode 100644 index 00000000..e9607265 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/global/security/config/RequestMatcherConfig.java @@ -0,0 +1,246 @@ +package stanl_2.final_backend.global.security.config; + +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; + +public class RequestMatcherConfig { + + public static void configureRequestMatchers(HttpSecurity http) throws Exception { + http.authorizeHttpRequests(auth -> auth + // 인증 없이 접근 가능한 API 설정 + .requestMatchers( + "/swagger-ui/**", + "/v3/api-docs/**", + "/swagger-resources/**", + "/webjars/**", + "/api/v1/auth/signin", + "/api/v1/auth/signup", + "/api/v1/auth/refresh", + "/api/v1/auth/checkmail", + "/api/v1/auth/checknum" + ).permitAll() + + // Organization API + .requestMatchers(HttpMethod.GET, "/api/v1/organization").hasAnyRole("organization-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + + // Auth API + .requestMatchers(HttpMethod.POST, "/api/v1/auth").hasAnyRole("grant-auth", "GOD", "DIRECTOR", "ADMIN") + + // Member API + .requestMatchers(HttpMethod.GET, "/api/v1/member").hasAnyRole("member-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/member/{centerId}").hasAnyRole("member-centerId-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/member/centerList").hasAnyRole("member-centerList-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/member/info/{loginId}").hasAnyRole("member-info-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/member/memberInfo/{memberId}").hasAnyRole("member-memberInfo-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/member/organization/{organizationId}").hasAnyRole("member-organization-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/member/search").hasAnyRole("member-search-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/member/search/list").hasAnyRole("member-search-list-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/member/excel").hasAnyRole("member-excel-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + + // Education API + .requestMatchers(HttpMethod.GET, "/api/v1/education").hasAnyRole("education-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/education/other/{loginId}").hasAnyRole("education-other-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + + // Family API + .requestMatchers(HttpMethod.GET, "/api/v1/family").hasAnyRole("family-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/family/other/{loginId}").hasAnyRole("family-other-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + + // Career API + .requestMatchers(HttpMethod.GET, "/api/v1/career").hasAnyRole("career-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/career/other/{loginId}").hasAnyRole("career-other-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/career").hasAnyRole("career-post", "GOD", "DIRECTOR", "ADMIN") + + // Certification API + .requestMatchers(HttpMethod.GET, "/api/v1/certification").hasAnyRole("certification-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/certification/other/{loginId}").hasAnyRole("certification-other-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/certification").hasAnyRole("certification-post", "GOD", "DIRECTOR", "ADMIN") + + // Customer API + .requestMatchers(HttpMethod.GET, "/api/v1/customer/list").hasAnyRole("customer-list-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/customer/search").hasAnyRole("customer-search-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/customer/{customerId}").hasAnyRole("customer-id-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/customer/contract/{customerId}").hasAnyRole("customer-contract-id-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/customer/excel").hasAnyRole("customer-excel-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/customer").hasAnyRole("customer-post", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.PUT, "/api/v1/customer/{customerId}").hasAnyRole("customer-put", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.DELETE, "/api/v1/customer/{customerId}").hasAnyRole("customer-delete", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + + // News API + .requestMatchers(HttpMethod.GET, "/api/v1/news/car").hasAnyRole("news-car-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + + // Claude API + .requestMatchers(HttpMethod.POST, "/api/v1/claude/summary").hasAnyRole("claude-summary-post", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + + // Contract API (Employee) + .requestMatchers(HttpMethod.GET, "/api/v1/contract/employee").hasAnyRole("contract-employee-get", "GOD", "EMPLOYEE") + .requestMatchers(HttpMethod.GET, "/api/v1/contract/employee/search").hasAnyRole("contract-employee-search-get", "GOD", "EMPLOYEE", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/contract/employee/{contractId}").hasAnyRole("contract-employee-id-get", "GOD", "EMPLOYEE") + .requestMatchers(HttpMethod.POST, "/api/v1/contract").hasAnyRole("contract-post", "GOD", "EMPLOYEE", "ADMIN") + .requestMatchers(HttpMethod.PUT, "/api/v1/contract/{contractId}").hasAnyRole("contract-id-put", "GOD", "EMPLOYEE", "ADMIN") + .requestMatchers(HttpMethod.DELETE, "/api/v1/contract/{contractId}").hasAnyRole("contract-id-delete", "GOD", "EMPLOYEE", "ADMIN") + + // Contract API (Center) + .requestMatchers(HttpMethod.GET, "/api/v1/contract/center").hasAnyRole("contract-center-get", "GOD", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/contract/center/search").hasAnyRole("contract-center-search-get", "GOD", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/contract/center/{contractId}").hasAnyRole("contract-center-id-get", "GOD", "ADMIN") + + // Contract API (General) + .requestMatchers(HttpMethod.GET, "/api/v1/contract").hasAnyRole("contract-get", "GOD", "DIRECTOR", "ADMIN", "EMPLOYEE") + .requestMatchers(HttpMethod.GET, "/api/v1/contract/search").hasAnyRole("contract-search-get", "GOD", "DIRECTOR") + .requestMatchers(HttpMethod.GET, "/api/v1/contract/{contractId}").hasAnyRole("contract-id-get", "GOD", "DIRECTOR", "ADMIN", "EMPLOYEE") + .requestMatchers(HttpMethod.PUT, "/api/v1/contract/status/{contractId}").hasAnyRole("contract-status-id-put", "GOD", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/contract/excel").hasAnyRole("contract-excel-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + + + // Order API (Employee) + .requestMatchers(HttpMethod.GET, "/api/v1/order/employee").hasAnyRole("order-employee-get", "GOD", "EMPLOYEE") + .requestMatchers(HttpMethod.GET, "/api/v1/order/employee/search").hasAnyRole("order-employee-search-get", "GOD", "EMPLOYEE", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/order/employee/{orderId}").hasAnyRole("order-employee-id-get", "GOD", "EMPLOYEE") + + // Order API (General) + .requestMatchers(HttpMethod.GET, "/api/v1/order/center").hasAnyRole("order-center", "GOD", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/order/center/search").hasAnyRole("order-center-search", "GOD", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/order").hasAnyRole("order-post", "GOD", "EMPLOYEE", "ADMIN") + .requestMatchers(HttpMethod.PUT, "/api/v1/order/{orderId}").hasAnyRole("order-id-put", "GOD", "EMPLOYEE", "ADMIN") + .requestMatchers(HttpMethod.DELETE, "/api/v1/order/{orderId}").hasAnyRole("order-id-delete", "GOD", "EMPLOYEE", "ADMIN") + + // Order API (Search and Excel) + .requestMatchers(HttpMethod.GET, "/api/v1/order").hasAnyRole("order-get", "GOD", "DIRECTOR") + .requestMatchers(HttpMethod.GET, "/api/v1/order/{orderId}").hasAnyRole("order-id-get", "GOD", "DIRECTOR", "EMPLOYEE", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/order/search").hasAnyRole("order-search-get", "GOD", "DIRECTOR") + .requestMatchers(HttpMethod.GET, "/api/v1/order/excel").hasAnyRole("order-excel-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + + // Order API (Status) + .requestMatchers(HttpMethod.PUT, "/api/v1/order/status/{orderId}").hasAnyRole("order-status-id-put", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + + + // Purchase Order API + .requestMatchers(HttpMethod.GET, "/api/v1/purchase-order/admin").hasAnyRole("purchase-order-admin-get", "GOD", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/purchase-order/admin/search").hasAnyRole("purchase-order-admin-search-get", "GOD", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/purchase-order/admin/{purchaseOrderId}").hasAnyRole("purchase-order-admin-id-get", "GOD", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/purchase-order").hasAnyRole("purchase-order-post", "GOD", "ADMIN") + .requestMatchers(HttpMethod.PUT, "/api/v1/purchase-order/{purchaseOrderId}").hasAnyRole("purchase-order-put", "GOD", "ADMIN") + .requestMatchers(HttpMethod.DELETE, "/api/v1/purchase-order/{purchaseOrderId}").hasAnyRole("purchase-order-delete", "GOD", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/purchase-order").hasAnyRole("purchase-order-get", "GOD", "DIRECTOR") + .requestMatchers(HttpMethod.GET, "/api/v1/purchase-order/search").hasAnyRole("purchase-order-search-get", "GOD", "DIRECTOR") + .requestMatchers(HttpMethod.GET, "/api/v1/purchase-order/{purchaseOrderId}").hasAnyRole("purchase-order-id-get", "GOD", "DIRECTOR", "ADMIN", "EMPLOYEE") + .requestMatchers(HttpMethod.GET, "/api/v1/purchase-order/excel").hasAnyRole("purchase-order-excel-get", "GOD", "DIRECTOR") + .requestMatchers(HttpMethod.PUT, "/api/v1/purchase-order/status/{purchaseOrderId}").hasAnyRole("purchase-order-status-put", "GOD", "DIRECTOR") + + // Alarm API + .requestMatchers(HttpMethod.GET, "/api/v1/alarm").hasAnyRole("alarm-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/alarm/{type}").hasAnyRole("alarm-type-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/alarm/connect").hasAnyRole("alarm-connect-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.PUT, "/api/v1/alarm/{alarmId}").hasAnyRole("alarm-id-put", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + + // Schedule API + .requestMatchers(HttpMethod.GET, "/api/v1/schedule").hasAnyRole("schedule-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/schedule/{scheduleId}").hasAnyRole("schedule-id-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/schedule/{year}/{month}").hasAnyRole("schedule-year-month-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/schedule").hasAnyRole("schedule-post", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.PUT, "/api/v1/schedule/{scheduleId}").hasAnyRole("schedule-id-put", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.DELETE, "/api/v1/schedule/{scheduleId}").hasAnyRole("schedule-id-delete", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + + // Notice API + .requestMatchers(HttpMethod.GET, "/api/v1/notice").hasAnyRole("notice-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/notice/{noticeId}").hasAnyRole("notice-id-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/notice/excel").hasAnyRole("notice-excel-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/notice").hasAnyRole("notice-post", "GOD", "DIRECTOR", "EMPLOYEE", "ADMIN") + .requestMatchers(HttpMethod.PUT, "/api/v1/notice/{noticeId}").hasAnyRole("notice-id-put", "GOD", "DIRECTOR", "EMPLOYEE", "ADMIN") + .requestMatchers(HttpMethod.DELETE, "/api/v1/notice/{noticeId}").hasAnyRole("notice-id-delete", "GOD", "DIRECTOR", "EMPLOYEE", "ADMIN") + + // Problem API + .requestMatchers(HttpMethod.GET, "/api/v1/problem").hasAnyRole("problem-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/problem/{problemId}").hasAnyRole("problem-id-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/problem/excel").hasAnyRole("problem-excel-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/problem").hasAnyRole("problem-post", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.PUT, "/api/v1/problem/{problemId}").hasAnyRole("problem-id-put", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.PUT, "/api/v1/problem/status/{problemId}").hasAnyRole("problem-status-put", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.DELETE, "/api/v1/problem/{problemId}").hasAnyRole("problem-id-delete", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + + // Promotion API + .requestMatchers(HttpMethod.GET, "/api/v1/promotion").hasAnyRole("promotion-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/promotion/{promotionId}").hasAnyRole("promotion-id-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/promotion/excel").hasAnyRole("promotion-excel-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/promotion").hasAnyRole("promotion-post", "GOD", "DIRECTOR", "EMPLOYEE", "ADMIN") + .requestMatchers(HttpMethod.PUT, "/api/v1/promotion/{promotionId}").hasAnyRole("promotion-id-put", "GOD", "DIRECTOR", "EMPLOYEE", "ADMIN") + .requestMatchers(HttpMethod.DELETE, "/api/v1/promotion/{promotionId}").hasAnyRole("promotion-id-delete", "GOD", "DIRECTOR", "EMPLOYEE", "ADMIN") + + // File API + .requestMatchers(HttpMethod.POST, "/api/v1/file").hasAnyRole("file-post", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + + // Evaluation API + .requestMatchers(HttpMethod.GET, "/api/v1/evaluation/manager").hasAnyRole("evaluation-manager-get", "GOD", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/evaluation/manager/search").hasAnyRole("evaluation-manager-search-get", "GOD", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/evaluation/representative").hasAnyRole("evaluation-representative-get", "GOD", "DIRECTOR") + .requestMatchers(HttpMethod.GET, "/api/v1/evaluation/representative/search").hasAnyRole("evaluation-representative-search-get", "GOD", "DIRECTOR") + .requestMatchers(HttpMethod.GET, "/api/v1/evaluation/{id}").hasAnyRole("evaluation-id-get", "GOD", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/evaluation/excel").hasAnyRole("evaluation-excel-get", "GOD", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/evaluation").hasAnyRole("evaluation-post", "GOD", "ADMIN") + .requestMatchers(HttpMethod.PUT, "/api/v1/evaluation/{id}").hasAnyRole("evaluation-id-put", "GOD", "ADMIN") + .requestMatchers(HttpMethod.DELETE, "/api/v1/evaluation/{id}").hasAnyRole("evaluation-id-delete", "GOD", "ADMIN") + + // Product API + .requestMatchers(HttpMethod.GET, "/api/v1/product/excel").hasAnyRole("product-excel-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/product/{productId}").hasAnyRole("product-id-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/product/search").hasAnyRole("product-search-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/product").hasAnyRole("product-post", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + + // Center API + .requestMatchers(HttpMethod.GET, "/api/v1/center").hasAnyRole("center-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/center/search").hasAnyRole("center-search-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/center/searchList").hasAnyRole("center-searchList-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/center/excel").hasAnyRole("center-excel-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/center/{centerId}").hasAnyRole("center-id-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/center").hasAnyRole("center-post", "GOD", "ADMIN") + .requestMatchers(HttpMethod.PUT, "/api/v1/center/{centerId}").hasAnyRole("center-id-put", "GOD", "ADMIN") + .requestMatchers(HttpMethod.DELETE, "/api/v1/center/{centerId}").hasAnyRole("center-id-delete", "GOD", "ADMIN") + + // Sales History API + .requestMatchers(HttpMethod.GET, "/api/v1/salesHistory").hasAnyRole("salesHistory-get", "GOD", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/salesHistory/excel").hasAnyRole("salesHistory-excel-get", "GOD", "EMPLOYEE", "DIRECTOR", "ADMIN") + + .requestMatchers(HttpMethod.POST, "/api/v1/salesHistory/statistics").hasAnyRole("salesHistory-statistics-post", "GOD", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/salesHistory/statistics/search").hasAnyRole("salesHistory-statistics-search-post", "GOD", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/salesHistory/statistics/search/month").hasAnyRole("salesHistory-statistics-search-month-post", "GOD", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/salesHistory/statistics/average").hasAnyRole("salesHistory-statistics-search-average", "GOD", "DIRECTOR", "ADMIN", "EMPLOYEE") + .requestMatchers(HttpMethod.POST, "/api/v1/salesHistory/statistics/all").hasAnyRole("salesHistory-statistics-search-all", "GOD", "DIRECTOR", "ADMIN", "EMPLOYEE") + .requestMatchers(HttpMethod.POST, "/api/v1/salesHistory/search").hasAnyRole("salesHistory-statistics-search-post", "GOD", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/salesHistory/statistics/best").hasAnyRole("salesHistory-statistics-best-post", "GOD", "DIRECTOR", "ADMIN", "EMPLOYEE") + .requestMatchers(HttpMethod.POST, "/api/v1/salesHistory/statistics/mySearch").hasAnyRole("salesHistory-statistics-best-post", "GOD", "ADMIN", "EMPLOYEE") + .requestMatchers(HttpMethod.GET, "/api/v1/salesHistory/employee/statistics").hasAnyRole("salesHistory-employee-statistics-get", "GOD", "EMPLOYEE", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/salesHistory/employee/statistics/search").hasAnyRole("salesHistory-employee-statistics-search-get", "GOD", "EMPLOYEE", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/salesHistory/employee/statistics/search/month").hasAnyRole("salesHistory-employee-statistics-search-month-get", "GOD", "EMPLOYEE", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/salesHistory/employee/search").hasAnyRole("salesHistory-employee-search-post", "GOD", "EMPLOYEE", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/salesHistory/employee").hasAnyRole("salesHistory-employee-get", "GOD", "EMPLOYEE", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/salesHistory/employee/statistics/search/daily").hasAnyRole("salesHistory-employee-statistics-search-daily-get", "GOD", "EMPLOYEE", "ADMIN") + + .requestMatchers(HttpMethod.POST, "/api/v1/salesHistory/statistics/center/search").hasAnyRole("salesHistory-statistics-center-search-post", "GOD", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/salesHistory/statistics/center/search/year").hasAnyRole("salesHistory-statistics-center-search-year-post", "GOD", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/salesHistory/statistics/center/search/month").hasAnyRole("salesHistory-statistics-center-search-month-post", "GOD", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/salesHistory/statistics/average/employee").hasAnyRole("salesHistory-statistics-average-employee-post", "GOD", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/salesHistory/statistics/average/center").hasAnyRole("salesHistory-statistics-average-center-post", "GOD", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/salesHistory/statistics/all").hasAnyRole("salesHistory-statistics-all-post", "GOD", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/salesHistory/statistics/all/year").hasAnyRole("salesHistory-statistics-all-year-post", "GOD", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/salesHistory/statistics/all/month").hasAnyRole("salesHistory-statistics-all-month-post", "GOD", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/salesHistory/search").hasAnyRole("salesHistory-search-post", "GOD", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/salesHistory/best").hasAnyRole("salesHistory-best", "GOD", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/salesHistory/employee").hasAnyRole("salesHistory-employee-get", "GOD", "EMPLOYEE") + .requestMatchers(HttpMethod.GET, "/api/v1/salesHistory/employee/statistics").hasAnyRole("salesHistory-employee-statistics-get", "GOD", "EMPLOYEE") + .requestMatchers(HttpMethod.GET, "/api/v1/salesHistory/employee/statistics/search").hasAnyRole("salesHistory-employee-statistics-search-get", "GOD", "EMPLOYEE") + .requestMatchers(HttpMethod.GET, "/api/v1/salesHistory/employee/statistics/search/year").hasAnyRole("salesHistory-employee-statistics-search-year-get", "GOD", "EMPLOYEE") + .requestMatchers(HttpMethod.GET, "/api/v1/salesHistory/employee/statistics/search/month").hasAnyRole("salesHistory-employee-statistics-search-month-get", "GOD", "DIRECTOR", "ADMIN", "EMPLOYEE") + .requestMatchers(HttpMethod.POST, "/api/v1/salesHistory/employee/search").hasAnyRole("salesHistory-employee-search-post", "GOD", "EMPLOYEE") + + // DashBoard API + .requestMatchers(HttpMethod.GET, "/api/v1/dashBoard/employee").hasAnyRole("dashboard-get", "GOD", "DIRECTOR", "ADMIN", "EMPLOYEE") + .requestMatchers(HttpMethod.GET, "/api/v1/dashBoard/admin").hasAnyRole("dashboard-get", "GOD", "DIRECTOR", "ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/dashBoard/director").hasAnyRole("dashboard-get", "GOD", "DIRECTOR") + + // 그 외 나머지 시스템 관리자만 접근 가능 + .anyRequest().hasRole("GOD") + ); + } +} + + diff --git a/src/main/java/stanl_2/final_backend/global/security/constants/ApplicationConstants.java b/src/main/java/stanl_2/final_backend/global/security/constants/ApplicationConstants.java deleted file mode 100644 index d4125f4b..00000000 --- a/src/main/java/stanl_2/final_backend/global/security/constants/ApplicationConstants.java +++ /dev/null @@ -1,23 +0,0 @@ -package stanl_2.final_backend.global.security.constants; - -import lombok.Getter; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import java.util.Base64; - -@Component -@Getter -public class ApplicationConstants { - - private final byte[] JWT_SECRET_KEY; - private final String JWT_HEADER; - - public ApplicationConstants( - @Value("${jwt.secret-default-value}") String jwtSecretDefaultValue, - @Value("${jwt.header}") String jwtHeader - ) { - this.JWT_SECRET_KEY = Base64.getDecoder().decode(jwtSecretDefaultValue); - this.JWT_HEADER = jwtHeader; - } -} \ No newline at end of file diff --git a/src/main/java/stanl_2/final_backend/global/security/events/AuthenticationEvents.java b/src/main/java/stanl_2/final_backend/global/security/events/AuthenticationEvents.java index 0a4bff1f..91f0ca6e 100644 --- a/src/main/java/stanl_2/final_backend/global/security/events/AuthenticationEvents.java +++ b/src/main/java/stanl_2/final_backend/global/security/events/AuthenticationEvents.java @@ -11,13 +11,13 @@ public class AuthenticationEvents { // 로그인 성공시 발생하는 이벤트 @EventListener - public void onSuccess(AuthenticationSuccessEvent successEvent) { + public void onSuccess(AuthenticationSuccessEvent successEvent){ log.info("로그인 성공 유저: {}", successEvent.getAuthentication().getName()); } // 로그인 실패시 발생하는 이벤트 @EventListener - public void onFailure(AbstractAuthenticationFailureEvent failureEvent) { + public void onFailure(AbstractAuthenticationFailureEvent failureEvent){ log.error("로그인 실패 유저: {} due to: {}", failureEvent.getAuthentication().getName(), failureEvent.getException().getMessage()); } } diff --git a/src/main/java/stanl_2/final_backend/global/security/events/AuthorizationEvents.java b/src/main/java/stanl_2/final_backend/global/security/events/AuthorizationEvents.java index b2e22d6e..fa35dafc 100644 --- a/src/main/java/stanl_2/final_backend/global/security/events/AuthorizationEvents.java +++ b/src/main/java/stanl_2/final_backend/global/security/events/AuthorizationEvents.java @@ -4,16 +4,16 @@ import org.springframework.context.event.EventListener; import org.springframework.security.authorization.event.AuthorizationDeniedEvent; import org.springframework.stereotype.Component; -import stanl_2.final_backend.global.exception.CommonException; -import stanl_2.final_backend.global.exception.ErrorCode; +import stanl_2.final_backend.global.exception.GlobalCommonException; +import stanl_2.final_backend.global.exception.GlobalErrorCode; @Component @Slf4j public class AuthorizationEvents { @EventListener - public void onFailure(AuthorizationDeniedEvent deniedEvent) { + public void onFailure(AuthorizationDeniedEvent deniedEvent){ log.error("권한 없음 유저: {} due to: {}", deniedEvent.getAuthentication().get().getName() , deniedEvent.getAuthorizationDecision().toString()); - throw new CommonException(ErrorCode.FORBIDDEN_ROLE); + throw new GlobalCommonException(GlobalErrorCode.UNAUTHORIZED); } } diff --git a/src/main/java/stanl_2/final_backend/global/security/filter/CsrfCookieFilter.java b/src/main/java/stanl_2/final_backend/global/security/filter/CsrfCookieFilter.java deleted file mode 100644 index 8f075a21..00000000 --- a/src/main/java/stanl_2/final_backend/global/security/filter/CsrfCookieFilter.java +++ /dev/null @@ -1,56 +0,0 @@ -package stanl_2.final_backend.global.security.filter; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.web.csrf.CsrfToken; -import org.springframework.web.filter.OncePerRequestFilter; -import stanl_2.final_backend.global.exception.CommonException; -import stanl_2.final_backend.global.exception.ErrorCode; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Optional; - -@Slf4j -public class CsrfCookieFilter extends OncePerRequestFilter { - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - - // CsrfToken은 Spring Security에서 CSRF 보호를 위한 토큰 - CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); - - if (csrfToken != null) { - // 기존 쿠키 확인 - Cookie[] cookies = request.getCookies(); - Optional csrfCookie = Optional.empty(); - - if (cookies != null) { - csrfCookie = Arrays.stream(cookies) - .filter(cookie -> "XSRF-TOKEN".equals(cookie.getName())) - .findFirst(); - } - - // 쿠키가 존재하지 않거나 토큰이 변경된 경우 쿠키 업데이트 - if (!csrfCookie.isPresent() || !csrfCookie.get().getValue().equals(csrfToken.getToken())) { - // 새로운 CSRF 토큰을 쿠키로 설정 - Cookie newCsrfCookie = new Cookie("XSRF-TOKEN", csrfToken.getToken()); - newCsrfCookie.setPath("/"); - newCsrfCookie.setHttpOnly(false); // JS에서도 접근할 수 있도록 설정 - newCsrfCookie.setSecure(request.isSecure()); // HTTPS인 경우에만 secure 설정 - newCsrfCookie.setMaxAge(-1); // 세션이 종료될 때 쿠키가 삭제되도록 설정 - - response.addCookie(newCsrfCookie); // 응답에 쿠키 추가 - } - } else { - throw new CommonException(ErrorCode.NOT_FOUND_CSRF_TOKEN); - } - - - filterChain.doFilter(request, response); - } -} diff --git a/src/main/java/stanl_2/final_backend/global/security/filter/JWTTokenGeneratorFilter.java b/src/main/java/stanl_2/final_backend/global/security/filter/JWTTokenGeneratorFilter.java deleted file mode 100644 index 8155046f..00000000 --- a/src/main/java/stanl_2/final_backend/global/security/filter/JWTTokenGeneratorFilter.java +++ /dev/null @@ -1,69 +0,0 @@ -package stanl_2.final_backend.global.security.filter; - -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.security.Keys; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.web.filter.OncePerRequestFilter; -import stanl_2.final_backend.domain.member.aggregate.entity.Member; -import stanl_2.final_backend.global.security.constants.ApplicationConstants; - -import javax.crypto.SecretKey; -import java.io.IOException; -import java.util.Date; -import java.util.stream.Collectors; - -@Slf4j -@RequiredArgsConstructor -public class JWTTokenGeneratorFilter extends OncePerRequestFilter { - - private final ApplicationConstants applicationConstants; - - @Override - protected void doFilterInternal(HttpServletRequest request, - HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { - log.info("g1"); - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - log.info("g2"); - if (null != authentication) { - Member member = (Member) authentication.getPrincipal(); - log.info("g3"); - - // Base64로 인코딩된 키를 디코딩하여 SecretKey 생성 - SecretKey secretKey = Keys.hmacShaKeyFor(applicationConstants.getJWT_SECRET_KEY()); - log.info("g4"); - - String jwt = Jwts.builder() - .setIssuer("STANL2") - .setSubject("JWT Token") -// .claim("username", authentication.getName()) -// .claim("id", member.getId()) - - .claim("authorities", authentication.getAuthorities().stream() - .map(GrantedAuthority::getAuthority).collect(Collectors.joining(","))) - .setIssuedAt(new Date()) - .setExpiration(new Date((new Date()).getTime() + 30000000)) - .signWith(secretKey) - .compact(); // Digital Signature 생성 - - log.info("g5"); - // JWT 토큰을 응답 헤더에 추가 - response.setHeader(applicationConstants.getJWT_HEADER(), jwt); - } - log.info("g6"); - filterChain.doFilter(request, response); - } - - @Override - protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { - return !request.getServletPath().equals("/api/v1/auth/signin"); - } -} diff --git a/src/main/java/stanl_2/final_backend/global/security/filter/JWTTokenValidatorFilter.java b/src/main/java/stanl_2/final_backend/global/security/filter/JWTTokenValidatorFilter.java index a531eab9..1f554b5d 100644 --- a/src/main/java/stanl_2/final_backend/global/security/filter/JWTTokenValidatorFilter.java +++ b/src/main/java/stanl_2/final_backend/global/security/filter/JWTTokenValidatorFilter.java @@ -1,105 +1,205 @@ package stanl_2.final_backend.global.security.filter; import io.jsonwebtoken.Claims; +import io.jsonwebtoken.JwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; -import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.OncePerRequestFilter; -import stanl_2.final_backend.global.exception.CommonException; -import stanl_2.final_backend.global.exception.ErrorCode; -import stanl_2.final_backend.global.security.constants.ApplicationConstants; +import stanl_2.final_backend.domain.log.command.aggregate.Log; +import stanl_2.final_backend.domain.log.command.repository.LogRepository; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.global.exception.GlobalCommonException; +import stanl_2.final_backend.global.exception.GlobalErrorCode; import javax.crypto.SecretKey; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.util.Base64; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; @Slf4j -@RequiredArgsConstructor public class JWTTokenValidatorFilter extends OncePerRequestFilter { - private final ApplicationConstants applicationConstants; + private final String jwtSecretKey; + private final LogRepository logRepository; // 로그 저장소 + private final MemberQueryService memberQueryService; + + public JWTTokenValidatorFilter(String jwtSecretKey, + LogRepository logRepository, + MemberQueryService memberQueryService) { + this.jwtSecretKey = jwtSecretKey; + this.logRepository = logRepository; // DI 주입 + this.memberQueryService = memberQueryService; + } @Override - protected void doFilterInternal(HttpServletRequest request, - HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { - - - Cookie[] cookies = request.getCookies(); - if (cookies != null) { - for (Cookie cookie : cookies) { - if ("SESSIONID".equals(cookie.getName())) { - String jwt = cookie.getValue(); - - try { - // 비밀 키를 Base64로 디코딩하여 생성 - byte[] decodedKey = Base64.getDecoder().decode(applicationConstants.getJWT_SECRET_KEY()); - SecretKey secretKey = Keys.hmacShaKeyFor(decodedKey); - - Claims claims = Jwts.parserBuilder() - .setSigningKey(secretKey) - .build() - .parseClaimsJws(jwt) - .getBody(); - - String authorities = (String) claims.get("authorities"); - Authentication authentication = new UsernamePasswordAuthenticationToken( - claims.getSubject(), null, - AuthorityUtils.commaSeparatedStringToAuthorityList(authorities)); - - SecurityContextHolder.getContext().setAuthentication(authentication); - } catch (Exception e) { - throw new CommonException(ErrorCode.INVALID_TOKEN_ERROR); - } + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION); + + if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { + String token = authorizationHeader.substring(7); + try { + SecretKey secretKey = Keys.hmacShaKeyFor(jwtSecretKey.getBytes(StandardCharsets.UTF_8)); + Claims claims = Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token) + .getBody(); + + // 사용자 정보 추출 + String username = claims.get("username", String.class); + String authorities = claims.get("authorities", String.class); + + if (username == null || authorities == null || authorities.isEmpty()) { + log.warn("토큰에 필수 정보가 누락되었습니다. username: {}, authorities: {}", username, authorities); + handleInvalidToken(response, "토큰에 필수 정보가 없습니다.", new JwtException("Missing required claims"), request); + return; } + + List grantedAuthorities = Arrays.stream(authorities.split(",")) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + + Authentication authentication = new UsernamePasswordAuthenticationToken( + username, null, grantedAuthorities); + SecurityContextHolder.getContext().setAuthentication(authentication); + + } catch (JwtException e) { + log.error("JWT 파싱 실패: {}", e.getMessage()); + handleInvalidToken(response, "유효하지 않은 토큰입니다.", e, request); + return; } + } else if (isExemptedPath(request)) { + log.debug("예외 경로 요청. JWT 검증 생략. 요청 URL: {}", request.getRequestURI()); + } else { + log.warn("Authorization 헤더가 없거나 올바르지 않은 형식입니다."); + handleInvalidToken(response, "인증 정보가 없습니다.", new JwtException("Missing Authorization Header"), request); + return; } + filterChain.doFilter(request, response); } -// String jwt = request.getHeader(applicationConstants.getJWT_HEADER()); -// if (null != jwt) { -// try { -// // 비밀키 가져오기 -// SecretKey secretKey = Keys.hmacShaKeyFor(applicationConstants.getJWT_SECRET_KEY()); -// String jwtToken = jwt.substring(7); -// // JWT 토큰 검증 -// Claims claims = Jwts.parserBuilder() -// .setSigningKey(secretKey) -// .build() -// .parseClaimsJws(jwtToken) -// .getBody(); -// -// String username = String.valueOf(claims.get("username")); -// String authorities = String.valueOf(claims.get("authorities")); -// Authentication authentication = new UsernamePasswordAuthenticationToken(username, null, -// AuthorityUtils.commaSeparatedStringToAuthorityList(authorities)); -// -// // SecurityContextHolder에 저장 -// SecurityContextHolder.getContext().setAuthentication(authentication); -// -// } catch (Exception exception) { -// throw new CommonException(ErrorCode.INVALID_TOKEN_ERROR); -// } -// } -// filterChain.doFilter(request, response); -// } + private void handleInvalidToken(HttpServletResponse response, String message, Exception e, HttpServletRequest request) throws IOException { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json;charset=UTF-8"); + + String jsonResponse = String.format( + "{\"code\": 40100, \"msg\": \"%s\", \"httpStatus\": \"UNAUTHORIZED\"}", + message + ); + + response.getWriter().write(jsonResponse); + + log.error("요청 거부됨: {}. Error: {}. Request URI: {}, Method: {}, Client IP: {}", + message, e.getMessage(), request.getRequestURI(), request.getMethod(), getClientIp(request)); + + saveErrorLog(message, e, request); + } + + + private void saveErrorLog(String message, Exception e, HttpServletRequest request) { + try { + Log logEntry = new Log(); + + // 에러 정보 + logEntry.setStatus("ERROR"); + logEntry.setErrorMessage(e.getMessage()); + + // 요청 정보 + logEntry.setUri(safeValue(request.getRequestURI())); + logEntry.setMethod(safeValue(request.getMethod())); + logEntry.setQueryString(safeValue(request.getQueryString())); + + // 유저 정보 + logEntry.setSessionId(safeValue(request.getRequestedSessionId())); + logEntry.setUserAgent(safeValue(request.getHeader("User-Agent"))); + + String loginId = "anonymousUser"; + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication != null && authentication.isAuthenticated() && + !"anonymousUser".equals(authentication.getPrincipal()) && + !authentication.getPrincipal().toString().startsWith("stanl_2") + ) { + loginId = authentication.getPrincipal().toString(); + } + + logEntry.setLoginId(loginId); + + // 네트워크 정보 + logEntry.setIpAddress(safeValue(getClientIp(request))); + logEntry.setHostName(safeValue(request.getRemoteHost())); + logEntry.setRemotePort(request.getRemotePort()); + + // Transaction ID + String transactionId = UUID.randomUUID().toString(); + logEntry.setTransactionId(transactionId); + + logRepository.save(logEntry); + + // 임원 일시 메일 전송 + String pos = memberQueryService.selectMemberInfo(loginId).getPosition(); + + if("DIRECTOR".equals(pos) || "CEO".equals(pos)){ + memberQueryService.sendErrorMail(loginId, logEntry); + } + + } catch (Exception ex) { + log.error("로그 저장 실패: {}", ex.getMessage()); + throw new GlobalCommonException(GlobalErrorCode.FAIL_LOG_SAVE); + } + } + + private String getClientIp(HttpServletRequest request) { + String[] headers = {"X-Forwarded-For", "Proxy-Client-IP", "WL-Proxy-Client-IP", + "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"}; + for (String header : headers) { + String ip = request.getHeader(header); + if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) { + return ip.split(",")[0]; + } + } + return request.getRemoteAddr(); + } + + private String safeValue(String value) { + return value != null ? value : "N/A"; + } + + @Override protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { + return isExemptedPath(request); + } + + private boolean isExemptedPath(HttpServletRequest request) { String path = request.getServletPath(); return path.equals("/api/v1/auth/signin") || - path.equals("/api/v1/auth/signup"); + path.equals("/api/v1/auth/signup") || + path.equals("/api/v1/auth/checkmail") || + path.equals("/api/v1/auth/checknum") || + path.startsWith("/swagger-ui") || + path.startsWith("/v3/api-docs") || + path.startsWith("/swagger-resources") || + path.startsWith("/webjars"); } + } diff --git a/src/main/java/stanl_2/final_backend/global/security/service/MemberPrincipal.java b/src/main/java/stanl_2/final_backend/global/security/service/MemberDetails.java similarity index 50% rename from src/main/java/stanl_2/final_backend/global/security/service/MemberPrincipal.java rename to src/main/java/stanl_2/final_backend/global/security/service/MemberDetails.java index 4d7d2867..62f8461b 100644 --- a/src/main/java/stanl_2/final_backend/global/security/service/MemberPrincipal.java +++ b/src/main/java/stanl_2/final_backend/global/security/service/MemberDetails.java @@ -1,27 +1,34 @@ package stanl_2.final_backend.global.security.service; -import lombok.RequiredArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; -import stanl_2.final_backend.domain.member.aggregate.entity.Member; +import stanl_2.final_backend.domain.member.command.domain.aggregate.entity.Member; import java.util.Collection; -import java.util.Collections; +import java.util.stream.Collectors; -// 사용자 인증 시 필요한 정보 제공 -@RequiredArgsConstructor -public class MemberPrincipal implements UserDetails { +public class MemberDetails implements UserDetails { private final Member member; + public MemberDetails(Member member) { + this.member = member; + } + public Member getMember() { return member; } @Override public Collection getAuthorities() { - return Collections.singletonList(new SimpleGrantedAuthority(member.getRole().name())); + return member.getRoles().stream() + .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getRole())) + .collect(Collectors.toList()); + } + + public String getPosition() { + return member.getPosition(); } @Override @@ -36,21 +43,22 @@ public String getUsername() { @Override public boolean isAccountNonExpired() { - return true; // 비즈니스 로직에 따라 설정 (true로 설정하면 계정 만료 미사용) + return member.getActive(); } @Override public boolean isAccountNonLocked() { - return true; // 비즈니스 로직에 따라 설정 (true로 설정하면 계정 잠금 미사용) + return member.getActive(); } @Override public boolean isCredentialsNonExpired() { - return true; // 비즈니스 로직에 따라 설정 (true로 설정하면 자격 증명 만료 미사용) + return member.getActive(); + } + + @Override + public boolean isEnabled() { + return member.getActive(); } -// @Override -// public boolean isEnabled() { -// return member.isEnabled(); // 활성화 상태에 따라 true/false 반환 -// } } diff --git a/src/main/java/stanl_2/final_backend/global/security/service/MemberDetailsService.java b/src/main/java/stanl_2/final_backend/global/security/service/MemberDetailsService.java index 4e0968ce..f6fbc818 100644 --- a/src/main/java/stanl_2/final_backend/global/security/service/MemberDetailsService.java +++ b/src/main/java/stanl_2/final_backend/global/security/service/MemberDetailsService.java @@ -1,6 +1,36 @@ package stanl_2.final_backend.global.security.service; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import stanl_2.final_backend.domain.member.command.domain.aggregate.entity.Member; +import stanl_2.final_backend.domain.member.command.domain.repository.MemberRepository; +import stanl_2.final_backend.global.exception.GlobalCommonException; +import stanl_2.final_backend.global.exception.GlobalErrorCode; -public interface MemberDetailsService extends UserDetailsService { +@Slf4j +@Service(value = "MemberDetailsService") +public class MemberDetailsService implements UserDetailsService { + + private final MemberRepository memberRepository; + + @Autowired + public MemberDetailsService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + // JPA를 사용하여 로그인 ID로 회원 정보 조회 + Member member = memberRepository.findByLoginId(username); + if (member == null) { + throw new GlobalCommonException(GlobalErrorCode.USER_NOT_FOUND); + } + + // Member를 기반으로 UserDetails 생성 + return new MemberDetails(member); + } } diff --git a/src/main/java/stanl_2/final_backend/global/security/service/MemberDetailsServiceImpl.java b/src/main/java/stanl_2/final_backend/global/security/service/MemberDetailsServiceImpl.java deleted file mode 100644 index c9425b01..00000000 --- a/src/main/java/stanl_2/final_backend/global/security/service/MemberDetailsServiceImpl.java +++ /dev/null @@ -1,28 +0,0 @@ -package stanl_2.final_backend.global.security.service; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Service; -import stanl_2.final_backend.domain.member.aggregate.entity.Member; -import stanl_2.final_backend.domain.member.repository.MemberRepository; - -// 사용자 정보 호출 -@Slf4j -@Service(value="memberDetailsServiceImpl") -@RequiredArgsConstructor -public class MemberDetailsServiceImpl implements MemberDetailsService { - - private final MemberRepository memberRepository; - - @Override - public UserDetails loadUserByUsername(String loginId) throws UsernameNotFoundException { - Member member = memberRepository.findByloginId(loginId); - if(member == null) { - log.info("D1"); - throw new UsernameNotFoundException(loginId); - } - return new MemberPrincipal(member); - } -} diff --git a/src/main/java/stanl_2/final_backend/global/utils/AESUtils.java b/src/main/java/stanl_2/final_backend/global/utils/AESUtils.java new file mode 100644 index 00000000..6cbee768 --- /dev/null +++ b/src/main/java/stanl_2/final_backend/global/utils/AESUtils.java @@ -0,0 +1,60 @@ +package stanl_2.final_backend.global.utils; + +import lombok.extern.slf4j.Slf4j; +import org.apache.tomcat.util.codec.binary.Base64; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; + +@Slf4j +@Component +public class AESUtils { + + @Value("${encryption.algorithm}") + private String algorithm; + + @Value("${encryption.transformation}") + private String transformation; + + @Value("${encryption.secret-key}") + private String secretKeyValue; + + /** + * AES 대칭키를 사용하여 문자열을 암호화합니다. + */ + public String encrypt(String data) throws GeneralSecurityException { + + if(data == null){ + return null; + } + + Cipher cipher = Cipher.getInstance(transformation); + SecretKey secretKey = new SecretKeySpec(secretKeyValue.getBytes(StandardCharsets.UTF_8), algorithm); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)); + return Base64.encodeBase64String(encryptedBytes); + } + + /** + * AES 대칭키를 사용하여 암호화된 문자열을 복호화합니다. + */ + public String decrypt(String encryptedData) throws GeneralSecurityException { + + if(encryptedData == null){ + return null; + } + + + Cipher cipher = Cipher.getInstance(transformation); + SecretKey secretKey = new SecretKeySpec(secretKeyValue.getBytes(StandardCharsets.UTF_8), algorithm); + cipher.init(Cipher.DECRYPT_MODE, secretKey); + byte[] decodedBytes = Base64.decodeBase64(encryptedData); + byte[] decryptedBytes = cipher.doFinal(decodedBytes); + return new String(decryptedBytes, StandardCharsets.UTF_8); + } +} diff --git a/src/main/java/stanl_2/final_backend/global/utils/RequestUtils.java b/src/main/java/stanl_2/final_backend/global/utils/RequestUtils.java deleted file mode 100644 index 3b28bde8..00000000 --- a/src/main/java/stanl_2/final_backend/global/utils/RequestUtils.java +++ /dev/null @@ -1,29 +0,0 @@ -package stanl_2.final_backend.global.utils; - -import jakarta.servlet.http.HttpServletRequest; - - -public class RequestUtils { - - public static String getClientIp(HttpServletRequest request) { - String ip = request.getHeader("X-Forwarded-For"); - - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("Proxy-Client-IP"); - } - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("WL-Proxy-Client-IP"); - } - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("HTTP_CLIENT_IP"); - } - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("HTTP_X_FORWARDED_FOR"); - } - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getRemoteAddr(); - } - - return ip; - } -} \ No newline at end of file diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 00000000..e69de29b diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 00000000..e69de29b diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 2c8c8022..e69de29b 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,43 +0,0 @@ -server: - forward-headers-strategy: native - -spring: - application: - name: finalbackend - - datasource: - driver-class-name: org.mariadb.jdbc.Driver -# url: jdbc:mariadb://${DATABASE_URL}:${MARIA_DATABASE_PORT}/${MARIA_DATABASE_NAME} - url: jdbc:mariadb://motive.cdw8kw8go27z.ap-northeast-2.rds.amazonaws.com:3306/motive - username: admin - password: motivepassword -# username: ${DB_USERNAME} -# password: ${DB_PASSWORD} - - profiles: - active: ${SPRING_PROFILES_ACTIVE} - - devtools: - livereload: - enabled: true - restart: - enabled: false - - jpa: - show-sql: true - hibernate: - ddl-auto: create - properties: - hibernate.format_sql: true -jwt: - secret-key: ${SECRET_KEY} - secret-default-value: c29tZSBzZWN1cmUgMTI4LWJpdCBzZWNyZXQga2V5IGZvciBKU1VfUElQ - header: Authorization - -logging: - pattern: - console: ${LOGPATTERN_CONSOLE:%green(%d{HH:mm:ss.SSS}) %blue(%-5level) %red([%thread]) %yellow(%logger{15}) - %msg%n} - level: - org: - springframework: - security: ${SPRING_SECURITY_LOG_LEVEL:TRACE} \ No newline at end of file diff --git a/src/main/resources/application_dev.yml b/src/main/resources/application_dev.yml deleted file mode 100644 index e9390662..00000000 --- a/src/main/resources/application_dev.yml +++ /dev/null @@ -1,8 +0,0 @@ -spring: - jpa: - hibernate: - ddl-auto: create - -logging: - level: - org.springframework.security: TRACE diff --git a/src/main/resources/application_prod.yml b/src/main/resources/application_prod.yml deleted file mode 100644 index bdb395a7..00000000 --- a/src/main/resources/application_prod.yml +++ /dev/null @@ -1,8 +0,0 @@ -spring: - jpa: - hibernate: - ddl-auto: create - -logging: - level: - org.springframework.security: DEBUG diff --git a/src/main/resources/final_backend/domain/center/query/repository/CenterMapper.xml b/src/main/resources/final_backend/domain/center/query/repository/CenterMapper.xml deleted file mode 100644 index ebfec867..00000000 --- a/src/main/resources/final_backend/domain/center/query/repository/CenterMapper.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/final_backend/domain/schedule/query/repository/ScheduleMapper.xml b/src/main/resources/final_backend/domain/schedule/query/repository/ScheduleMapper.xml deleted file mode 100644 index 2c094da2..00000000 --- a/src/main/resources/final_backend/domain/schedule/query/repository/ScheduleMapper.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/image/admin.png b/src/main/resources/image/admin.png new file mode 100644 index 00000000..c123cce9 Binary files /dev/null and b/src/main/resources/image/admin.png differ diff --git a/src/main/resources/image/default.png b/src/main/resources/image/default.png new file mode 100644 index 00000000..60cef806 Binary files /dev/null and b/src/main/resources/image/default.png differ diff --git a/src/main/resources/image/director.png b/src/main/resources/image/director.png new file mode 100644 index 00000000..dd657843 Binary files /dev/null and b/src/main/resources/image/director.png differ diff --git a/src/main/resources/image/employee.png b/src/main/resources/image/employee.png new file mode 100644 index 00000000..f1788ca3 Binary files /dev/null and b/src/main/resources/image/employee.png differ diff --git a/src/main/resources/image/god.png b/src/main/resources/image/god.png new file mode 100644 index 00000000..48ef3b5a Binary files /dev/null and b/src/main/resources/image/god.png differ diff --git a/src/main/resources/sql/ddl.sql b/src/main/resources/sql/ddl.sql index 2432f0af..e69de29b 100644 --- a/src/main/resources/sql/ddl.sql +++ b/src/main/resources/sql/ddl.sql @@ -1,386 +0,0 @@ -<<<<<<< HEAD -<<<<<<< HEAD --- 자식 테이블부터 삭제 -DROP TABLE IF EXISTS tb_product_option; -DROP TABLE IF EXISTS tb_update_history; -DROP TABLE IF EXISTS tb_member_detail; -DROP TABLE IF EXISTS tb_alarm; -DROP TABLE IF EXISTS tb_schedule; -DROP TABLE IF EXISTS tb_file; -DROP TABLE IF EXISTS tb_promotion; -DROP TABLE IF EXISTS tb_evaluation; -DROP TABLE IF EXISTS tb_purchase_order; -DROP TABLE IF EXISTS tb_problem; -DROP TABLE IF EXISTS tb_order; -DROP TABLE IF EXISTS tb_notice; -DROP TABLE IF EXISTS tb_contract; -DROP TABLE IF EXISTS tb_product; -DROP TABLE IF EXISTS tb_customer_info; -DROP TABLE IF EXISTS tb_member_role; -DROP TABLE IF EXISTS tb_member; -DROP TABLE IF EXISTS tb_center; -DROP TABLE IF EXISTS tb_organization_chart; -DROP TABLE IF EXISTS tb_sales_history; - --- 조직 관련 테이블 생성 -CREATE TABLE tb_organization_chart -( - ORG_CHA_ID VARCHAR(255) NOT NULL, - ORG_CHA_NAME VARCHAR(255) NOT NULL, - ORG_CHA_DEPTH INT NOT NULL, - PRIMARY KEY (ORG_CHA_ID) -); - -CREATE TABLE tb_center -( - CENT_ID VARCHAR(255) NOT NULL, - CENT_NAME VARCHAR(255) NOT NULL, - CENT_ADR VARCHAR(255) NOT NULL, - CENT_PHO VARCHAR(255) NOT NULL, - CENT_MEM_CNT INT NOT NULL, - CREATED_AT CHAR(19) NOT NULL, - UPDATED_AT CHAR(19) NOT NULL DEFAULT 'CURRENT_TIMESTAMP', - DELETED_AT CHAR(19) NULL, - ACTIVE BOOLEAN NOT NULL, - CENT_OPR_AT VARCHAR(255) NOT NULL, - PRIMARY KEY (CENT_ID) -); - -CREATE TABLE tb_member -( - MEM_ID VARCHAR(255) NOT NULL, - MEM_LOGN_ID INT NOT NULL, - MEM_PWD VARCHAR(255) NOT NULL, - MEM_NAME VARCHAR(255) NOT NULL, - MEM_EMA VARCHAR(255) NOT NULL, - MEM_AGE INT NOT NULL, - MEM_SEX VARCHAR(255) NOT NULL DEFAULT 'FEMALE', - MEM_IDEN_NO VARCHAR(255) NOT NULL, - MEM_PHO VARCHAR(255) NOT NULL, - MEM_EMER_PHO VARCHAR(255) NULL, - MEM_ADR VARCHAR(255) NOT NULL, - MEM_NOTE VARCHAR(255) NULL, - MEM_POS VARCHAR(255) NOT NULL DEFAULT 'INTERN', - MEM_GRD VARCHAR(255) NOT NULL DEFAULT 'High School', - MEM_JOB_TYPE VARCHAR(255) NOT NULL, - MEM_MIL VARCHAR(255) NOT NULL DEFAULT 'exemption', - MEM_BANK_NAME VARCHAR(255) NULL, - MEM_ACC VARCHAR(255) NULL, - CREATED_AT CHAR(19) NOT NULL, - UPDATED_AT CHAR(19) NOT NULL, - DELETED_AT CHAR(19) NULL, - ACTIVE BOOLEAN NOT NULL DEFAULT TRUE, - CENTER_ID VARCHAR(255) NOT NULL, - ORG_CHA_ID VARCHAR(255) NOT NULL, - PRIMARY KEY (MEM_ID), - FOREIGN KEY (CENTER_ID) REFERENCES tb_center (CENT_ID), - FOREIGN KEY (ORG_CHA_ID) REFERENCES tb_organization_chart (ORG_CHA_ID) -); - -CREATE TABLE tb_member_role -( - MEM_ROL_ID VARCHAR(255) NOT NULL, - MEM_ROL_NAME VARCHAR(255) NOT NULL DEFAULT '영업 사원', - MEM_ID VARCHAR(255) NOT NULL, - PRIMARY KEY (MEM_ROL_ID), - FOREIGN KEY (MEM_ID) REFERENCES tb_member (MEM_ID) -); - -CREATE TABLE tb_customer_info -( - CUST_ID VARCHAR(255) NOT NULL, - CUST_NAME VARCHAR(255) NOT NULL, - CUST_AGE INT NOT NULL, - CUST_SEX VARCHAR(255) NOT NULL DEFAULT 'FEMALE', - CUST_PHO VARCHAR(255) NOT NULL, - CUST_EMER_PHO VARCHAR(255) NOT NULL, - CUST_EMA VARCHAR(255) NOT NULL, - ACTIVE BOOLEAN NOT NULL DEFAULT TRUE, - MEM_ID VARCHAR(255) NOT NULL, - PRIMARY KEY (CUST_ID), - FOREIGN KEY (MEM_ID) REFERENCES tb_member (MEM_ID) -); - --- 부모 테이블 생성 -CREATE TABLE tb_product -( - PROD_ID VARCHAR(255) NOT NULL, - PROD_SER_NO VARCHAR(255) NOT NULL, - PROD_COST INT NOT NULL, - PROD_NAME VARCHAR(255) NOT NULL, - PROD_STCK INT NOT NULL DEFAULT 0, - CREATED_AT CHAR(19) NOT NULL, - UPDATED_AT CHAR(19) NOT NULL DEFAULT 'CURRENT_TIMESTAMP', - DELETED_AT CHAR(19) NULL, - ACTIVE BOOLEAN NOT NULL DEFAULT TRUE, - PRIMARY KEY (PROD_ID, PROD_SER_NO) -); - -CREATE TABLE tb_contract -( - CONR_ID VARCHAR(255) NOT NULL, - CONR_NAME VARCHAR(255) NOT NULL, - CONR_CUST_NAME VARCHAR(255) NOT NULL, - CONR_CUST_IDEN_NO VARCHAR(255) NOT NULL, - CONR_CUST_ADR VARCHAR(255) NOT NULL, - CONR_CUST_EMA VARCHAR(255) NOT NULL, - CONR_CUST_PHO VARCHAR(255) NOT NULL, - CONR_COMP_NAME VARCHAR(255) NULL, - CONR_CUST_CLA VARCHAR(255) NOT NULL DEFAULT 'PERSONAL', - CONR_CUST_PUR_COND VARCHAR(255) NOT NULL DEFAULT 'CASH', - CONR_SERI_NUM VARCHAR(255) NOT NULL, - CONR_NO_OF_VEH INT NOT NULL DEFAULT 1, - ACTIVE BOOLEAN NOT NULL DEFAULT TRUE, - CREATED_AT CHAR(19) NOT NULL, - UPDATED_AT CHAR(19) NOT NULL, - MEM_ID VARCHAR(255) NOT NULL, - CENT_ID VARCHAR(255) NOT NULL, - CUST_ID2 VARCHAR(255) NOT NULL, - PROD_ID VARCHAR(255) NOT NULL, - PRIMARY KEY (CONR_ID), - FOREIGN KEY (MEM_ID) REFERENCES tb_member (MEM_ID) ON DELETE CASCADE, - FOREIGN KEY (CUST_ID2) REFERENCES tb_customer_info (CUST_ID) ON DELETE CASCADE, - FOREIGN KEY (PROD_ID) REFERENCES tb_product (PROD_ID) ON DELETE CASCADE, - FOREIGN KEY (CENT_ID) REFERENCES tb_center (CENT_ID) ON DELETE CASCADE -); - -CREATE TABLE tb_notice -( - NOT_ID VARCHAR(255) NOT NULL, - NOT_TTL VARCHAR(255) NOT NULL, - NOT_TAG VARCHAR(255) NOT NULL DEFAULT 'ALL', - NOT_CLA VARCHAR(255) NOT NULL DEFAULT 'NORMAL', - NOT_CONT TEXT NOT NULL, - CREATED_AT CHAR(19) NOT NULL, - UPDATED_AT CHAR(19) NOT NULL, - DELETED_AT CHAR(19) NULL, - ACTIVE BOOLEAN NOT NULL DEFAULT TRUE, - MEM_ID VARCHAR(255) NOT NULL, - PRIMARY KEY (NOT_ID), - FOREIGN KEY (MEM_ID) REFERENCES tb_member (MEM_ID) -); - -CREATE TABLE tb_order -( - ORD_ID VARCHAR(255) NOT NULL, - ORD_TTL VARCHAR(255) NOT NULL, - ACTIVE BOOLEAN NOT NULL DEFAULT TRUE, - CREATED_AT CHAR(19) NOT NULL, - UPDATED_AT CHAR(19) NOT NULL, - DELETED_AT CHAR(19) NULL, - ORD_STAT VARCHAR(255) NOT NULL DEFAULT 'WAIT', - CONR_ID VARCHAR(255) NOT NULL, - MEM_ID VARCHAR(255) NOT NULL, - MEM_ID2 VARCHAR(255) NOT NULL, - PRIMARY KEY (ORD_ID), - FOREIGN KEY (MEM_ID) REFERENCES tb_member (MEM_ID) ON DELETE CASCADE, - FOREIGN KEY (MEM_ID2) REFERENCES tb_member (MEM_ID) ON DELETE CASCADE, - FOREIGN KEY (CONR_ID) REFERENCES tb_contract (CONR_ID) ON DELETE CASCADE -); - -CREATE TABLE tb_PROBLEM -( - PROB_ID VARCHAR(255) NOT NULL, - PROB_TTL VARCHAR(255) NOT NULL, - PROB_CONT VARCHAR(255) NOT NULL, - CREATED_AT CHAR(19) NOT NULL, - UPDATED_AT CHAR(19) NOT NULL, - ACTIVE BOOLEAN NOT NULL DEFAULT TRUE, - DELETED_AT CHAR(19) NULL, - CUST_ID VARCHAR(255) NOT NULL, - MEM_ID VARCHAR(255) NOT NULL, - PROD_ID VARCHAR(255) NOT NULL, - PRIMARY KEY (PROB_ID), - FOREIGN KEY (CUST_ID) REFERENCES tb_customer_info (CUST_ID), - FOREIGN KEY (MEM_ID) REFERENCES tb_member (MEM_ID), - FOREIGN KEY (PROD_ID) REFERENCES tb_product (PROD_ID) -); - -CREATE TABLE tb_purchase_order -( - PUR_ORD_ID VARCHAR(255) NOT NULL, - PUR_ORD_TTL VARCHAR(255) NOT NULL, - PUR_CONT TEXT NOT NULL, - CREATED_AT CHAR(19) NOT NULL, - UPDATED_AT CHAR(19) NOT NULL, - DELETED_AT CHAR(19) NULL, - ACTIVE BOOLEAN NOT NULL DEFAULT TRUE, - PUR_ORD_STAT VARCHAR(255) NOT NULL DEFAULT 'WAIT', - ORD_ID VARCHAR(255) NOT NULL, - MEM_ID VARCHAR(255) NOT NULL, - PRIMARY KEY (PUR_ORD_ID), - FOREIGN KEY (ORD_ID) REFERENCES tb_order (ORD_ID), - FOREIGN KEY (MEM_ID) REFERENCES tb_member (MEM_ID) -); - -CREATE TABLE tb_EVALUATION -( - EVAL_ID VARCHAR(255) NOT NULL, - EVAL_TTL VARCHAR(255) NOT NULL, - EVAL_CONT VARCHAR(255) NOT NULL, - CREATED_AT CHAR(19) NOT NULL, - UPDATED_AT CHAR(19) NOT NULL, - DELETED_AT CHAR(19) NULL, - ACTIVE BOOLEAN NOT NULL DEFAULT TRUE, - CENT_ID VARCHAR(255) NOT NULL, - MEM_ID VARCHAR(255) NOT NULL, - PRIMARY KEY (EVAL_ID), - FOREIGN KEY (CENT_ID) REFERENCES tb_center (CENT_ID), - FOREIGN KEY (MEM_ID) REFERENCES tb_member (MEM_ID) -); - -CREATE TABLE tb_promotion -( - PROM_ID VARCHAR(255) NOT NULL, - PROM_TTL VARCHAR(255) NOT NULL, - PROM_CONT VARCHAR(255) NOT NULL, - CREATED_AT CHAR(19) NOT NULL, - UPDATED_AT CHAR(19) NOT NULL, - DELETED_AT CHAR(19) NULL, - ACTIVE BOOLEAN NOT NULL DEFAULT TRUE, - MEM_ID VARCHAR(255) NOT NULL, - PRIMARY KEY (PROM_ID), - FOREIGN KEY (MEM_ID) REFERENCES tb_member (MEM_ID) -); - -CREATE TABLE tb_file -( - FILE_ID VARCHAR(255) NOT NULL, - FILE_NAME VARCHAR(255) NOT NULL, - FILE_URL VARCHAR(255) NOT NULL, - FILE_TYPE VARCHAR(255) NOT NULL, - ACTIVE BOOLEAN NOT NULL DEFAULT TRUE, - CREATED_AT CHAR(19) NOT NULL, - DELETED_AT CHAR(19) NULL, - MEM_ID VARCHAR(255) NOT NULL, - PRIMARY KEY (FILE_ID), - FOREIGN KEY (MEM_ID) REFERENCES tb_member (MEM_ID) -); - -CREATE TABLE tb_schedule -( - SCH_ID VARCHAR(255) NOT NULL, - SCH_NAME VARCHAR(255) NOT NULL, - SCH_CONT VARCHAR(255) NOT NULL, - SCH_RES CHAR(19) NOT NULL, - CREATED_AT CHAR(19) NOT NULL, - UPDATED_AT CHAR(19) NOT NULL, - DELETED_AT CHAR(19) NULL, - ACTIVE BOOLEAN NOT NULL DEFAULT TRUE, - MEM_ID VARCHAR(255) NOT NULL, - PRIMARY KEY (SCH_ID), - FOREIGN KEY (MEM_ID) REFERENCES tb_member (MEM_ID) -); - -CREATE TABLE tb_alarm -( - ALR_ID VARCHAR(255) NOT NULL, - ALR_MSG VARCHAR(255) NOT NULL, - ALR_URL VARCHAR(255) NOT NULL, - ALR_READ_STAT BOOLEAN NOT NULL DEFAULT FALSE, - CREATED_AT CHAR(19) NOT NULL, - MEM_ID VARCHAR(255) NOT NULL, - PRIMARY KEY (ALR_ID), - FOREIGN KEY (MEM_ID) REFERENCES tb_member (MEM_ID) -); - -CREATE TABLE tb_member_detail -( - MEM_DET_ID VARCHAR(255) NOT NULL, - MEM_DET_REL VARCHAR(255) NOT NULL, - MEM_DET_NAME VARCHAR(255) NOT NULL, - MEM_ID VARCHAR(255) NOT NULL, - PRIMARY KEY (MEM_DET_ID), - FOREIGN KEY (MEM_ID) REFERENCES tb_member (MEM_ID) -); - -CREATE TABLE tb_UPDATE_HISTORY -( - UPD_ID VARCHAR(255) NOT NULL, - UPD_IP VARCHAR(255) NOT NULL, - UPDATED_AT CHAR(19) NOT NULL, - MEM_ID VARCHAR(255) NOT NULL, - PRIMARY KEY (UPD_ID), - FOREIGN KEY (MEM_ID) REFERENCES tb_member (MEM_ID) -); - -CREATE TABLE tb_PRODUCT_OPTION -( - PROD_ID VARCHAR(255) NOT NULL, - PROD_SER_NO VARCHAR(255) NOT NULL, - OPT_CNTY CHAR(1) NOT NULL DEFAULT 'K', - OPT_MNFR CHAR(1) NOT NULL DEFAULT 'N', - OPT_VHC_TYPE CHAR(1) NOT NULL, - OPT_CHSS CHAR(1) NOT NULL, - OPT_DTIL_TYPE CHAR(1) NOT NULL, - OPT_BODY_TYPE CHAR(1) NOT NULL, - OPT_SFTY_DVCE CHAR(1) NOT NULL, - OPT_ENGN_CPCT CHAR(1) NOT NULL, - OPT_SCRT_CODE CHAR(1) NOT NULL, - OPT_PRDC_YEAR CHAR(1) NOT NULL, - OPT_PRDC_PLNT CHAR(1) NOT NULL, - OPT_ENGN CHAR(1) NOT NULL DEFAULT '0', - OPT_MSSN CHAR(1) NOT NULL DEFAULT '0', - OPT_TRIM CHAR(1) NOT NULL DEFAULT '0', - OPT_XTNL_COLR CHAR(1) NOT NULL, - OPT_ITNL_COLR CHAR(1) NOT NULL, - OPT_HUD CHAR(1) NOT NULL DEFAULT '0', - OPT_NAVI CHAR(1) NOT NULL DEFAULT '0', - OPT_DRVE_WISE CHAR(1) NOT NULL DEFAULT '0', - OPT_SMRT_CNCT CHAR(1) NOT NULL DEFAULT '0', - OPT_STYL CHAR(1) NOT NULL DEFAULT '0', - OPT_MY_CFRT_PCKG CHAR(1) NOT NULL DEFAULT '0', - OPT_OTDR_PCKG CHAR(1) NOT NULL DEFAULT '0', - OPT_SUN_ROOF CHAR(1) NOT NULL DEFAULT '0', - OPT_SOND CHAR(1) NOT NULL DEFAULT '0', - ACTIVE BOOLEAN NOT NULL DEFAULT TRUE, - PRIMARY KEY (PROD_ID, PROD_SER_NO), - FOREIGN KEY (PROD_ID, PROD_SER_NO) REFERENCES tb_product (PROD_ID, PROD_SER_NO) ON DELETE CASCADE -); - -CREATE TABLE tb_sales_history -( - SAL_HIST_ID VARCHAR(255) NOT NULL COMMENT 'Comment 1번 참고', - CONR_ID VARCHAR(255) NOT NULL COMMENT 'Comment 1번 참고' -); - --- SET foreign_key_checks = 0; --- --- --- -- 테이블 삭제 순서 --- DROP TABLE IF EXISTS MEMBER; --- DROP TABLE IF EXISTS CENTER; --- --- -- 테이블 생성 --- CREATE TABLE member ( --- member_id BIGINT AUTO_INCREMENT PRIMARY KEY, --- member_active BOOLEAN NOT NULL, --- member_created_at TIMESTAMP, --- member_email VARCHAR(255), --- member_login_id VARCHAR(50) UNIQUE, --- member_name VARCHAR(100), --- member_password VARCHAR(255), --- member_phone VARCHAR(15), --- member_role VARCHAR(50), --- member_updated_at TIMESTAMP --- ); --- --- CREATE TABLE center ( --- # CENT_ID VARCHAR(255) NOT NULL PRIMARY KEY, --- CENT_ID BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY, --- CENT_NAME VARCHAR(255) NOT NULL, --- CENT_ADR VARCHAR(255) NOT NULL, --- CENT_PHO VARCHAR(255) NOT NULL, --- CENT_MEM_CNT INTEGER NOT NULL, --- CENT_OPR_AT VARCHAR(255) NOT NULL, --- CREATED_AT CHAR(19) NOT NULL, --- UPDATED_AT CHAR(19) NOT NULL DEFAULT CREATED_AT, --- DELETED_AT CHAR(19) NULL, --- ACTIVE BOOLEAN NOT NULL --- ); --- --- -- 제약 조건 추가 --- --- --- -- 더미 데이터 삽입 --- diff --git a/src/main/resources/stanl_2/final_backend/domain/A_sample/query/repository/SampleMapper.xml b/src/main/resources/stanl_2/final_backend/domain/A_sample/query/repository/SampleMapper.xml index 6e8020d5..9b005431 100644 --- a/src/main/resources/stanl_2/final_backend/domain/A_sample/query/repository/SampleMapper.xml +++ b/src/main/resources/stanl_2/final_backend/domain/A_sample/query/repository/SampleMapper.xml @@ -34,4 +34,20 @@ WHERE A.SAM_ID = #{ id } + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/stanl_2/final_backend/domain/alarm/query/repository/AlarmMapper.xml b/src/main/resources/stanl_2/final_backend/domain/alarm/query/repository/AlarmMapper.xml new file mode 100644 index 00000000..00745d2f --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/alarm/query/repository/AlarmMapper.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/stanl_2/final_backend/domain/career/query/repository/CareerMapper.xml b/src/main/resources/stanl_2/final_backend/domain/career/query/repository/CareerMapper.xml new file mode 100644 index 00000000..8f4e08f5 --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/career/query/repository/CareerMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/stanl_2/final_backend/domain/center/query/repository/CenterMapper.xml b/src/main/resources/stanl_2/final_backend/domain/center/query/repository/CenterMapper.xml new file mode 100644 index 00000000..d490d7f0 --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/center/query/repository/CenterMapper.xml @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/stanl_2/final_backend/domain/certification/query/repository/CertificationMapper.xml b/src/main/resources/stanl_2/final_backend/domain/certification/query/repository/CertificationMapper.xml new file mode 100644 index 00000000..361542c9 --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/certification/query/repository/CertificationMapper.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/stanl_2/final_backend/domain/contract/query/repository/ContractMapper.xml b/src/main/resources/stanl_2/final_backend/domain/contract/query/repository/ContractMapper.xml new file mode 100644 index 00000000..7a6d2bf5 --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/contract/query/repository/ContractMapper.xml @@ -0,0 +1,776 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/stanl_2/final_backend/domain/contract/query/repository/UpdateHistoryMapper.xml b/src/main/resources/stanl_2/final_backend/domain/contract/query/repository/UpdateHistoryMapper.xml new file mode 100644 index 00000000..2caa85f3 --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/contract/query/repository/UpdateHistoryMapper.xml @@ -0,0 +1,14 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/stanl_2/final_backend/domain/customer/query/repository/CustomerMapper.xml b/src/main/resources/stanl_2/final_backend/domain/customer/query/repository/CustomerMapper.xml new file mode 100644 index 00000000..40de28d4 --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/customer/query/repository/CustomerMapper.xml @@ -0,0 +1,250 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/stanl_2/final_backend/domain/education/query/repository/EducationMapper.xml b/src/main/resources/stanl_2/final_backend/domain/education/query/repository/EducationMapper.xml new file mode 100644 index 00000000..67598006 --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/education/query/repository/EducationMapper.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/stanl_2/final_backend/domain/evaluation/query/repository/EvaluationMapper.xml b/src/main/resources/stanl_2/final_backend/domain/evaluation/query/repository/EvaluationMapper.xml new file mode 100644 index 00000000..9b42eb05 --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/evaluation/query/repository/EvaluationMapper.xml @@ -0,0 +1,270 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/stanl_2/final_backend/domain/family/query/repository/FamilyMapper.xml b/src/main/resources/stanl_2/final_backend/domain/family/query/repository/FamilyMapper.xml new file mode 100644 index 00000000..a45cca05 --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/family/query/repository/FamilyMapper.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/stanl_2/final_backend/domain/log/query/repository/LogMapper.xml b/src/main/resources/stanl_2/final_backend/domain/log/query/repository/LogMapper.xml new file mode 100644 index 00000000..17c77076 --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/log/query/repository/LogMapper.xml @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/stanl_2/final_backend/domain/member/query/repository/AuthMapper.xml b/src/main/resources/stanl_2/final_backend/domain/member/query/repository/AuthMapper.xml new file mode 100644 index 00000000..04f24cb1 --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/member/query/repository/AuthMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/stanl_2/final_backend/domain/member/query/repository/MemberMapper.xml b/src/main/resources/stanl_2/final_backend/domain/member/query/repository/MemberMapper.xml new file mode 100644 index 00000000..7f5731ba --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/member/query/repository/MemberMapper.xml @@ -0,0 +1,296 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/stanl_2/final_backend/domain/member/query/repository/MemberRoleMapper.xml b/src/main/resources/stanl_2/final_backend/domain/member/query/repository/MemberRoleMapper.xml new file mode 100644 index 00000000..09e5073f --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/member/query/repository/MemberRoleMapper.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/stanl_2/final_backend/domain/notices/query/repository/NoticeMapper.xml b/src/main/resources/stanl_2/final_backend/domain/notices/query/repository/NoticeMapper.xml new file mode 100644 index 00000000..436723a7 --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/notices/query/repository/NoticeMapper.xml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/stanl_2/final_backend/domain/order/query/repository/OrderMapper.xml b/src/main/resources/stanl_2/final_backend/domain/order/query/repository/OrderMapper.xml new file mode 100644 index 00000000..6c22c9bb --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/order/query/repository/OrderMapper.xml @@ -0,0 +1,539 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/stanl_2/final_backend/domain/organization/query/repository/OrganizationMapper.xml b/src/main/resources/stanl_2/final_backend/domain/organization/query/repository/OrganizationMapper.xml new file mode 100644 index 00000000..bf6b95ba --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/organization/query/repository/OrganizationMapper.xml @@ -0,0 +1,18 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/stanl_2/final_backend/domain/problem/query/repository/ProblemMapper.xml b/src/main/resources/stanl_2/final_backend/domain/problem/query/repository/ProblemMapper.xml new file mode 100644 index 00000000..229e6697 --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/problem/query/repository/ProblemMapper.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/stanl_2/final_backend/domain/product/query/repository/ProductMapper.xml b/src/main/resources/stanl_2/final_backend/domain/product/query/repository/ProductMapper.xml new file mode 100644 index 00000000..7ec87675 --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/product/query/repository/ProductMapper.xml @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/stanl_2/final_backend/domain/promotion/query/repository/PromotionMapper.xml b/src/main/resources/stanl_2/final_backend/domain/promotion/query/repository/PromotionMapper.xml new file mode 100644 index 00000000..ba5e0b80 --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/promotion/query/repository/PromotionMapper.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/stanl_2/final_backend/domain/purchase_order/query/repository/PurchaseOrderMapper.xml b/src/main/resources/stanl_2/final_backend/domain/purchase_order/query/repository/PurchaseOrderMapper.xml new file mode 100644 index 00000000..0c81dd83 --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/purchase_order/query/repository/PurchaseOrderMapper.xml @@ -0,0 +1,404 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/stanl_2/final_backend/domain/sales_history/query/repository/SalesHistoryMapper.xml b/src/main/resources/stanl_2/final_backend/domain/sales_history/query/repository/SalesHistoryMapper.xml new file mode 100644 index 00000000..ab93afaa --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/sales_history/query/repository/SalesHistoryMapper.xml @@ -0,0 +1,995 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/stanl_2/final_backend/domain/schedule/query/repository/ScheduleMapper.xml b/src/main/resources/stanl_2/final_backend/domain/schedule/query/repository/ScheduleMapper.xml index c0a90c39..a84bf589 100644 --- a/src/main/resources/stanl_2/final_backend/domain/schedule/query/repository/ScheduleMapper.xml +++ b/src/main/resources/stanl_2/final_backend/domain/schedule/query/repository/ScheduleMapper.xml @@ -2,3 +2,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/stanl_2/final_backend/domain/schedule/query/repository/sample.xml b/src/main/resources/stanl_2/final_backend/domain/schedule/query/repository/sample.xml new file mode 100644 index 00000000..2037307e --- /dev/null +++ b/src/main/resources/stanl_2/final_backend/domain/schedule/query/repository/sample.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/templates/errorMail.html b/src/main/resources/templates/errorMail.html new file mode 100644 index 00000000..60f39997 --- /dev/null +++ b/src/main/resources/templates/errorMail.html @@ -0,0 +1,89 @@ + + + + + + + Error Notification + + + +
+

🚨 시스템 에러 알림

+

안녕하세요,

+

다음과 같은 에러가 발생했습니다. 빠른 확인 및 조치를 부탁드립니다:

+ +

유저 정보

+
+

Login ID: [Login ID]

+

Name: [Name]

+

Position: [Position]

+

User Agent: [User Agent]

+
+ +

에러 정보

+
+

상태: ERROR

+

에러 메시지: [에러 메시지]

+
+ +

요청 정보

+
+

URI: [URI]

+

Method: [Method]

+

Query String: [Query String]

+
+ +

네트워크 정보

+
+

IP 주소: [IP Address]

+

호스트 이름: [Host Name]

+

포트 번호: [Port]

+
+ +

감사합니다.

+
+ + + diff --git a/src/main/resources/templates/mail.html b/src/main/resources/templates/mail.html new file mode 100644 index 00000000..964945de --- /dev/null +++ b/src/main/resources/templates/mail.html @@ -0,0 +1,60 @@ + + + + + + + Email Confirmation + + + +
+

STANL2 본인 확인

+

안녕하세요,

+

본인 확인을 위해 아래 인증 코드를 입력해 주세요:

+
+ + [인증 코드] +
+

감사합니다.

+
+ + + diff --git a/src/main/resources/templates/pwdMail.html b/src/main/resources/templates/pwdMail.html new file mode 100644 index 00000000..c42cf423 --- /dev/null +++ b/src/main/resources/templates/pwdMail.html @@ -0,0 +1,61 @@ + + + + + + + Temporary Password + + + +
+

STANL2 임시 비밀번호 발급

+

안녕하세요,

+

요청하신 임시 비밀번호가 발급되었습니다. 아래의 비밀번호를 사용해 로그인하신 후, 꼭 비밀번호를 변경해 주세요:

+
+ + [임시 비밀번호] +
+

감사합니다.

+
+ + + diff --git a/src/test/java/stanl_2/final_backend/domain/notices/command/domain/service/NoticeServiceTests.java b/src/test/java/stanl_2/final_backend/domain/notices/command/domain/service/NoticeServiceTests.java new file mode 100644 index 00000000..bbaa8eec --- /dev/null +++ b/src/test/java/stanl_2/final_backend/domain/notices/command/domain/service/NoticeServiceTests.java @@ -0,0 +1,93 @@ +package stanl_2.final_backend.domain.notices.command.domain.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.modelmapper.ModelMapper; +import stanl_2.final_backend.domain.alarm.command.application.service.AlarmCommandService; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.domain.notices.command.application.dto.NoticeAlarmDTO; +import stanl_2.final_backend.domain.notices.command.application.dto.NoticeDeleteDTO; +import stanl_2.final_backend.domain.notices.command.application.dto.NoticeRegistDTO; +import stanl_2.final_backend.domain.notices.command.domain.aggragate.entity.Notice; +import stanl_2.final_backend.domain.notices.command.domain.repository.NoticeRepository; +import stanl_2.final_backend.global.redis.RedisService; + +import java.security.Principal; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class NoticeServiceTests { + + @InjectMocks + private NoticeCommandServiceImpl noticeCommandService; + + @Mock + private NoticeRepository noticeRepository; + + @Mock + private RedisService redisService; + + @Mock + private AuthQueryService authQueryService; + + @Mock + private AlarmCommandService alarmCommandService; + + @Mock + private MemberQueryService memberQueryService; + + @Mock + private ModelMapper modelMapper; + + @Mock + private Principal principal; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void 게시글_등록() throws Exception { + NoticeRegistDTO registDTO = new NoticeRegistDTO(); + registDTO.setMemberLoginId("loginId"); + Notice notice = new Notice(); + when(modelMapper.map(registDTO, Notice.class)).thenReturn(notice); + when(noticeRepository.save(notice)).thenReturn(notice); + NoticeAlarmDTO alarmDTO = new NoticeAlarmDTO(); + when(modelMapper.map(notice, NoticeAlarmDTO.class)).thenReturn(alarmDTO); + + + noticeCommandService.registerNotice(registDTO, principal); + + + verify(alarmCommandService).sendNoticeAlarm(alarmDTO); + } + + + + @Test + void 게시글_삭제() { + // Arrange + NoticeDeleteDTO deleteDTO = new NoticeDeleteDTO(); + deleteDTO.setNoticeId("validId"); + + Notice notice = new Notice(); + notice.setMemberId("memberId"); + when(noticeRepository.findByNoticeId("validId")).thenReturn(Optional.of(notice)); + when(principal.getName()).thenReturn("memberId"); + + // Act + noticeCommandService.deleteNotice(deleteDTO, principal); + + // Assert + verify(noticeRepository).save(notice); + assertNotNull(notice.getDeletedAt()); + } +} diff --git a/src/test/java/stanl_2/final_backend/domain/problem/command/domain/aggregate/service/ProblemServiceTests.java b/src/test/java/stanl_2/final_backend/domain/problem/command/domain/aggregate/service/ProblemServiceTests.java new file mode 100644 index 00000000..a50ed39e --- /dev/null +++ b/src/test/java/stanl_2/final_backend/domain/problem/command/domain/aggregate/service/ProblemServiceTests.java @@ -0,0 +1,94 @@ +package stanl_2.final_backend.domain.problem.command.domain.aggregate.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.modelmapper.ModelMapper; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.domain.problem.command.application.dto.ProblemRegistDTO; +import stanl_2.final_backend.domain.problem.command.domain.aggregate.entity.Problem; +import stanl_2.final_backend.domain.problem.command.domain.aggregate.repository.ProblemRepository; +import stanl_2.final_backend.global.redis.RedisService; + +import java.security.Principal; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class ProblemServiceTests { + + @InjectMocks + private ProblemServiceImpl problemService; + @Mock + private ProblemRepository problemRepository; + + @Mock + private AuthQueryService authQueryService; + + @Mock + private MemberQueryService memberQueryService; + + @Mock + private RedisService redisService; + + @Mock + private ModelMapper modelMapper; + + @Mock + private Principal principal; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void 문제사항_등록() throws Exception { + ProblemRegistDTO registDTO = new ProblemRegistDTO(); + registDTO.setMemberLoginId("loginId"); + registDTO.setContent("contentTest"); + when(authQueryService.selectMemberIdByLoginId("loginId")).thenReturn("memberId"); + when(memberQueryService.selectNameById("memberId")).thenReturn("memberName"); + Problem problem = new Problem(); + when(modelMapper.map(registDTO, Problem.class)).thenReturn(problem); + when(problemRepository.save(problem)).thenReturn(problem); + + + problemService.registerProblem(registDTO, principal); + + + verify(redisService).clearProblemCache(); + verify(authQueryService).selectMemberIdByLoginId("loginId"); + verify(memberQueryService).selectNameById("memberId"); + verify(problemRepository).save(problem); + } + + + @Test + void 문제사항_상태수정() { + Problem problem = new Problem(); + problem.setMemberId("memberId"); + when(problemRepository.findById("problemId")).thenReturn(Optional.of(problem)); + when(principal.getName()).thenReturn("memberId1"); + + problemService.modifyStatus("problemId", principal); + + assertEquals("DONE", problem.getStatus()); + } + + @Test + void 문제사항_삭제() { + Problem problem = new Problem(); + problem.setMemberId("memberId"); + when(problemRepository.findById("problemId")).thenReturn(Optional.of(problem)); + when(principal.getName()).thenReturn("memberId"); + + problemService.deleteProblem("problemId", principal); + + assertNotNull(problem.getDeletedAt()); + } +} diff --git a/src/test/java/stanl_2/final_backend/domain/promotion/command/domain/aggregate/service/PromotionServiceTests.java b/src/test/java/stanl_2/final_backend/domain/promotion/command/domain/aggregate/service/PromotionServiceTests.java new file mode 100644 index 00000000..f82eebec --- /dev/null +++ b/src/test/java/stanl_2/final_backend/domain/promotion/command/domain/aggregate/service/PromotionServiceTests.java @@ -0,0 +1,92 @@ +package stanl_2.final_backend.domain.promotion.command.domain.aggregate.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.modelmapper.ModelMapper; +import stanl_2.final_backend.domain.member.query.service.AuthQueryService; +import stanl_2.final_backend.domain.member.query.service.MemberQueryService; +import stanl_2.final_backend.domain.promotion.command.application.dto.PromotionRegistDTO; +import stanl_2.final_backend.domain.promotion.command.domain.aggregate.entity.Promotion; +import stanl_2.final_backend.domain.promotion.command.domain.aggregate.repository.PromotionRepository; +import stanl_2.final_backend.global.redis.RedisService; + +import java.security.GeneralSecurityException; +import java.security.Principal; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class PromotionServiceTests { + + @InjectMocks + private PromotionServiceImpl promotionService; + + @Mock + private PromotionRepository promotionRepository; + + @Mock + private AuthQueryService authQueryService; + + @Mock + private MemberQueryService memberQueryService; + + @Mock + private RedisService redisService; + + @Mock + private ModelMapper modelMapper; + + @Mock + private Principal principal; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + // 테스트 1: registerPromotion 성공 케이스 + @Test + void 프로모션_등록() throws Exception { + PromotionRegistDTO registDTO = new PromotionRegistDTO(); + registDTO.setMemberLoginId("loginId"); + registDTO.setContent("promotion content"); + + when(authQueryService.selectMemberIdByLoginId("loginId")).thenReturn("memberId"); + when(memberQueryService.selectNameById("memberId")).thenReturn("memberName"); + + Promotion promotion = new Promotion(); + when(modelMapper.map(registDTO, Promotion.class)).thenReturn(promotion); + when(promotionRepository.save(promotion)).thenReturn(promotion); + + promotionService.registerPromotion(registDTO, principal); + + verify(redisService).clearPromotionCache(); + verify(authQueryService).selectMemberIdByLoginId("loginId"); + verify(memberQueryService).selectNameById("memberId"); + verify(promotionRepository).save(promotion); + } + + @Test + void 프로모션_삭제() throws GeneralSecurityException { + // Arrange + String promotionId = "test"; + String loginId = "loginId"; + String memberId = "memberId"; + + Promotion promotion = new Promotion(); + promotion.setMemberId(memberId); + + when(principal.getName()).thenReturn(loginId); + when(authQueryService.selectMemberIdByLoginId(loginId)).thenReturn(memberId); + when(memberQueryService.selectNameById(memberId)).thenReturn(memberId); + when(promotionRepository.findById(promotionId)).thenReturn(Optional.of(promotion)); + + promotionService.deletePromotion(promotionId, principal); + + assertNotNull(promotion.getDeletedAt()); + } +} \ No newline at end of file