diff --git a/src/main/java/com/susanghan_guys/server/feedback/application/FeedbackService.java b/src/main/java/com/susanghan_guys/server/feedback/application/FeedbackService.java index 229b7b7d..c289332c 100644 --- a/src/main/java/com/susanghan_guys/server/feedback/application/FeedbackService.java +++ b/src/main/java/com/susanghan_guys/server/feedback/application/FeedbackService.java @@ -6,11 +6,14 @@ import com.susanghan_guys.server.feedback.exception.code.FeedbackErrorCode; import com.susanghan_guys.server.feedback.infrastructure.mapper.FeedbackMapper; import com.susanghan_guys.server.feedback.infrastructure.persistence.FeedbackRepository; +import com.susanghan_guys.server.global.security.CurrentUserProvider; +import com.susanghan_guys.server.user.domain.User; import com.susanghan_guys.server.work.domain.Work; 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.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -21,18 +24,21 @@ public class FeedbackService { private final WorkRepository workRepository; private final FeedbackRepository feedbackRepository; + private final CurrentUserProvider currentUserProvider; @Transactional public void createFeedback(Long workId, FeedbackRequest request) { + User user = currentUserProvider.getCurrentUser(); + Work work = workRepository.findById(workId) .orElseThrow(() -> new WorkException(WorkErrorCode.WORK_NOT_FOUND)); - if (feedbackRepository.existsByWorkId(workId)) { + Feedback feedback = FeedbackMapper.toEntity(request.score(), request.content(), user, work); + + try { + feedbackRepository.save(feedback); + } catch (DataIntegrityViolationException e) { throw new FeedbackException(FeedbackErrorCode.FEEDBACK_ALREADY_EXIST); } - - Feedback feedback = FeedbackMapper.toEntity(request.score(), request.content(), work); - - feedbackRepository.save(feedback); } } diff --git a/src/main/java/com/susanghan_guys/server/feedback/domain/Feedback.java b/src/main/java/com/susanghan_guys/server/feedback/domain/Feedback.java index ceec3bfe..d92555f0 100644 --- a/src/main/java/com/susanghan_guys/server/feedback/domain/Feedback.java +++ b/src/main/java/com/susanghan_guys/server/feedback/domain/Feedback.java @@ -1,6 +1,7 @@ package com.susanghan_guys.server.feedback.domain; import com.susanghan_guys.server.global.domain.BaseEntity; +import com.susanghan_guys.server.user.domain.User; import com.susanghan_guys.server.work.domain.Work; import jakarta.persistence.*; import lombok.AccessLevel; @@ -9,6 +10,12 @@ import lombok.NoArgsConstructor; @Entity +@Table(uniqueConstraints = { + @UniqueConstraint( + name = "UK_feedback_user_work", + columnNames = {"user_id", "work_id"} + ) +}) @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Feedback extends BaseEntity { @@ -24,18 +31,24 @@ public class Feedback extends BaseEntity { @Column(name = "content", nullable = false) private String content; - @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "work_id", nullable = false, unique = true) + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "work_id", nullable = false) private Work work; @Builder public Feedback( Integer score, String content, + User user, Work work ) { this.score = score; this.content = content; + this.user = user; this.work = work; } } diff --git a/src/main/java/com/susanghan_guys/server/feedback/infrastructure/mapper/FeedbackMapper.java b/src/main/java/com/susanghan_guys/server/feedback/infrastructure/mapper/FeedbackMapper.java index bb8d017b..0d991af6 100644 --- a/src/main/java/com/susanghan_guys/server/feedback/infrastructure/mapper/FeedbackMapper.java +++ b/src/main/java/com/susanghan_guys/server/feedback/infrastructure/mapper/FeedbackMapper.java @@ -1,14 +1,16 @@ package com.susanghan_guys.server.feedback.infrastructure.mapper; import com.susanghan_guys.server.feedback.domain.Feedback; +import com.susanghan_guys.server.user.domain.User; import com.susanghan_guys.server.work.domain.Work; public class FeedbackMapper { - public static Feedback toEntity(Integer score, String content, Work work) { + public static Feedback toEntity(Integer score, String content, User user, Work work) { return Feedback.builder() .score(score) .content(content) + .user(user) .work(work) .build(); } diff --git a/src/main/java/com/susanghan_guys/server/feedback/infrastructure/persistence/FeedbackRepository.java b/src/main/java/com/susanghan_guys/server/feedback/infrastructure/persistence/FeedbackRepository.java index 1b0140d3..811f2ca4 100644 --- a/src/main/java/com/susanghan_guys/server/feedback/infrastructure/persistence/FeedbackRepository.java +++ b/src/main/java/com/susanghan_guys/server/feedback/infrastructure/persistence/FeedbackRepository.java @@ -1,8 +1,10 @@ package com.susanghan_guys.server.feedback.infrastructure.persistence; import com.susanghan_guys.server.feedback.domain.Feedback; +import com.susanghan_guys.server.user.domain.User; +import com.susanghan_guys.server.work.domain.Work; import org.springframework.data.jpa.repository.JpaRepository; public interface FeedbackRepository extends JpaRepository { - boolean existsByWorkId(Long workId); + boolean existsByWorkAndUser(Work work, User user); } diff --git a/src/main/java/com/susanghan_guys/server/global/client/openai/prompt/DcaWorkEvaluationPrompt.java b/src/main/java/com/susanghan_guys/server/global/client/openai/prompt/DcaWorkEvaluationPrompt.java index 05797dba..6faea9e8 100644 --- a/src/main/java/com/susanghan_guys/server/global/client/openai/prompt/DcaWorkEvaluationPrompt.java +++ b/src/main/java/com/susanghan_guys/server/global/client/openai/prompt/DcaWorkEvaluationPrompt.java @@ -96,13 +96,16 @@ public static OpenAiPrompt buildDcaDetailEvaluationPrompt( 2. Output "score": INTEGER (0-10). Scoring rule: - - Baseline: 4 points - - 10-8 points: The work fully and clearly meets the criterion, going beyond expectations with concrete and persuasive execution. (e.g. a heavy theme reframed into a light participatory idea that feels fresh and positive) - - 7-6 points: The work sufficiently meets the criterion but shows some lack of clarity, specificity, or persuasiveness. (e.g. a perception shift is attempted but remains limited or predictable) - - 5 or below: The work fails to meet the core requirement of the criterion. (e.g. participation exists but the structure lacks potential to expand into a true public campaign) - - If the score is 7 or below, and there is a clear reason for the lower score, add at least one limitation, weakness, or risk factor to justify it. \s - - If no meaningful reason is apparent, you may omit this part instead of forcing a generic remark. - + - Baseline guideline: 6 points + - 9 points: Allowed, but only if the rationale is very specific, concrete, and clearly persuasive.\s + → Do NOT overuse; should be rare, but possible. + - 10 points: Practically impossible. Use only in extraordinary, near-perfect cases. + - 7–8 points: Solid and well-justified executions. + - 6 points: Baseline / average quality. If you assign 6, you must provide one clear weakness/limitation. + - 5 points or below: Strongly discouraged. Only use if the campaign is clearly and fundamentally flawed. + If you assign ≤5, you must justify with one clear weakness/risk factor (do not list more than one). + - Below 5: Not allowed. + Conservativeness: - Do NOT inflate scores without clear evidence. - Be conservative: most scores should be 8 or below. diff --git a/src/main/java/com/susanghan_guys/server/mail/application/MailService.java b/src/main/java/com/susanghan_guys/server/mail/application/MailService.java index 9a316338..316f4c50 100644 --- a/src/main/java/com/susanghan_guys/server/mail/application/MailService.java +++ b/src/main/java/com/susanghan_guys/server/mail/application/MailService.java @@ -68,6 +68,9 @@ private void sendWorkMembers(Work work, String template) { ), template); for (WorkMember workMember : work.getWorkMembers()) { + if (work.getUser().getEmail().equals(workMember.getTeamMember().getEmail())) { + continue; + } personalizeMail(new MailRequest( workMember.getTeamMember().getEmail(), workMember.getTeamMember().getName(), 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 8f1e0082..e6107072 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 @@ -17,7 +17,6 @@ import com.susanghan_guys.server.personalwork.infrastructure.persistence.EvaluationRepository; import com.susanghan_guys.server.user.domain.User; import com.susanghan_guys.server.work.domain.Work; -import com.susanghan_guys.server.work.domain.type.ReportStatus; 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; @@ -98,7 +97,6 @@ private List getOrCreateEvaluation(Long workId) { List detailEvals = getOrCreateDetailEvaluation(workId, evaluation.getType()); evaluation.updateScore(detailEvals); } - work.updateReportStatus(ReportStatus.COMPLETED); return evaluations; } 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 index ec83a820..c49c3e99 100644 --- a/src/main/java/com/susanghan_guys/server/personalwork/application/ReportInternalService.java +++ b/src/main/java/com/susanghan_guys/server/personalwork/application/ReportInternalService.java @@ -8,6 +8,7 @@ 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.ReportStatus; 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; @@ -17,6 +18,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.UUID; @Service @RequiredArgsConstructor @@ -30,7 +32,6 @@ public class ReportInternalService { private final EvaluationRepository evaluationRepository; private final DetailEvalRepository detailEvalRepository; - @Transactional public ReportPipelineResponse runPipeline(Long workId) { boolean summaryDone = false; @@ -51,6 +52,11 @@ public ReportPipelineResponse runPipeline(Long workId) { runEvaluationInternal(workId); evaluationDone = true; + if (work.getCode() == null) { + work.updateCode(UUID.randomUUID().toString().substring(0, 6).toUpperCase()); + } + work.updateReportStatus(ReportStatus.COMPLETED); + return ReportPipelineResponse.builder() .summaryDone(summaryDone) .briefDone(briefDone) 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 b8b1c274..6b6efbc2 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 @@ -17,7 +17,6 @@ import com.susanghan_guys.server.personalwork.infrastructure.persistence.EvaluationRepository; import com.susanghan_guys.server.user.domain.User; import com.susanghan_guys.server.work.domain.Work; -import com.susanghan_guys.server.work.domain.type.ReportStatus; 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; @@ -108,7 +107,6 @@ private List getOrCreateEvaluation(Long workId) { List detailEvals = getOrCreateDetailEvaluation(workId, evaluation.getType()); evaluation.updateScore(detailEvals); } - work.updateReportStatus(ReportStatus.COMPLETED); return evaluations; } diff --git a/src/main/java/com/susanghan_guys/server/work/application/ReportService.java b/src/main/java/com/susanghan_guys/server/work/application/ReportService.java index acaac796..dec220ed 100644 --- a/src/main/java/com/susanghan_guys/server/work/application/ReportService.java +++ b/src/main/java/com/susanghan_guys/server/work/application/ReportService.java @@ -1,5 +1,6 @@ package com.susanghan_guys.server.work.application; +import com.susanghan_guys.server.feedback.infrastructure.persistence.FeedbackRepository; import com.susanghan_guys.server.global.security.CurrentUserProvider; import com.susanghan_guys.server.user.domain.User; import com.susanghan_guys.server.work.application.validator.ReportValidator; @@ -26,7 +27,6 @@ import java.util.Collections; import java.util.List; import java.util.Set; -import java.util.UUID; @Service @RequiredArgsConstructor @@ -35,6 +35,7 @@ public class ReportService { private final CurrentUserProvider currentUserProvider; private final WorkRepository workRepository; + private final FeedbackRepository feedbackRepository; private final WorkVisibilityRepository workVisibilityRepository; private final ReportValidator reportValidator; @@ -64,9 +65,11 @@ public ReportInfoResponse getReportInfo(Long workId) { Work work = workRepository.findById(workId) .orElseThrow(() -> new WorkException(WorkErrorCode.WORK_NOT_FOUND)); + boolean hasFeedback = feedbackRepository.existsByWorkAndUser(work, user); + reportValidator.validateReportInfo(user, work); - return ReportInfoResponse.from(work); + return ReportInfoResponse.from(work, hasFeedback); } @Transactional diff --git a/src/main/java/com/susanghan_guys/server/work/dto/response/ReportInfoResponse.java b/src/main/java/com/susanghan_guys/server/work/dto/response/ReportInfoResponse.java index b217c92b..91a43ecc 100644 --- a/src/main/java/com/susanghan_guys/server/work/dto/response/ReportInfoResponse.java +++ b/src/main/java/com/susanghan_guys/server/work/dto/response/ReportInfoResponse.java @@ -20,9 +20,11 @@ public record ReportInfoResponse( Brand brand, @Schema(description = "공모전 팀원", example = "[\"김철수\", \"주정빈\", \"강수진\"]") - List workMembers + List workMembers, + + boolean hasFeedback ) { - public static ReportInfoResponse from(Work work) { + public static ReportInfoResponse from(Work work, boolean hasFeedback) { return ReportInfoResponse.builder() .workName(work.getTitle()) .contestName(work.getContest().getTitle()) @@ -32,6 +34,7 @@ public static ReportInfoResponse from(Work work) { .map(workMember -> workMember.getTeamMember().getName()) .toList() ) + .hasFeedback(hasFeedback) .build(); } }