diff --git a/src/main/java/com/susanghan_guys/server/personalwork/application/DcaBriefEvaluationService.java b/src/main/java/com/susanghan_guys/server/personalwork/application/DcaBriefEvaluationService.java index a46c81e..19f8632 100644 --- a/src/main/java/com/susanghan_guys/server/personalwork/application/DcaBriefEvaluationService.java +++ b/src/main/java/com/susanghan_guys/server/personalwork/application/DcaBriefEvaluationService.java @@ -69,4 +69,9 @@ private DcaBriefEvaluationResponse getOrCreateBriefAnalysis(Long workId) { }); } + @Transactional + public void runBriefInternal(Long workId) { + getOrCreateBriefAnalysis(workId); + } + } \ No newline at end of file diff --git a/src/main/java/com/susanghan_guys/server/personalwork/application/DcaWorkEvaluationService.java b/src/main/java/com/susanghan_guys/server/personalwork/application/DcaWorkEvaluationService.java index 29eecec..8f1e008 100644 --- a/src/main/java/com/susanghan_guys/server/personalwork/application/DcaWorkEvaluationService.java +++ b/src/main/java/com/susanghan_guys/server/personalwork/application/DcaWorkEvaluationService.java @@ -124,4 +124,9 @@ private List getOrCreateDetailEvaluation(Long workId, EvaluationType return detailEvals; } + @Transactional + public void runDcaEvaluationInternal(Long workId) { + getOrCreateEvaluation(workId); + } + } diff --git a/src/main/java/com/susanghan_guys/server/personalwork/application/ReportInternalService.java b/src/main/java/com/susanghan_guys/server/personalwork/application/ReportInternalService.java new file mode 100644 index 0000000..ec83a82 --- /dev/null +++ b/src/main/java/com/susanghan_guys/server/personalwork/application/ReportInternalService.java @@ -0,0 +1,98 @@ +package com.susanghan_guys.server.personalwork.application; + +import com.susanghan_guys.server.personalwork.domain.Evaluation; +import com.susanghan_guys.server.personalwork.domain.type.EvaluationType; +import com.susanghan_guys.server.personalwork.dto.response.ReportPipelineResponse; +import com.susanghan_guys.server.personalwork.exception.PersonalWorkException; +import com.susanghan_guys.server.personalwork.exception.code.PersonalWorkErrorCode; +import com.susanghan_guys.server.personalwork.infrastructure.persistence.DetailEvalRepository; +import com.susanghan_guys.server.personalwork.infrastructure.persistence.EvaluationRepository; +import com.susanghan_guys.server.work.domain.Work; +import com.susanghan_guys.server.work.domain.type.WorkType; +import com.susanghan_guys.server.work.exception.WorkException; +import com.susanghan_guys.server.work.exception.code.WorkErrorCode; +import com.susanghan_guys.server.work.infrastructure.persistence.WorkRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ReportInternalService { + + private final WorkRepository workRepository; + private final WorkSummaryService workSummaryService; + private final DcaBriefEvaluationService dcaBriefEvaluationService; + private final DcaWorkEvaluationService dcaWorkEvaluationService; + private final YccWorkEvaluationService yccWorkEvaluationService; + private final EvaluationRepository evaluationRepository; + private final DetailEvalRepository detailEvalRepository; + + + @Transactional + public ReportPipelineResponse runPipeline(Long workId) { + boolean summaryDone = false; + boolean briefDone = false; + boolean evaluationDone = false; + + Work work = workRepository.findById(workId) + .orElseThrow(() -> new WorkException(WorkErrorCode.WORK_NOT_FOUND)); + + workSummaryService.runSummaryInternal(workId); + summaryDone = true; + + if (work.getType() == WorkType.DCA) { + dcaBriefEvaluationService.runBriefInternal(workId); + briefDone = true; + } + + runEvaluationInternal(workId); + evaluationDone = true; + + return ReportPipelineResponse.builder() + .summaryDone(summaryDone) + .briefDone(briefDone) + .evaluationDone(evaluationDone) + .build(); + } + + @Transactional + public void deleteAllEvaluationsByWorkId(Long workId) { + // 작품 유효성 + workRepository.findById(workId) + .orElseThrow(() -> new PersonalWorkException(PersonalWorkErrorCode.WORK_NOT_FOUND)); + + // 부모 엔티티를 엔티티 삭제(remove)로 지워야 orphanRemoval이 동작함 + List evaluations = evaluationRepository.findAllByWorkId(workId); + if (!evaluations.isEmpty()) { + evaluationRepository.deleteAll(evaluations); // 각 엔티티 remove -> 자식(DetailEval)도 함께 제거 + } + } + + @Transactional + public void deleteDetailEvalsByWorkIdAndType(Long workId, EvaluationType type) { + workRepository.findById(workId) + .orElseThrow(() -> new PersonalWorkException(PersonalWorkErrorCode.WORK_NOT_FOUND)); + + Evaluation evaluation = evaluationRepository.findByWorkIdAndType(workId, type) + .orElseThrow(() -> new PersonalWorkException(PersonalWorkErrorCode.EVALUATION_NOT_FOUND)); + + if (!evaluation.getDetails().isEmpty()) { + detailEvalRepository.deleteAll(evaluation.getDetails()); + evaluation.getDetails().clear(); + } + } + + public void runEvaluationInternal(Long workId) { + Work work = workRepository.findById(workId) + .orElseThrow(() -> new WorkException(WorkErrorCode.WORK_NOT_FOUND)); + + switch (work.getType()) { + case DCA -> dcaWorkEvaluationService.runDcaEvaluationInternal(workId); + case YCC -> yccWorkEvaluationService.runYccEvaluationInternal(workId); + default -> throw new PersonalWorkException(PersonalWorkErrorCode.UNSUPPORTED_WORK_TYPE); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/susanghan_guys/server/personalwork/application/WorkSummaryService.java b/src/main/java/com/susanghan_guys/server/personalwork/application/WorkSummaryService.java index 85046e6..14ae68f 100644 --- a/src/main/java/com/susanghan_guys/server/personalwork/application/WorkSummaryService.java +++ b/src/main/java/com/susanghan_guys/server/personalwork/application/WorkSummaryService.java @@ -103,4 +103,16 @@ private Summary getOrCreateYccWorkSummary(Long workId) { }); } + @Transactional + public void runSummaryInternal(Long workId) { + Work work = workRepository.findById(workId) + .orElseThrow(() -> new WorkException(WorkErrorCode.WORK_NOT_FOUND)); + + Summary summary = switch (work.getType()) { + case DCA -> getOrCreateDcaWorkSummary(workId); + case YCC -> getOrCreateYccWorkSummary(workId); + default -> throw new PersonalWorkException(PersonalWorkErrorCode.UNSUPPORTED_WORK_TYPE); + }; + } + } diff --git a/src/main/java/com/susanghan_guys/server/personalwork/application/YccWorkEvaluationService.java b/src/main/java/com/susanghan_guys/server/personalwork/application/YccWorkEvaluationService.java index 1097d84..b8b1c27 100644 --- a/src/main/java/com/susanghan_guys/server/personalwork/application/YccWorkEvaluationService.java +++ b/src/main/java/com/susanghan_guys/server/personalwork/application/YccWorkEvaluationService.java @@ -133,4 +133,9 @@ private List getOrCreateDetailEvaluation(Long workId, EvaluationType return detailEvals; } + + @Transactional + public void runYccEvaluationInternal(Long workId) { + getOrCreateEvaluation(workId); + } } diff --git a/src/main/java/com/susanghan_guys/server/personalwork/dto/response/ReportPipelineResponse.java b/src/main/java/com/susanghan_guys/server/personalwork/dto/response/ReportPipelineResponse.java new file mode 100644 index 0000000..dcafc61 --- /dev/null +++ b/src/main/java/com/susanghan_guys/server/personalwork/dto/response/ReportPipelineResponse.java @@ -0,0 +1,18 @@ +package com.susanghan_guys.server.personalwork.dto.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; + +@Builder +@Schema(description = "리포트 생성 파이프라인 결과") +public record ReportPipelineResponse( + @Schema(description = "요약 완료 여부") + boolean summaryDone, + + @Schema(description = "브리프 해석 완료 여부 (DCA만)") + boolean briefDone, + + @Schema(description = "총평 완료 여부") + boolean evaluationDone +) {} \ No newline at end of file diff --git a/src/main/java/com/susanghan_guys/server/personalwork/exception/code/PersonalWorkErrorCode.java b/src/main/java/com/susanghan_guys/server/personalwork/exception/code/PersonalWorkErrorCode.java index 16152dc..fec62df 100644 --- a/src/main/java/com/susanghan_guys/server/personalwork/exception/code/PersonalWorkErrorCode.java +++ b/src/main/java/com/susanghan_guys/server/personalwork/exception/code/PersonalWorkErrorCode.java @@ -16,6 +16,7 @@ public enum PersonalWorkErrorCode implements BaseCode { SUMMARY_NOT_FOUND(HttpStatus.NOT_FOUND, 404, "출품작 요약을 찾을 수 없습니다."), DETAIL_EVALUATION_TYPE_NOT_FOUND(HttpStatus.NOT_FOUND, 404, "해당 타입의 세부 총평을 찾을 수 없습니다."), BRIEF_ANALYSIS_NOT_FOUND(HttpStatus.NOT_FOUND, 404, "브리프 해석을 찾을 수 없습니다."), + UNSUPPORTED_WORK_TYPE(HttpStatus.BAD_REQUEST, 400, "실행할 수 없는 타입의 출품작입니다."), ; private final HttpStatus httpStatus; diff --git a/src/main/java/com/susanghan_guys/server/personalwork/presentation/ReportInternalController.java b/src/main/java/com/susanghan_guys/server/personalwork/presentation/ReportInternalController.java new file mode 100644 index 0000000..0d4fa9c --- /dev/null +++ b/src/main/java/com/susanghan_guys/server/personalwork/presentation/ReportInternalController.java @@ -0,0 +1,43 @@ +package com.susanghan_guys.server.personalwork.presentation; + +import com.susanghan_guys.server.global.common.CommonResponse; +import com.susanghan_guys.server.personalwork.application.ReportInternalService; +import com.susanghan_guys.server.personalwork.domain.type.EvaluationType; +import com.susanghan_guys.server.personalwork.dto.response.ReportPipelineResponse; +import com.susanghan_guys.server.personalwork.presentation.swagger.ReportInternalSwagger; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import static com.susanghan_guys.server.personalwork.presentation.response.PersonalWorkSuccessCode.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/v1/internal/reports") +public class ReportInternalController implements ReportInternalSwagger { + + private final ReportInternalService reportInternalService; + + @Override + @PostMapping("/{workId}/pipeline") + public CommonResponse runPipeline(@PathVariable Long workId) { + ReportPipelineResponse result = reportInternalService.runPipeline(workId); + return CommonResponse.success(REPORT_PIPELINE_SUCCESS, result); + } + + @Override + @DeleteMapping("/{workId}/evaluations") + public CommonResponse deleteAllEvaluations(@PathVariable Long workId) { + reportInternalService.deleteAllEvaluationsByWorkId(workId); + return CommonResponse.success(WORK_EVALUATIONS_DELETE_SUCCESS, "OK"); + } + + @Override + @DeleteMapping("/{workId}/evaluations/{type}/details") + public CommonResponse deleteDetailEvaluations( + @PathVariable Long workId, + @PathVariable EvaluationType type + ) { + reportInternalService.deleteDetailEvalsByWorkIdAndType(workId, type); + return CommonResponse.success(DETAIL_EVALUATION_DELETE_SUCCESS, "OK"); + } +} \ No newline at end of file diff --git a/src/main/java/com/susanghan_guys/server/personalwork/presentation/response/PersonalWorkSuccessCode.java b/src/main/java/com/susanghan_guys/server/personalwork/presentation/response/PersonalWorkSuccessCode.java index 5e87607..1cd0054 100644 --- a/src/main/java/com/susanghan_guys/server/personalwork/presentation/response/PersonalWorkSuccessCode.java +++ b/src/main/java/com/susanghan_guys/server/personalwork/presentation/response/PersonalWorkSuccessCode.java @@ -23,7 +23,11 @@ public enum PersonalWorkSuccessCode implements BaseCode { WORK_WEAKNESSES_SUCCESS(HttpStatus.OK, 200, "Work Weaknesses Retrieval Success"), WORK_DETAILS_FETCH_SUCCESS(HttpStatus.OK, 200, "Work Detail Evaluation Retrieval Success"), - WORK_SUMMARY_SUCCESS(HttpStatus.OK, 200, "Work Summary Retrieval Success") + WORK_SUMMARY_SUCCESS(HttpStatus.OK, 200, "Work Summary Retrieval Success"), + + WORK_EVALUATIONS_DELETE_SUCCESS(HttpStatus.OK, 200, "Work Evaluations Delete Success"), + DETAIL_EVALUATION_DELETE_SUCCESS(HttpStatus.OK, 200, "Detail Evaluations Delete Success"), + REPORT_PIPELINE_SUCCESS(HttpStatus.OK, 200, "Report Pipeline Success"), ; private final HttpStatus httpStatus; diff --git a/src/main/java/com/susanghan_guys/server/personalwork/presentation/swagger/ReportInternalSwagger.java b/src/main/java/com/susanghan_guys/server/personalwork/presentation/swagger/ReportInternalSwagger.java new file mode 100644 index 0000000..77e86f5 --- /dev/null +++ b/src/main/java/com/susanghan_guys/server/personalwork/presentation/swagger/ReportInternalSwagger.java @@ -0,0 +1,54 @@ +package com.susanghan_guys.server.personalwork.presentation.swagger; + +import com.susanghan_guys.server.global.common.CommonResponse; +import com.susanghan_guys.server.personalwork.domain.type.EvaluationType; +import com.susanghan_guys.server.personalwork.dto.response.ReportPipelineResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.PathVariable; + +@Tag(name = "[백엔드 내부 - 리포트 생성 및 삭제]", description = "요약/브리프/총평을 일괄 생성 API") +public interface ReportInternalSwagger { + + @Operation( + summary = "리포트 파이프라인 실행 API(workType에 따라 분기)", + description = """ + ### Pathvariable + `workId` : 작품 고유 ID + + ### 동작 + - DCA: 요약 → 브리프 해석 → 총평 + - YCC: 요약 → 총평 + """ + ) + @ApiResponse(responseCode = "200", description = "수상 리포트 생성 파이프라인이 실행되었습니다.") + CommonResponse runPipeline(@PathVariable Long workId); + + @Operation( + summary = "작품 전체 총평/세부 총평 삭제 API(내부용)", + description = """ + ### Pathvariable + - `workId`: 작품 고유 ID + + ### 동작 + - 해당 작품의 모든 Evaluation(전체 총평) 삭제 + - 각 Evaluation에 매핑된 DetailEval도 함께 삭제 + """ + ) + CommonResponse deleteAllEvaluations(@PathVariable Long workId); + + @Operation( + summary = "작품 특정 타입 총평/세부 총평 삭제 API(내부용)", + description = """ + ### Pathvariable + - `workId`: 작품 고유 ID + - `type`: EvaluationType (예: TARGET_FITNESS, BRAND_UNDERSTANDING, ...) + + ### 동작 + - 지정한 작품(workId)의 특정 타입 Evaluation은 그대로 두고, + 그 Evaluation에 연결된 DetailEval(세부 총평)만 삭제합니다. + """ + ) + CommonResponse deleteDetailEvaluations(@PathVariable Long workId, @PathVariable EvaluationType type); +} \ No newline at end of file