From 3cf9b5b34f0319b23161ae3b8ffad9ec5d2707df Mon Sep 17 00:00:00 2001 From: wngktjd13 Date: Mon, 12 Jan 2026 21:12:13 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20=ED=9C=B4=EA=B0=80=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EC=86=8C=EC=86=8D=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ReportSchedulerService.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/bmilab/backend/domain/report/service/ReportSchedulerService.java b/src/main/java/com/bmilab/backend/domain/report/service/ReportSchedulerService.java index a21caa4..84a6783 100644 --- a/src/main/java/com/bmilab/backend/domain/report/service/ReportSchedulerService.java +++ b/src/main/java/com/bmilab/backend/domain/report/service/ReportSchedulerService.java @@ -6,6 +6,9 @@ import com.bmilab.backend.domain.project.repository.ProjectRepository; import com.bmilab.backend.domain.report.dto.query.GetAllReportsQueryResult; import com.bmilab.backend.domain.report.repository.ReportRepository; +import com.bmilab.backend.domain.user.entity.User; +import com.bmilab.backend.domain.user.entity.UserProjectCategory; +import com.bmilab.backend.domain.user.repository.UserProjectCategoryRepository; import com.bmilab.backend.domain.user.repository.UserRepository; import com.bmilab.backend.global.email.EmailSender; import jakarta.mail.MessagingException; @@ -25,6 +28,7 @@ import java.time.temporal.TemporalAdjusters; import java.util.List; import java.util.Locale; +import java.util.stream.Collectors; @Slf4j @Service @@ -42,6 +46,7 @@ public class ReportSchedulerService { private final ProjectRepository projectRepository; private final UserRepository userRepository; private final LeaveRepository leaveRepository; + private final UserProjectCategoryRepository userProjectCategoryRepository; private static final DateTimeFormatter DATE_WITH_DAY_FORMATTER = DateTimeFormatter.ofPattern("MM/dd E", Locale.KOREAN); @@ -119,14 +124,14 @@ private String buildWeeklyLeavesMessage(List leaves) { StringBuilder sb = new StringBuilder(); for (Leave leave : leaves) { String name = escMdV2(leave.getUser().getName()); - String email = leave.getUser().getEmail(); + String categoryDisplay = getCategoryDisplay(leave.getUser()); String leaveType = escMdV2(leave.getType().getDescription()); String period = formatLeavePeriod(leave); sb.append("\\- ") .append(name) .append(" \\(") - .append("`").append(email).append("`") + .append(categoryDisplay) .append("\\) \\- ") .append(leaveType) .append(" \\(") @@ -137,6 +142,16 @@ private String buildWeeklyLeavesMessage(List leaves) { return sb.toString().trim(); } + private String getCategoryDisplay(User user) { + List categories = userProjectCategoryRepository.findAllByUser(user); + if (categories.isEmpty()) { + return "소속 없음"; + } + return categories.stream() + .map(upc -> escMdV2(upc.getCategory().getName())) + .collect(Collectors.joining(", ")); + } + private String formatLeavePeriod(Leave leave) { String startDateStr = escMdV2(leave.getStartDate().format(DATE_WITH_DAY_FORMATTER)); From 55f262dd4c45c89b6ff920a56b49762217353a71 Mon Sep 17 00:00:00 2001 From: wngktjd13 Date: Mon, 12 Jan 2026 21:14:06 +0900 Subject: [PATCH 2/8] =?UTF-8?q?refactor:=20Task=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?=EC=97=B0=EA=B5=AC=EC=84=B1=EA=B3=BC=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../task/dto/request/ConferenceRequest.java | 24 --------- .../task/dto/request/PatentRequest.java | 19 ------- .../dto/request/PublicationUpdateRequest.java | 27 ---------- .../task/dto/response/ConferenceResponse.java | 33 ------------ .../task/dto/response/PatentResponse.java | 29 ----------- .../dto/response/PublicationResponse.java | 37 -------------- .../domain/task/entity/Conference.java | 47 ----------------- .../backend/domain/task/entity/Patent.java | 43 ---------------- .../domain/task/entity/Publication.java | 51 ------------------- .../task/repository/ConferenceRepository.java | 11 ---- .../task/repository/TaskPatentRepository.java | 11 ---- .../repository/TaskPublicationRepository.java | 11 ---- 12 files changed, 343 deletions(-) delete mode 100644 src/main/java/com/bmilab/backend/domain/task/dto/request/ConferenceRequest.java delete mode 100644 src/main/java/com/bmilab/backend/domain/task/dto/request/PatentRequest.java delete mode 100644 src/main/java/com/bmilab/backend/domain/task/dto/request/PublicationUpdateRequest.java delete mode 100644 src/main/java/com/bmilab/backend/domain/task/dto/response/ConferenceResponse.java delete mode 100644 src/main/java/com/bmilab/backend/domain/task/dto/response/PatentResponse.java delete mode 100644 src/main/java/com/bmilab/backend/domain/task/dto/response/PublicationResponse.java delete mode 100644 src/main/java/com/bmilab/backend/domain/task/entity/Conference.java delete mode 100644 src/main/java/com/bmilab/backend/domain/task/entity/Patent.java delete mode 100644 src/main/java/com/bmilab/backend/domain/task/entity/Publication.java delete mode 100644 src/main/java/com/bmilab/backend/domain/task/repository/ConferenceRepository.java delete mode 100644 src/main/java/com/bmilab/backend/domain/task/repository/TaskPatentRepository.java delete mode 100644 src/main/java/com/bmilab/backend/domain/task/repository/TaskPublicationRepository.java diff --git a/src/main/java/com/bmilab/backend/domain/task/dto/request/ConferenceRequest.java b/src/main/java/com/bmilab/backend/domain/task/dto/request/ConferenceRequest.java deleted file mode 100644 index d16d153..0000000 --- a/src/main/java/com/bmilab/backend/domain/task/dto/request/ConferenceRequest.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.bmilab.backend.domain.task.dto.request; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; - -import java.time.LocalDate; - -public record ConferenceRequest( - @Schema(description = "발표 제목", example = "Deep Learning for Medical Image Analysis") - @NotBlank(message = "발표 제목은 필수입니다.") - String presentationTitle, - - @Schema(description = "학회명", example = "MICCAI 2024") - @NotBlank(message = "학회명은 필수입니다.") - String conferenceName, - - @Schema(description = "발표자", example = "홍길동") - @NotBlank(message = "발표자는 필수입니다.") - String presenter, - - @Schema(description = "발표일", example = "2025-10-20") - LocalDate presentationDate -) { -} diff --git a/src/main/java/com/bmilab/backend/domain/task/dto/request/PatentRequest.java b/src/main/java/com/bmilab/backend/domain/task/dto/request/PatentRequest.java deleted file mode 100644 index 7bd82bd..0000000 --- a/src/main/java/com/bmilab/backend/domain/task/dto/request/PatentRequest.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.bmilab.backend.domain.task.dto.request; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; - -import java.time.LocalDate; - -public record PatentRequest( - @Schema(description = "특허명", example = "의료 영상 분석 장치 및 방법") - @NotBlank(message = "특허명은 필수입니다.") - String patentTitle, - - @Schema(description = "특허 번호", example = "10-2024-0001234") - String patentNumber, - - @Schema(description = "출원일", example = "2025-10-15") - LocalDate applicationDate -) { -} diff --git a/src/main/java/com/bmilab/backend/domain/task/dto/request/PublicationUpdateRequest.java b/src/main/java/com/bmilab/backend/domain/task/dto/request/PublicationUpdateRequest.java deleted file mode 100644 index 1d45d66..0000000 --- a/src/main/java/com/bmilab/backend/domain/task/dto/request/PublicationUpdateRequest.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.bmilab.backend.domain.task.dto.request; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; - -import java.time.LocalDate; - -public record PublicationUpdateRequest( - @Schema(description = "논문 제목", example = "AI 기반 헬스케어 시스템 연구") - @NotBlank(message = "논문 제목은 필수입니다.") - String title, - - @Schema(description = "저자", example = "김연구, 박연구") - @NotBlank(message = "저자는 필수입니다.") - String authors, - - @Schema(description = "학술지명", example = "HealthCare") - @NotBlank(message = "학술지명은 필수입니다.") - String journal, - - @Schema(description = "게재일", example = "2025-11-15") - LocalDate publicationDate, - - @Schema(description = "DOI", example = "10.1038/s41586-021-03819-2") - String doi -) { -} \ No newline at end of file diff --git a/src/main/java/com/bmilab/backend/domain/task/dto/response/ConferenceResponse.java b/src/main/java/com/bmilab/backend/domain/task/dto/response/ConferenceResponse.java deleted file mode 100644 index 1f04713..0000000 --- a/src/main/java/com/bmilab/backend/domain/task/dto/response/ConferenceResponse.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.bmilab.backend.domain.task.dto.response; - -import com.bmilab.backend.domain.task.entity.Conference; -import io.swagger.v3.oas.annotations.media.Schema; - -import java.time.LocalDate; - -public record ConferenceResponse( - @Schema(description = "학회 발표 ID") - Long id, - - @Schema(description = "발표 제목") - String presentationTitle, - - @Schema(description = "학회명") - String conferenceName, - - @Schema(description = "발표자") - String presenter, - - @Schema(description = "발표일") - LocalDate presentationDate -) { - public static ConferenceResponse from(Conference conference) { - return new ConferenceResponse( - conference.getId(), - conference.getPresentationTitle(), - conference.getConferenceName(), - conference.getPresenter(), - conference.getPresentationDate() - ); - } -} diff --git a/src/main/java/com/bmilab/backend/domain/task/dto/response/PatentResponse.java b/src/main/java/com/bmilab/backend/domain/task/dto/response/PatentResponse.java deleted file mode 100644 index 93a76d8..0000000 --- a/src/main/java/com/bmilab/backend/domain/task/dto/response/PatentResponse.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.bmilab.backend.domain.task.dto.response; - -import com.bmilab.backend.domain.task.entity.Patent; -import io.swagger.v3.oas.annotations.media.Schema; - -import java.time.LocalDate; - -public record PatentResponse( - @Schema(description = "특허 ID") - Long id, - - @Schema(description = "특허명") - String patentTitle, - - @Schema(description = "특허 번호") - String patentNumber, - - @Schema(description = "출원일") - LocalDate applicationDate -) { - public static PatentResponse from(Patent patent) { - return new PatentResponse( - patent.getId(), - patent.getPatentTitle(), - patent.getPatentNumber(), - patent.getApplicationDate() - ); - } -} diff --git a/src/main/java/com/bmilab/backend/domain/task/dto/response/PublicationResponse.java b/src/main/java/com/bmilab/backend/domain/task/dto/response/PublicationResponse.java deleted file mode 100644 index cf4f47e..0000000 --- a/src/main/java/com/bmilab/backend/domain/task/dto/response/PublicationResponse.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.bmilab.backend.domain.task.dto.response; - -import com.bmilab.backend.domain.task.entity.Publication; -import io.swagger.v3.oas.annotations.media.Schema; - -import java.time.LocalDate; - -public record PublicationResponse( - @Schema(description = "논문 ID") - Long id, - - @Schema(description = "논문 제목") - String title, - - @Schema(description = "저자") - String authors, - - @Schema(description = "학술지명") - String journal, - - @Schema(description = "게재일") - LocalDate publicationDate, - - @Schema(description = "DOI") - String doi -) { - public static PublicationResponse from(Publication publication) { - return new PublicationResponse( - publication.getId(), - publication.getTitle(), - publication.getAuthors(), - publication.getJournal(), - publication.getPublicationDate(), - publication.getDoi() - ); - } -} diff --git a/src/main/java/com/bmilab/backend/domain/task/entity/Conference.java b/src/main/java/com/bmilab/backend/domain/task/entity/Conference.java deleted file mode 100644 index dddec9e..0000000 --- a/src/main/java/com/bmilab/backend/domain/task/entity/Conference.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.bmilab.backend.domain.task.entity; - -import com.bmilab.backend.global.entity.BaseTimeEntity; -import jakarta.persistence.*; -import lombok.*; -import org.hibernate.annotations.OnDelete; -import org.hibernate.annotations.OnDeleteAction; - -import java.time.LocalDate; - -@Entity -@Table(name = "conferences") -@Getter -@Builder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class Conference extends BaseTimeEntity { - - @Id - @Column(name = "conference_id") - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "task_id", nullable = false) - @OnDelete(action = OnDeleteAction.CASCADE) - private Task task; - - @Column(name = "presentation_title", nullable = false) - private String presentationTitle; - - @Column(name = "conference_name", nullable = false) - private String conferenceName; - - @Column(nullable = false) - private String presenter; - - @Column(name = "presentation_date") - private LocalDate presentationDate; - - public void update(String presentationTitle, String conferenceName, String presenter, LocalDate presentationDate) { - this.presentationTitle = presentationTitle; - this.conferenceName = conferenceName; - this.presenter = presenter; - this.presentationDate = presentationDate; - } -} diff --git a/src/main/java/com/bmilab/backend/domain/task/entity/Patent.java b/src/main/java/com/bmilab/backend/domain/task/entity/Patent.java deleted file mode 100644 index c950331..0000000 --- a/src/main/java/com/bmilab/backend/domain/task/entity/Patent.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.bmilab.backend.domain.task.entity; - -import com.bmilab.backend.global.entity.BaseTimeEntity; -import jakarta.persistence.*; -import lombok.*; -import org.hibernate.annotations.OnDelete; -import org.hibernate.annotations.OnDeleteAction; - -import java.time.LocalDate; - -@Entity(name = "TaskPatent") -@Table(name = "patents") -@Getter -@Builder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class Patent extends BaseTimeEntity { - - @Id - @Column(name = "patent_id") - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "task_id", nullable = false) - @OnDelete(action = OnDeleteAction.CASCADE) - private Task task; - - @Column(name = "patent_title", nullable = false) - private String patentTitle; - - @Column(name = "patent_number") - private String patentNumber; - - @Column(name = "application_date") - private LocalDate applicationDate; - - public void update(String patentTitle, String patentNumber, LocalDate applicationDate) { - this.patentTitle = patentTitle; - this.patentNumber = patentNumber; - this.applicationDate = applicationDate; - } -} diff --git a/src/main/java/com/bmilab/backend/domain/task/entity/Publication.java b/src/main/java/com/bmilab/backend/domain/task/entity/Publication.java deleted file mode 100644 index e382d87..0000000 --- a/src/main/java/com/bmilab/backend/domain/task/entity/Publication.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.bmilab.backend.domain.task.entity; - -import com.bmilab.backend.global.entity.BaseTimeEntity; -import jakarta.persistence.*; -import lombok.*; -import org.hibernate.annotations.OnDelete; -import org.hibernate.annotations.OnDeleteAction; - -import java.time.LocalDate; - -@Entity(name = "TaskPublication") -@Table(name = "publications") -@Getter -@Builder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class Publication extends BaseTimeEntity { - - @Id - @Column(name = "publication_id") - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "task_id", nullable = false) - @OnDelete(action = OnDeleteAction.CASCADE) - private Task task; - - @Column(nullable = false) - private String title; - - @Column(columnDefinition = "TEXT", nullable = false) - private String authors; - - @Column(nullable = false) - private String journal; - - @Column(name = "publication_date") - private LocalDate publicationDate; - - @Column(columnDefinition = "TEXT") - private String doi; - - public void update(String title, String authors, String journal, LocalDate publicationDate, String doi) { - this.title = title; - this.authors = authors; - this.journal = journal; - this.publicationDate = publicationDate; - this.doi = doi; - } -} diff --git a/src/main/java/com/bmilab/backend/domain/task/repository/ConferenceRepository.java b/src/main/java/com/bmilab/backend/domain/task/repository/ConferenceRepository.java deleted file mode 100644 index e67f23e..0000000 --- a/src/main/java/com/bmilab/backend/domain/task/repository/ConferenceRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.bmilab.backend.domain.task.repository; - -import com.bmilab.backend.domain.task.entity.Conference; -import com.bmilab.backend.domain.task.entity.Task; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; - -public interface ConferenceRepository extends JpaRepository { - Optional findByTask(Task task); -} diff --git a/src/main/java/com/bmilab/backend/domain/task/repository/TaskPatentRepository.java b/src/main/java/com/bmilab/backend/domain/task/repository/TaskPatentRepository.java deleted file mode 100644 index d4facda..0000000 --- a/src/main/java/com/bmilab/backend/domain/task/repository/TaskPatentRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.bmilab.backend.domain.task.repository; - -import com.bmilab.backend.domain.task.entity.Patent; -import com.bmilab.backend.domain.task.entity.Task; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; - -public interface TaskPatentRepository extends JpaRepository { - Optional findByTask(Task task); -} diff --git a/src/main/java/com/bmilab/backend/domain/task/repository/TaskPublicationRepository.java b/src/main/java/com/bmilab/backend/domain/task/repository/TaskPublicationRepository.java deleted file mode 100644 index 22801bf..0000000 --- a/src/main/java/com/bmilab/backend/domain/task/repository/TaskPublicationRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.bmilab.backend.domain.task.repository; - -import com.bmilab.backend.domain.task.entity.Publication; -import com.bmilab.backend.domain.task.entity.Task; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; - -public interface TaskPublicationRepository extends JpaRepository { - Optional findByTask(Task task); -} From 35acadf8721e74b8b07aca855b1f4e54e49c3df9 Mon Sep 17 00:00:00 2001 From: wngktjd13 Date: Mon, 12 Jan 2026 21:14:36 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20Research=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20task=20=EC=97=B0=EB=8F=99=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../paper/dto/request/CreatePaperRequest.java | 2 ++ .../paper/dto/request/UpdatePaperRequest.java | 2 ++ .../research/paper/dto/response/PaperResponse.java | 3 +++ .../backend/domain/research/paper/entity/Paper.java | 11 +++++++++-- .../research/paper/repository/PaperRepository.java | 3 +++ .../domain/research/paper/service/PaperService.java | 13 ++++++++++++- .../patent/repository/PatentRepository.java | 3 +++ .../repository/AcademicPresentationRepository.java | 3 +++ 8 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/bmilab/backend/domain/research/paper/dto/request/CreatePaperRequest.java b/src/main/java/com/bmilab/backend/domain/research/paper/dto/request/CreatePaperRequest.java index 309c432..4386f5e 100644 --- a/src/main/java/com/bmilab/backend/domain/research/paper/dto/request/CreatePaperRequest.java +++ b/src/main/java/com/bmilab/backend/domain/research/paper/dto/request/CreatePaperRequest.java @@ -42,6 +42,8 @@ public record CreatePaperRequest( ProfessorRole professorRole, @Schema(description = "대표 실적 여부", example = "true") boolean isRepresentative, + @Schema(description = "연계 과제 ID (선택적)", example = "1") + Long taskId, @Schema(description = "첨부 파일 ID 목록", example = "[\"a1b2c3d4-e5f6-7890-1234-567890abcdef\"]") List fileIds ) { diff --git a/src/main/java/com/bmilab/backend/domain/research/paper/dto/request/UpdatePaperRequest.java b/src/main/java/com/bmilab/backend/domain/research/paper/dto/request/UpdatePaperRequest.java index 468aa87..38dc84b 100644 --- a/src/main/java/com/bmilab/backend/domain/research/paper/dto/request/UpdatePaperRequest.java +++ b/src/main/java/com/bmilab/backend/domain/research/paper/dto/request/UpdatePaperRequest.java @@ -42,6 +42,8 @@ public record UpdatePaperRequest( ProfessorRole professorRole, @Schema(description = "대표 실적 여부", example = "false") boolean isRepresentative, + @Schema(description = "연계 과제 ID (선택적)", example = "1") + Long taskId, @Schema(description = "첨부 파일 ID 목록", example = "[\"a1b2c3d4-e5f6-7890-1234-567890abcdef\"]") List fileIds ) { diff --git a/src/main/java/com/bmilab/backend/domain/research/paper/dto/response/PaperResponse.java b/src/main/java/com/bmilab/backend/domain/research/paper/dto/response/PaperResponse.java index d5d7c00..8b9e4c7 100644 --- a/src/main/java/com/bmilab/backend/domain/research/paper/dto/response/PaperResponse.java +++ b/src/main/java/com/bmilab/backend/domain/research/paper/dto/response/PaperResponse.java @@ -49,6 +49,8 @@ public record PaperResponse( String professorRole, @Schema(description = "대표 실적 여부") boolean isRepresentative, + @Schema(description = "연계 과제 ID") + Long taskId, @Schema(description = "첨부 파일 목록") List files ) { @@ -73,6 +75,7 @@ public PaperResponse(Paper paper, List correspondingAu paper.getCitations(), paper.getProfessorRole().getDescription(), paper.getIsRepresentative(), + paper.getTask() != null ? paper.getTask().getId() : null, files ); } diff --git a/src/main/java/com/bmilab/backend/domain/research/paper/entity/Paper.java b/src/main/java/com/bmilab/backend/domain/research/paper/entity/Paper.java index 91ee11f..ae80111 100644 --- a/src/main/java/com/bmilab/backend/domain/research/paper/entity/Paper.java +++ b/src/main/java/com/bmilab/backend/domain/research/paper/entity/Paper.java @@ -1,6 +1,7 @@ package com.bmilab.backend.domain.research.paper.entity; import com.bmilab.backend.domain.research.paper.enums.ProfessorRole; +import com.bmilab.backend.domain.task.entity.Task; import com.bmilab.backend.global.entity.BaseTimeEntity; import lombok.AccessLevel; import lombok.Builder; @@ -68,8 +69,12 @@ public class Paper extends BaseTimeEntity { private Boolean isRepresentative; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "task_id") + private Task task; + @Builder - public Paper(LocalDate acceptDate, LocalDate publishDate, Journal journal, String paperTitle, String allAuthors, int authorCount, String firstAuthor, String coAuthors, String vol, String page, String paperLink, String doi, String pmid, Integer citations, ProfessorRole professorRole, Boolean isRepresentative) { + public Paper(LocalDate acceptDate, LocalDate publishDate, Journal journal, String paperTitle, String allAuthors, int authorCount, String firstAuthor, String coAuthors, String vol, String page, String paperLink, String doi, String pmid, Integer citations, ProfessorRole professorRole, Boolean isRepresentative, Task task) { this.acceptDate = acceptDate; this.publishDate = publishDate; this.journal = journal; @@ -86,9 +91,10 @@ public Paper(LocalDate acceptDate, LocalDate publishDate, Journal journal, Strin this.citations = citations; this.professorRole = professorRole; this.isRepresentative = isRepresentative; + this.task = task; } - public void update(LocalDate acceptDate, LocalDate publishDate, Journal journal, String paperTitle, String allAuthors, int authorCount, String firstAuthor, String coAuthors, String vol, String page, String paperLink, String doi, String pmid, Integer citations, ProfessorRole professorRole, Boolean isRepresentative) { + public void update(LocalDate acceptDate, LocalDate publishDate, Journal journal, String paperTitle, String allAuthors, int authorCount, String firstAuthor, String coAuthors, String vol, String page, String paperLink, String doi, String pmid, Integer citations, ProfessorRole professorRole, Boolean isRepresentative, Task task) { this.acceptDate = acceptDate; this.publishDate = publishDate; this.journal = journal; @@ -105,6 +111,7 @@ public void update(LocalDate acceptDate, LocalDate publishDate, Journal journal, this.citations = citations; this.professorRole = professorRole; this.isRepresentative = isRepresentative; + this.task = task; } } \ No newline at end of file diff --git a/src/main/java/com/bmilab/backend/domain/research/paper/repository/PaperRepository.java b/src/main/java/com/bmilab/backend/domain/research/paper/repository/PaperRepository.java index fa2fb08..f41e1ca 100644 --- a/src/main/java/com/bmilab/backend/domain/research/paper/repository/PaperRepository.java +++ b/src/main/java/com/bmilab/backend/domain/research/paper/repository/PaperRepository.java @@ -3,5 +3,8 @@ import com.bmilab.backend.domain.research.paper.entity.Paper; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface PaperRepository extends JpaRepository, PaperRepositoryCustom { + List findAllByTaskId(Long taskId); } diff --git a/src/main/java/com/bmilab/backend/domain/research/paper/service/PaperService.java b/src/main/java/com/bmilab/backend/domain/research/paper/service/PaperService.java index 7b312ed..1f138c4 100644 --- a/src/main/java/com/bmilab/backend/domain/research/paper/service/PaperService.java +++ b/src/main/java/com/bmilab/backend/domain/research/paper/service/PaperService.java @@ -20,6 +20,9 @@ import com.bmilab.backend.domain.research.paper.repository.PaperCorrespondingAuthorRepository; import com.bmilab.backend.domain.research.paper.repository.PaperRepository; import com.bmilab.backend.domain.research.service.AuthorSyncService; +import com.bmilab.backend.domain.task.entity.Task; +import com.bmilab.backend.domain.task.exception.TaskErrorCode; +import com.bmilab.backend.domain.task.repository.TaskRepository; import com.bmilab.backend.domain.user.entity.User; import com.bmilab.backend.global.exception.ApiException; import com.bmilab.backend.global.exception.GlobalErrorCode; @@ -43,12 +46,16 @@ public class PaperService { private final PaperCorrespondingAuthorRepository paperCorrespondingAuthorRepository; private final JournalRepository journalRepository; private final ExternalProfessorRepository externalProfessorRepository; + private final TaskRepository taskRepository; private final FileService fileService; private final AuthorSyncService authorSyncService; public PaperResponse createPaper(CreatePaperRequest dto) { Journal journal = journalRepository.findById(dto.journalId()) .orElseThrow(() -> new ApiException(PaperErrorCode.JOURNAL_NOT_FOUND)); + Task task = dto.taskId() != null + ? taskRepository.findById(dto.taskId()).orElseThrow(() -> new ApiException(TaskErrorCode.TASK_NOT_FOUND)) + : null; int authorCount = (dto.allAuthors() != null) ? dto.allAuthors().split(",").length : 0; Paper newPaper = Paper.builder() .acceptDate(dto.acceptDate()) @@ -67,6 +74,7 @@ public PaperResponse createPaper(CreatePaperRequest dto) { .citations(dto.citations()) .professorRole(dto.professorRole()) .isRepresentative(dto.isRepresentative()) + .task(task) .build(); paperRepository.save(newPaper); @@ -143,8 +151,11 @@ public PaperResponse updatePaper(Long paperId, UpdatePaperRequest dto) { .orElseThrow(() -> new ApiException(PaperErrorCode.PAPER_NOT_FOUND)); Journal journal = journalRepository.findById(dto.journalId()) .orElseThrow(() -> new ApiException(PaperErrorCode.JOURNAL_NOT_FOUND)); + Task task = dto.taskId() != null + ? taskRepository.findById(dto.taskId()).orElseThrow(() -> new ApiException(TaskErrorCode.TASK_NOT_FOUND)) + : null; int authorCount = (dto.allAuthors() != null) ? dto.allAuthors().split(",").length : 0; - paper.update(dto.acceptDate(), dto.publishDate(), journal, dto.paperTitle(), dto.allAuthors(), authorCount, dto.firstAuthor(), dto.coAuthors(), dto.vol(), dto.page(), dto.paperLink(), dto.doi(), dto.pmid(), dto.citations(), dto.professorRole(), dto.isRepresentative()); + paper.update(dto.acceptDate(), dto.publishDate(), journal, dto.paperTitle(), dto.allAuthors(), authorCount, dto.firstAuthor(), dto.coAuthors(), dto.vol(), dto.page(), dto.paperLink(), dto.doi(), dto.pmid(), dto.citations(), dto.professorRole(), dto.isRepresentative(), task); // Handle PaperCorrespondingAuthor linking paperCorrespondingAuthorRepository.deleteAllByPaperId(paperId); diff --git a/src/main/java/com/bmilab/backend/domain/research/patent/repository/PatentRepository.java b/src/main/java/com/bmilab/backend/domain/research/patent/repository/PatentRepository.java index 5a5ea53..e6c5840 100644 --- a/src/main/java/com/bmilab/backend/domain/research/patent/repository/PatentRepository.java +++ b/src/main/java/com/bmilab/backend/domain/research/patent/repository/PatentRepository.java @@ -3,5 +3,8 @@ import com.bmilab.backend.domain.research.patent.entity.Patent; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface PatentRepository extends JpaRepository, PatentRepositoryCustom { + List findAllByTaskId(Long taskId); } diff --git a/src/main/java/com/bmilab/backend/domain/research/presentation/repository/AcademicPresentationRepository.java b/src/main/java/com/bmilab/backend/domain/research/presentation/repository/AcademicPresentationRepository.java index e3ef49a..6241f51 100644 --- a/src/main/java/com/bmilab/backend/domain/research/presentation/repository/AcademicPresentationRepository.java +++ b/src/main/java/com/bmilab/backend/domain/research/presentation/repository/AcademicPresentationRepository.java @@ -3,5 +3,8 @@ import com.bmilab.backend.domain.research.presentation.entity.AcademicPresentation; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface AcademicPresentationRepository extends JpaRepository, AcademicPresentationRepositoryCustom { + List findAllByTaskId(Long taskId); } \ No newline at end of file From e2109da02df5c6d430ace4cb887bd2ff94e3b784 Mon Sep 17 00:00:00 2001 From: wngktjd13 Date: Mon, 12 Jan 2026 21:15:03 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20Task=20=EC=97=B0=EA=B4=80=20?= =?UTF-8?q?=EC=97=B0=EA=B5=AC=EC=84=B1=EA=B3=BC=20=EC=A1=B0=ED=9A=8C=20API?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/task/controller/TaskApi.java | 126 +++---------- .../task/controller/TaskController.java | 84 +++------ .../domain/task/service/TaskService.java | 174 ++++++++---------- 3 files changed, 123 insertions(+), 261 deletions(-) diff --git a/src/main/java/com/bmilab/backend/domain/task/controller/TaskApi.java b/src/main/java/com/bmilab/backend/domain/task/controller/TaskApi.java index 1bbf12d..5ec4417 100644 --- a/src/main/java/com/bmilab/backend/domain/task/controller/TaskApi.java +++ b/src/main/java/com/bmilab/backend/domain/task/controller/TaskApi.java @@ -1,9 +1,9 @@ package com.bmilab.backend.domain.task.controller; +import com.bmilab.backend.domain.research.paper.dto.response.PaperSummaryResponse; +import com.bmilab.backend.domain.research.patent.dto.response.PatentSummaryResponse; +import com.bmilab.backend.domain.research.presentation.dto.response.AcademicPresentationSummaryResponse; import com.bmilab.backend.domain.task.dto.request.AcknowledgementUpdateRequest; -import com.bmilab.backend.domain.task.dto.request.ConferenceRequest; -import com.bmilab.backend.domain.task.dto.request.PatentRequest; -import com.bmilab.backend.domain.task.dto.request.PublicationUpdateRequest; import com.bmilab.backend.domain.task.dto.request.TaskAgreementUpdateRequest; import com.bmilab.backend.domain.task.dto.request.TaskBasicInfoUpdateRequest; import com.bmilab.backend.domain.task.dto.request.TaskPeriodUpdateRequest; @@ -11,9 +11,6 @@ import com.bmilab.backend.domain.task.dto.request.TaskProposalUpdateRequest; import com.bmilab.backend.domain.task.dto.request.TaskRequest; import com.bmilab.backend.domain.task.dto.response.AcknowledgementResponse; -import com.bmilab.backend.domain.task.dto.response.ConferenceResponse; -import com.bmilab.backend.domain.task.dto.response.PatentResponse; -import com.bmilab.backend.domain.task.dto.response.PublicationResponse; import com.bmilab.backend.domain.task.dto.response.TaskAgreementResponse; import com.bmilab.backend.domain.task.dto.response.TaskBasicInfoResponse; import com.bmilab.backend.domain.task.dto.response.TaskPeriodResponse; @@ -449,56 +446,52 @@ ResponseEntity> getTaskProjects( @PathVariable Long taskId ); - @Operation(summary = "논문 정보 조회", description = "과제별 논문 정보를 조회하는 GET API") + @Operation(summary = "과제에 연구프로젝트 추가", description = "과제와 연구프로젝트를 연결하는 POST API") @ApiResponses( value = { @ApiResponse( responseCode = "200", - description = "논문 정보 조회 성공" + description = "연구프로젝트 추가 성공" ), @ApiResponse( responseCode = "404", - description = "과제 정보를 찾을 수 없습니다.", + description = "과제 또는 연구프로젝트 정보를 찾을 수 없습니다.", content = @Content(schema = @Schema(implementation = ErrorResponse.class)) ) } ) - ResponseEntity getPublication( + ResponseEntity addProjectToTask( @AuthenticationPrincipal UserAuthInfo userAuthInfo, - @PathVariable Long taskId + @PathVariable Long taskId, + @PathVariable Long projectId ); - @Operation(summary = "논문 정보 저장", description = "논문 정보를 저장하는 PUT API") + @Operation(summary = "과제에서 연구프로젝트 제거", description = "과제와 연구프로젝트 연결을 해제하는 DELETE API") @ApiResponses( value = { @ApiResponse( responseCode = "200", - description = "논문 정보 저장 성공" + description = "연구프로젝트 제거 성공" ), @ApiResponse( responseCode = "404", - description = "과제 정보를 찾을 수 없습니다.", - content = @Content(schema = @Schema(implementation = ErrorResponse.class)) - ), - @ApiResponse( - responseCode = "400", - description = "저장할 수 없는 상태입니다.", + description = "과제 또는 연구프로젝트 정보를 찾을 수 없습니다.", content = @Content(schema = @Schema(implementation = ErrorResponse.class)) ) } ) - ResponseEntity savePublication( + ResponseEntity removeProjectFromTask( @AuthenticationPrincipal UserAuthInfo userAuthInfo, @PathVariable Long taskId, - @RequestBody PublicationUpdateRequest request + @PathVariable Long projectId ); - @Operation(summary = "학회발표 정보 조회", description = "과제별 학회발표 정보를 조회하는 GET API") + @Operation(summary = "과제 관련 논문 목록 조회", description = "과제와 연결된 논문 목록을 조회하는 GET API") @ApiResponses( value = { @ApiResponse( responseCode = "200", - description = "학회발표 정보 조회 성공" + description = "논문 목록 조회 성공" ), @ApiResponse( responseCode = "404", @@ -507,42 +500,17 @@ ResponseEntity savePublication( ) } ) - ResponseEntity getConference( + ResponseEntity> getTaskPapers( @AuthenticationPrincipal UserAuthInfo userAuthInfo, @PathVariable Long taskId ); - @Operation(summary = "학회발표 정보 저장", description = "학회발표 정보를 저장하는 PUT API") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "학회발표 정보 저장 성공" - ), - @ApiResponse( - responseCode = "404", - description = "과제 정보를 찾을 수 없습니다.", - content = @Content(schema = @Schema(implementation = ErrorResponse.class)) - ), - @ApiResponse( - responseCode = "400", - description = "저장할 수 없는 상태입니다.", - content = @Content(schema = @Schema(implementation = ErrorResponse.class)) - ) - } - ) - ResponseEntity saveConference( - @AuthenticationPrincipal UserAuthInfo userAuthInfo, - @PathVariable Long taskId, - @RequestBody ConferenceRequest request - ); - - @Operation(summary = "특허 정보 조회", description = "과제별 특허 정보를 조회하는 GET API") + @Operation(summary = "과제 관련 학회발표 목록 조회", description = "과제와 연결된 학회발표 목록을 조회하는 GET API") @ApiResponses( value = { @ApiResponse( responseCode = "200", - description = "특허 정보 조회 성공" + description = "학회발표 목록 조회 성공" ), @ApiResponse( responseCode = "404", @@ -551,73 +519,27 @@ ResponseEntity saveConference( ) } ) - ResponseEntity getPatent( + ResponseEntity> getTaskPresentations( @AuthenticationPrincipal UserAuthInfo userAuthInfo, @PathVariable Long taskId ); - @Operation(summary = "특허 정보 저장", description = "특허 정보를 저장하는 PUT API") + @Operation(summary = "과제 관련 특허 목록 조회", description = "과제와 연결된 특허 목록을 조회하는 GET API") @ApiResponses( value = { @ApiResponse( responseCode = "200", - description = "특허 정보 저장 성공" + description = "특허 목록 조회 성공" ), @ApiResponse( responseCode = "404", description = "과제 정보를 찾을 수 없습니다.", content = @Content(schema = @Schema(implementation = ErrorResponse.class)) - ), - @ApiResponse( - responseCode = "400", - description = "저장할 수 없는 상태입니다.", - content = @Content(schema = @Schema(implementation = ErrorResponse.class)) - ) - } - ) - ResponseEntity savePatent( - @AuthenticationPrincipal UserAuthInfo userAuthInfo, - @PathVariable Long taskId, - @RequestBody PatentRequest request - ); - - @Operation(summary = "과제에 연구프로젝트 추가", description = "과제와 연구프로젝트를 연결하는 POST API") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "연구프로젝트 추가 성공" - ), - @ApiResponse( - responseCode = "404", - description = "과제 또는 연구프로젝트 정보를 찾을 수 없습니다.", - content = @Content(schema = @Schema(implementation = ErrorResponse.class)) ) } ) - ResponseEntity addProjectToTask( + ResponseEntity> getTaskPatents( @AuthenticationPrincipal UserAuthInfo userAuthInfo, - @PathVariable Long taskId, - @PathVariable Long projectId - ); - - @Operation(summary = "과제에서 연구프로젝트 제거", description = "과제와 연구프로젝트 연결을 해제하는 DELETE API") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "연구프로젝트 제거 성공" - ), - @ApiResponse( - responseCode = "404", - description = "과제 또는 연구프로젝트 정보를 찾을 수 없습니다.", - content = @Content(schema = @Schema(implementation = ErrorResponse.class)) - ) - } - ) - ResponseEntity removeProjectFromTask( - @AuthenticationPrincipal UserAuthInfo userAuthInfo, - @PathVariable Long taskId, - @PathVariable Long projectId + @PathVariable Long taskId ); } diff --git a/src/main/java/com/bmilab/backend/domain/task/controller/TaskController.java b/src/main/java/com/bmilab/backend/domain/task/controller/TaskController.java index 706e357..3c3f59a 100644 --- a/src/main/java/com/bmilab/backend/domain/task/controller/TaskController.java +++ b/src/main/java/com/bmilab/backend/domain/task/controller/TaskController.java @@ -1,9 +1,9 @@ package com.bmilab.backend.domain.task.controller; +import com.bmilab.backend.domain.research.paper.dto.response.PaperSummaryResponse; +import com.bmilab.backend.domain.research.patent.dto.response.PatentSummaryResponse; +import com.bmilab.backend.domain.research.presentation.dto.response.AcademicPresentationSummaryResponse; import com.bmilab.backend.domain.task.dto.request.AcknowledgementUpdateRequest; -import com.bmilab.backend.domain.task.dto.request.ConferenceRequest; -import com.bmilab.backend.domain.task.dto.request.PatentRequest; -import com.bmilab.backend.domain.task.dto.request.PublicationUpdateRequest; import com.bmilab.backend.domain.task.dto.request.TaskAgreementUpdateRequest; import com.bmilab.backend.domain.task.dto.request.TaskBasicInfoUpdateRequest; import com.bmilab.backend.domain.task.dto.request.TaskPeriodUpdateRequest; @@ -11,9 +11,6 @@ import com.bmilab.backend.domain.task.dto.request.TaskProposalUpdateRequest; import com.bmilab.backend.domain.task.dto.request.TaskRequest; import com.bmilab.backend.domain.task.dto.response.AcknowledgementResponse; -import com.bmilab.backend.domain.task.dto.response.ConferenceResponse; -import com.bmilab.backend.domain.task.dto.response.PatentResponse; -import com.bmilab.backend.domain.task.dto.response.PublicationResponse; import com.bmilab.backend.domain.task.dto.response.TaskAgreementResponse; import com.bmilab.backend.domain.task.dto.response.TaskBasicInfoResponse; import com.bmilab.backend.domain.task.dto.response.TaskPeriodResponse; @@ -246,83 +243,50 @@ public ResponseEntity> getTaskProjects( return ResponseEntity.ok(response); } - @GetMapping("/{taskId}/publication") - public ResponseEntity getPublication( - @AuthenticationPrincipal UserAuthInfo userAuthInfo, - @PathVariable Long taskId - ) { - PublicationResponse response = taskService.getPublication(userAuthInfo.getUserId(), taskId); - return ResponseEntity.ok(response); - } - - @PutMapping("/{taskId}/publication") - public ResponseEntity savePublication( + @PostMapping("/{taskId}/projects/{projectId}") + public ResponseEntity addProjectToTask( @AuthenticationPrincipal UserAuthInfo userAuthInfo, @PathVariable Long taskId, - @RequestBody @Valid PublicationUpdateRequest request + @PathVariable Long projectId ) { - boolean isAdmin = userAuthInfo.getUser().getRole() == Role.ADMIN; - taskService.savePublication(userAuthInfo.getUserId(), isAdmin, taskId, request); + taskService.addProjectToTask(userAuthInfo.getUserId(), taskId, projectId); return ResponseEntity.ok().build(); } - @GetMapping("/{taskId}/conference") - public ResponseEntity getConference( - @AuthenticationPrincipal UserAuthInfo userAuthInfo, - @PathVariable Long taskId - ) { - ConferenceResponse response = taskService.getConference(userAuthInfo.getUserId(), taskId); - return ResponseEntity.ok(response); - } - - @PutMapping("/{taskId}/conference") - public ResponseEntity saveConference( + @DeleteMapping("/{taskId}/projects/{projectId}") + public ResponseEntity removeProjectFromTask( @AuthenticationPrincipal UserAuthInfo userAuthInfo, @PathVariable Long taskId, - @RequestBody @Valid ConferenceRequest request + @PathVariable Long projectId ) { - boolean isAdmin = userAuthInfo.getUser().getRole() == Role.ADMIN; - taskService.saveConference(userAuthInfo.getUserId(), isAdmin, taskId, request); + taskService.removeProjectFromTask(userAuthInfo.getUserId(), taskId, projectId); return ResponseEntity.ok().build(); } - @GetMapping("/{taskId}/patent") - public ResponseEntity getPatent( + @GetMapping("/{taskId}/papers") + public ResponseEntity> getTaskPapers( @AuthenticationPrincipal UserAuthInfo userAuthInfo, @PathVariable Long taskId ) { - PatentResponse response = taskService.getPatent(userAuthInfo.getUserId(), taskId); + List response = taskService.getTaskPapers(userAuthInfo.getUserId(), taskId); return ResponseEntity.ok(response); } - @PutMapping("/{taskId}/patent") - public ResponseEntity savePatent( + @GetMapping("/{taskId}/presentations") + public ResponseEntity> getTaskPresentations( @AuthenticationPrincipal UserAuthInfo userAuthInfo, - @PathVariable Long taskId, - @RequestBody @Valid PatentRequest request - ) { - boolean isAdmin = userAuthInfo.getUser().getRole() == Role.ADMIN; - taskService.savePatent(userAuthInfo.getUserId(), isAdmin, taskId, request); - return ResponseEntity.ok().build(); - } - - @PostMapping("/{taskId}/projects/{projectId}") - public ResponseEntity addProjectToTask( - @AuthenticationPrincipal UserAuthInfo userAuthInfo, - @PathVariable Long taskId, - @PathVariable Long projectId + @PathVariable Long taskId ) { - taskService.addProjectToTask(userAuthInfo.getUserId(), taskId, projectId); - return ResponseEntity.ok().build(); + List response = taskService.getTaskPresentations(userAuthInfo.getUserId(), taskId); + return ResponseEntity.ok(response); } - @DeleteMapping("/{taskId}/projects/{projectId}") - public ResponseEntity removeProjectFromTask( + @GetMapping("/{taskId}/patents") + public ResponseEntity> getTaskPatents( @AuthenticationPrincipal UserAuthInfo userAuthInfo, - @PathVariable Long taskId, - @PathVariable Long projectId + @PathVariable Long taskId ) { - taskService.removeProjectFromTask(userAuthInfo.getUserId(), taskId, projectId); - return ResponseEntity.ok().build(); + List response = taskService.getTaskPatents(userAuthInfo.getUserId(), taskId); + return ResponseEntity.ok(response); } } diff --git a/src/main/java/com/bmilab/backend/domain/task/service/TaskService.java b/src/main/java/com/bmilab/backend/domain/task/service/TaskService.java index 302876e..5bb7ce8 100644 --- a/src/main/java/com/bmilab/backend/domain/task/service/TaskService.java +++ b/src/main/java/com/bmilab/backend/domain/task/service/TaskService.java @@ -6,10 +6,24 @@ import com.bmilab.backend.domain.project.entity.Project; import com.bmilab.backend.domain.project.exception.ProjectErrorCode; import com.bmilab.backend.domain.project.repository.ProjectRepository; +import com.bmilab.backend.domain.research.paper.dto.response.PaperSummaryResponse; +import com.bmilab.backend.domain.research.paper.entity.Paper; +import com.bmilab.backend.domain.research.paper.entity.PaperAuthor; +import com.bmilab.backend.domain.research.paper.entity.PaperCorrespondingAuthor; +import com.bmilab.backend.domain.research.paper.repository.PaperAuthorRepository; +import com.bmilab.backend.domain.research.paper.repository.PaperCorrespondingAuthorRepository; +import com.bmilab.backend.domain.research.paper.repository.PaperRepository; +import com.bmilab.backend.domain.research.patent.dto.response.PatentSummaryResponse; +import com.bmilab.backend.domain.research.patent.entity.Patent; +import com.bmilab.backend.domain.research.patent.entity.PatentAuthor; +import com.bmilab.backend.domain.research.patent.repository.PatentAuthorRepository; +import com.bmilab.backend.domain.research.patent.repository.PatentRepository; +import com.bmilab.backend.domain.research.presentation.dto.response.AcademicPresentationSummaryResponse; +import com.bmilab.backend.domain.research.presentation.entity.AcademicPresentation; +import com.bmilab.backend.domain.research.presentation.entity.AcademicPresentationAuthor; +import com.bmilab.backend.domain.research.presentation.repository.AcademicPresentationAuthorRepository; +import com.bmilab.backend.domain.research.presentation.repository.AcademicPresentationRepository; import com.bmilab.backend.domain.task.dto.request.AcknowledgementUpdateRequest; -import com.bmilab.backend.domain.task.dto.request.ConferenceRequest; -import com.bmilab.backend.domain.task.dto.request.PatentRequest; -import com.bmilab.backend.domain.task.dto.request.PublicationUpdateRequest; import com.bmilab.backend.domain.task.dto.request.TaskAgreementUpdateRequest; import com.bmilab.backend.domain.task.dto.request.TaskBasicInfoUpdateRequest; import com.bmilab.backend.domain.task.dto.request.TaskPeriodRequest; @@ -18,9 +32,6 @@ import com.bmilab.backend.domain.task.dto.request.TaskProposalUpdateRequest; import com.bmilab.backend.domain.task.dto.request.TaskRequest; import com.bmilab.backend.domain.task.dto.response.AcknowledgementResponse; -import com.bmilab.backend.domain.task.dto.response.ConferenceResponse; -import com.bmilab.backend.domain.task.dto.response.PatentResponse; -import com.bmilab.backend.domain.task.dto.response.PublicationResponse; import com.bmilab.backend.domain.task.dto.response.TaskAgreementResponse; import com.bmilab.backend.domain.task.dto.response.TaskBasicInfoResponse; import com.bmilab.backend.domain.task.dto.response.TaskMemberSummary; @@ -64,12 +75,16 @@ public class TaskService { private final TaskPresentationMakerRepository taskPresentationMakerRepository; private final TaskAgreementRepository taskAgreementRepository; private final AcknowledgementRepository acknowledgementRepository; - private final TaskPublicationRepository publicationRepository; - private final ConferenceRepository conferenceRepository; - private final TaskPatentRepository patentRepository; private final ProjectRepository projectRepository; private final UserService userService; private final FileService fileService; + private final PaperRepository paperRepository; + private final PaperAuthorRepository paperAuthorRepository; + private final PaperCorrespondingAuthorRepository paperCorrespondingAuthorRepository; + private final AcademicPresentationRepository academicPresentationRepository; + private final AcademicPresentationAuthorRepository academicPresentationAuthorRepository; + private final PatentRepository patentRepository; + private final PatentAuthorRepository patentAuthorRepository; @Transactional @@ -680,96 +695,6 @@ public List getTaskProjects(Long userId, Long taskId) { return projects.stream().map(TaskProjectSummary::from).collect(Collectors.toList()); } - public PublicationResponse getPublication(Long userId, Long taskId) { - - Task task = getTaskById(taskId); - Publication publication = publicationRepository.findByTask(task).orElse(null); - - return PublicationResponse.from(publication); - } - - @Transactional - public void savePublication(Long userId, boolean isAdmin, Long taskId, PublicationUpdateRequest request) { - - Task task = getTaskById(taskId); - - if (!task.canBeEditedByUser(userId, isAdmin)) { - throw new ApiException(TaskErrorCode.TASK_CANNOT_EDIT); - } - - Publication publication = publicationRepository.findByTask(task) - .orElseGet(() -> Publication.builder().task(task).build()); - - publication.update( - request.title(), - request.authors(), - request.journal(), - request.publicationDate(), - request.doi() - ); - - publicationRepository.save(publication); - } - - public ConferenceResponse getConference(Long userId, Long taskId) { - - Task task = getTaskById(taskId); - Conference conference = conferenceRepository.findByTask(task).orElse(null); - - return ConferenceResponse.from(conference); - } - - @Transactional - public void saveConference(Long userId, boolean isAdmin, Long taskId, ConferenceRequest request) { - - Task task = getTaskById(taskId); - - if (!task.canBeEditedByUser(userId, isAdmin)) { - throw new ApiException(TaskErrorCode.TASK_CANNOT_EDIT); - } - - Conference conference = conferenceRepository.findByTask(task) - .orElseGet(() -> Conference.builder().task(task).build()); - - conference.update( - request.presentationTitle(), - request.conferenceName(), - request.presenter(), - request.presentationDate() - ); - - conferenceRepository.save(conference); - } - - public PatentResponse getPatent(Long userId, Long taskId) { - - Task task = getTaskById(taskId); - Patent patent = patentRepository.findByTask(task).orElse(null); - - return PatentResponse.from(patent); - } - - @Transactional - public void savePatent(Long userId, boolean isAdmin, Long taskId, PatentRequest request) { - - Task task = getTaskById(taskId); - - if (!task.canBeEditedByUser(userId, isAdmin)) { - throw new ApiException(TaskErrorCode.TASK_CANNOT_EDIT); - } - - Patent patent = patentRepository.findByTask(task) - .orElseGet(() -> Patent.builder().task(task).build()); - - patent.update( - request.patentTitle(), - request.patentNumber(), - request.applicationDate() - ); - - patentRepository.save(patent); - } - @Transactional public void addProjectToTask(Long userId, Long taskId, Long projectId) { Task task = getTaskById(taskId); @@ -792,6 +717,57 @@ public void removeProjectFromTask(Long userId, Long taskId, Long projectId) { projectRepository.save(project); } + public List getTaskPapers(Long userId, Long taskId) { + Task task = getTaskById(taskId); + List papers = paperRepository.findAllByTaskId(taskId); + + return papers.stream() + .map(paper -> { + List correspondingAuthors = + paperCorrespondingAuthorRepository.findAllByPaperId(paper.getId()); + List paperAuthors = + paperAuthorRepository.findAllByPaperId(paper.getId()); + List files = fileService.findAllByDomainTypeAndEntityId( + FileDomainType.PAPER_ATTACHMENT, paper.getId()) + .stream() + .map(FileSummary::from) + .toList(); + return PaperSummaryResponse.from(paper, correspondingAuthors, paperAuthors, files); + }) + .toList(); + } + + public List getTaskPresentations(Long userId, Long taskId) { + Task task = getTaskById(taskId); + List presentations = academicPresentationRepository.findAllByTaskId(taskId); + + return presentations.stream() + .map(presentation -> { + List authors = + academicPresentationAuthorRepository.findAllByAcademicPresentationId(presentation.getId()); + return AcademicPresentationSummaryResponse.from(presentation, authors); + }) + .toList(); + } + + public List getTaskPatents(Long userId, Long taskId) { + Task task = getTaskById(taskId); + List patents = patentRepository.findAllByTaskId(taskId); + + return patents.stream() + .map(patent -> { + List patentAuthors = + patentAuthorRepository.findAllByPatentId(patent.getId()); + List files = fileService.findAllByDomainTypeAndEntityId( + FileDomainType.PATENT_ATTACHMENT, patent.getId()) + .stream() + .map(FileSummary::from) + .toList(); + return PatentSummaryResponse.from(patent, patentAuthors, files); + }) + .toList(); + } + private Task getTaskById(Long taskId) { return taskRepository.findById(taskId).orElseThrow(() -> new ApiException(TaskErrorCode.TASK_NOT_FOUND)); From a72f1a52d52a598daef98256cc11fa54db5c3920 Mon Sep 17 00:00:00 2001 From: wngktjd13 Date: Mon, 12 Jan 2026 21:57:44 +0900 Subject: [PATCH 5/8] =?UTF-8?q?fix:=20=EC=97=B0=EA=B5=AC=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EB=8F=99=EC=A0=81=20=EA=B3=84=EC=82=B0=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/query/GetAllProjectsQueryResult.java | 18 +++++++++++++++ .../project/dto/response/ProjectDetail.java | 2 +- .../dto/response/ProjectFindAllResponse.java | 2 +- .../response/UserProjectFindAllResponse.java | 2 +- .../domain/project/entity/Project.java | 22 +++++++++++++++++++ 5 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/bmilab/backend/domain/project/dto/query/GetAllProjectsQueryResult.java b/src/main/java/com/bmilab/backend/domain/project/dto/query/GetAllProjectsQueryResult.java index dee0361..c1b9a37 100644 --- a/src/main/java/com/bmilab/backend/domain/project/dto/query/GetAllProjectsQueryResult.java +++ b/src/main/java/com/bmilab/backend/domain/project/dto/query/GetAllProjectsQueryResult.java @@ -39,4 +39,22 @@ public class GetAllProjectsQueryResult { private boolean isAccessible; private boolean isPinned; + + public ProjectStatus getEffectiveStatus() { + if (this.status == ProjectStatus.WAITING) { + return ProjectStatus.WAITING; + } + + LocalDate today = LocalDate.now(); + + if (this.endDate != null && today.isAfter(this.endDate)) { + return ProjectStatus.COMPLETED; + } + + if (this.startDate != null && !today.isBefore(this.startDate)) { + return ProjectStatus.IN_PROGRESS; + } + + return ProjectStatus.PENDING; + } } diff --git a/src/main/java/com/bmilab/backend/domain/project/dto/response/ProjectDetail.java b/src/main/java/com/bmilab/backend/domain/project/dto/response/ProjectDetail.java index 1473e10..55b92ff 100644 --- a/src/main/java/com/bmilab/backend/domain/project/dto/response/ProjectDetail.java +++ b/src/main/java/com/bmilab/backend/domain/project/dto/response/ProjectDetail.java @@ -112,7 +112,7 @@ public static ProjectDetail from(Project project, List parti .category(ProjectCategorySummary.from(project.getCategory())) .isPrivate(project.isPrivate()) .isAccessible(isAccessible) - .status(project.getStatus()) + .status(project.getEffectiveStatus()) .irbId(project.getIrbId()) .drbId(project.getDrbId()) .piList( diff --git a/src/main/java/com/bmilab/backend/domain/project/dto/response/ProjectFindAllResponse.java b/src/main/java/com/bmilab/backend/domain/project/dto/response/ProjectFindAllResponse.java index 6a315a3..8b94849 100644 --- a/src/main/java/com/bmilab/backend/domain/project/dto/response/ProjectFindAllResponse.java +++ b/src/main/java/com/bmilab/backend/domain/project/dto/response/ProjectFindAllResponse.java @@ -85,7 +85,7 @@ public static ProjectSummary from(GetAllProjectsQueryResult queryResult) { .toList() ) .participantCount(queryResult.getParticipantCount().intValue()) - .status(queryResult.getStatus()) + .status(queryResult.getEffectiveStatus()) .isPrivate(queryResult.isPrivate()) .isAccessible(queryResult.isAccessible()) .isPinned(queryResult.isPinned()) diff --git a/src/main/java/com/bmilab/backend/domain/project/dto/response/UserProjectFindAllResponse.java b/src/main/java/com/bmilab/backend/domain/project/dto/response/UserProjectFindAllResponse.java index 93a2621..c9529c5 100644 --- a/src/main/java/com/bmilab/backend/domain/project/dto/response/UserProjectFindAllResponse.java +++ b/src/main/java/com/bmilab/backend/domain/project/dto/response/UserProjectFindAllResponse.java @@ -45,7 +45,7 @@ public static UserProjectItem from(Project project) { .builder() .projectId(project.getId()) .title(project.getTitle()) - .status(project.getStatus()) + .status(project.getEffectiveStatus()) .category(project.getCategory() == null ? null : ProjectCategorySummary.from(project.getCategory())) .startDate(project.getStartDate()) .endDate(project.getEndDate()) diff --git a/src/main/java/com/bmilab/backend/domain/project/entity/Project.java b/src/main/java/com/bmilab/backend/domain/project/entity/Project.java index 4bd85f2..5321b12 100644 --- a/src/main/java/com/bmilab/backend/domain/project/entity/Project.java +++ b/src/main/java/com/bmilab/backend/domain/project/entity/Project.java @@ -125,6 +125,28 @@ public void complete(LocalDate endDate) { this.status = ProjectStatus.COMPLETED; } + public void updateStatus(ProjectStatus status) { + this.status = status; + } + + public ProjectStatus getEffectiveStatus() { + if (this.status == ProjectStatus.WAITING) { + return ProjectStatus.WAITING; + } + + LocalDate today = LocalDate.now(); + + if (this.endDate != null && today.isAfter(this.endDate)) { + return ProjectStatus.COMPLETED; + } + + if (this.startDate != null && !today.isBefore(this.startDate)) { + return ProjectStatus.IN_PROGRESS; + } + + return ProjectStatus.PENDING; + } + public List getPIList() { return List.of(pi.split(",")); } From fb01cc3999ae0e2c5d62167aed31f11eed9c22ab Mon Sep 17 00:00:00 2001 From: wngktjd13 Date: Mon, 12 Jan 2026 21:59:10 +0900 Subject: [PATCH 6/8] =?UTF-8?q?fix:=201=EC=9B=94=20=EC=97=B0=EC=B0=A8=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/domain/leave/service/LeaveSchedulerService.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/com/bmilab/backend/domain/leave/service/LeaveSchedulerService.java b/src/main/java/com/bmilab/backend/domain/leave/service/LeaveSchedulerService.java index 0fb050f..cf1fa00 100644 --- a/src/main/java/com/bmilab/backend/domain/leave/service/LeaveSchedulerService.java +++ b/src/main/java/com/bmilab/backend/domain/leave/service/LeaveSchedulerService.java @@ -35,10 +35,6 @@ public class LeaveSchedulerService { public void updateUserAnnualLeaves() { YearMonth lastMonthWithYear = YearMonth.now().minusMonths(1); - if (lastMonthWithYear.getYear() == 2025) { - return; - } - int endOfMonth = lastMonthWithYear.lengthOfMonth(); int holidayCount = countHolidays(lastMonthWithYear); int weekendCount = (int) countWeekends(lastMonthWithYear); From e464386f1ed12442153d1b285847c392a0979a9c Mon Sep 17 00:00:00 2001 From: wngktjd13 Date: Mon, 12 Jan 2026 21:59:25 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20=ED=9C=B4=EA=B0=80=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EC=8B=A0=EC=B2=AD=20=EB=B0=A9=EC=A7=80=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../leave/exception/LeaveErrorCode.java | 2 + .../leave/repository/LeaveRepository.java | 9 ++++ .../domain/leave/service/LeaveService.java | 51 ++++++++++++++++++- 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/bmilab/backend/domain/leave/exception/LeaveErrorCode.java b/src/main/java/com/bmilab/backend/domain/leave/exception/LeaveErrorCode.java index 85170f4..153759f 100644 --- a/src/main/java/com/bmilab/backend/domain/leave/exception/LeaveErrorCode.java +++ b/src/main/java/com/bmilab/backend/domain/leave/exception/LeaveErrorCode.java @@ -10,6 +10,8 @@ public enum LeaveErrorCode implements ErrorCode { LEAVE_NOT_FOUND("휴가 정보를 찾을 수 없습니다.", HttpStatus.NOT_FOUND), LEAVE_ALREADY_DONE("이미 처리된 휴가입니다.", HttpStatus.BAD_REQUEST), + LEAVE_DUPLICATE("해당 날짜에 이미 신청된 휴가가 있습니다.", HttpStatus.CONFLICT), + LEAVE_NOT_APPROVED("승인된 휴가만 수정할 수 있습니다.", HttpStatus.BAD_REQUEST), LEAVE_COUNT_REQUIRED("연가 수가 부족합니다.", HttpStatus.BAD_REQUEST), USER_LEAVE_NOT_FOUND("사용자의 휴가 정보를 찾을 수 없습니다.", HttpStatus.NOT_FOUND), HOLIDAY_API_ERROR("공휴일 API 조회 중 오류가 발생했습니다.", HttpStatus.INTERNAL_SERVER_ERROR), diff --git a/src/main/java/com/bmilab/backend/domain/leave/repository/LeaveRepository.java b/src/main/java/com/bmilab/backend/domain/leave/repository/LeaveRepository.java index bdbacee..d06e542 100644 --- a/src/main/java/com/bmilab/backend/domain/leave/repository/LeaveRepository.java +++ b/src/main/java/com/bmilab/backend/domain/leave/repository/LeaveRepository.java @@ -39,4 +39,13 @@ public interface LeaveRepository extends JpaRepository { Page findAllByStatus(LeaveStatus status, Pageable pageable); List findAllByStatus(LeaveStatus status); + + @Query("SELECT COUNT(l) > 0 FROM Leave l " + + "WHERE l.user.id = :userId " + + "AND l.status != 'REJECTED' " + + "AND ( " + + " (l.endDate IS NULL AND l.startDate BETWEEN :startDate AND :endDate) " + + " OR (l.endDate IS NOT NULL AND l.startDate <= :endDate AND l.endDate >= :startDate) " + + ")") + boolean existsOverlappingLeave(Long userId, LocalDate startDate, LocalDate endDate); } diff --git a/src/main/java/com/bmilab/backend/domain/leave/service/LeaveService.java b/src/main/java/com/bmilab/backend/domain/leave/service/LeaveService.java index 0b79701..7ddd925 100644 --- a/src/main/java/com/bmilab/backend/domain/leave/service/LeaveService.java +++ b/src/main/java/com/bmilab/backend/domain/leave/service/LeaveService.java @@ -1,5 +1,6 @@ package com.bmilab.backend.domain.leave.service; +import com.bmilab.backend.domain.leave.dto.request.AdminUpdateLeaveRequest; import com.bmilab.backend.domain.leave.dto.request.ApplyLeaveRequest; import com.bmilab.backend.domain.leave.dto.request.RejectLeaveRequest; import com.bmilab.backend.domain.leave.dto.response.LeaveFindAllResponse; @@ -75,9 +76,12 @@ public UserLeaveResponse getLeavesByUser(Long userId, Pageable pageable) { public void applyLeave(Long userId, ApplyLeaveRequest request) { User user = userService.findUserById(userId); LeaveType type = request.type(); - double leaveCount = (request.endDate() == null) ? 1 : calculateLeaveCount(request.startDate(), request.endDate()); + LocalDate startDate = request.startDate(); + LocalDate endDate = request.endDate() != null ? request.endDate() : startDate; + double leaveCount = (request.endDate() == null) ? 1 : calculateLeaveCount(startDate, endDate); validateLeaveTypeAccessPermission(user, type); + validateNoDuplicateLeave(userId, startDate, endDate); if (type.isHalf()) { leaveCount *= 0.5; @@ -136,6 +140,45 @@ public void rejectLeave(Long processorId, long leaveId, RejectLeaveRequest reque leave.reject(request.rejectReason(), processor, LocalDateTime.now()); } + @Transactional + public void updateLeaveByAdmin(long leaveId, AdminUpdateLeaveRequest request) { + Leave leave = leaveRepository.findById(leaveId) + .orElseThrow(() -> new ApiException(LeaveErrorCode.LEAVE_NOT_FOUND)); + + if (!leave.isApproved()) { + throw new ApiException(LeaveErrorCode.LEAVE_NOT_APPROVED); + } + + User user = leave.getUser(); + UserLeave userLeave = userLeaveRepository.findByUserId(user.getId()) + .orElseThrow(() -> new ApiException(LeaveErrorCode.USER_LEAVE_NOT_FOUND)); + + // 기존 휴가 일수 복원 + userLeave.restoreLeave(leave.getLeaveCount(), leave.isAnnualLeave()); + + // 새로운 휴가 일수 계산 + LocalDate startDate = request.startDate(); + LocalDate endDate = request.endDate() != null ? request.endDate() : startDate; + LeaveType newType = request.type(); + double newLeaveCount = (request.endDate() == null) ? 1 : calculateLeaveCount(startDate, endDate); + + if (newType.isHalf()) { + newLeaveCount *= 0.5; + } + + // 새로운 휴가 일수 차감 + if (newType == LeaveType.ANNUAL && userLeave.getAnnualLeaveCount() < newLeaveCount) { + // 복원한 것을 다시 되돌림 + userLeave.useLeave(leave.getLeaveCount(), leave.isAnnualLeave()); + throw new ApiException(LeaveErrorCode.LEAVE_COUNT_REQUIRED); + } + + userLeave.useLeave(newLeaveCount, newType == LeaveType.ANNUAL); + + // 휴가 정보 업데이트 + leave.update(startDate, request.endDate(), newType, newLeaveCount, request.reason()); + } + public int countDatesByUserIdWithMonth(long userId, YearMonth yearMonth) { return leaveRepository.findAllByUserId(userId) .stream() @@ -149,4 +192,10 @@ private void validateLeaveTypeAccessPermission(User user, LeaveType type) { throw new ApiException(LeaveErrorCode.ACCESS_DENIED); } } + + private void validateNoDuplicateLeave(Long userId, LocalDate startDate, LocalDate endDate) { + if (leaveRepository.existsOverlappingLeave(userId, startDate, endDate)) { + throw new ApiException(LeaveErrorCode.LEAVE_DUPLICATE); + } + } } From 12eac07c4a30527f70a2613d36724cecee0cd459 Mon Sep 17 00:00:00 2001 From: wngktjd13 Date: Mon, 12 Jan 2026 21:59:53 +0900 Subject: [PATCH 8/8] =?UTF-8?q?feat:=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=EC=8A=B9=EC=9D=B8=20=ED=9B=84=20=ED=9C=B4=EA=B0=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../leave/controller/AdminLeaveApi.java | 23 ++++++++++++++ .../controller/AdminLeaveController.java | 11 +++++++ .../dto/request/AdminUpdateLeaveRequest.java | 30 +++++++++++++++++++ .../backend/domain/leave/entity/Leave.java | 8 +++++ .../domain/leave/entity/UserLeave.java | 7 +++++ 5 files changed, 79 insertions(+) create mode 100644 src/main/java/com/bmilab/backend/domain/leave/dto/request/AdminUpdateLeaveRequest.java diff --git a/src/main/java/com/bmilab/backend/domain/leave/controller/AdminLeaveApi.java b/src/main/java/com/bmilab/backend/domain/leave/controller/AdminLeaveApi.java index dae5409..7ea1b82 100644 --- a/src/main/java/com/bmilab/backend/domain/leave/controller/AdminLeaveApi.java +++ b/src/main/java/com/bmilab/backend/domain/leave/controller/AdminLeaveApi.java @@ -1,5 +1,6 @@ package com.bmilab.backend.domain.leave.controller; +import com.bmilab.backend.domain.leave.dto.request.AdminUpdateLeaveRequest; import com.bmilab.backend.domain.leave.dto.request.RejectLeaveRequest; import com.bmilab.backend.domain.leave.dto.response.LeaveFindAllResponse; import com.bmilab.backend.domain.leave.enums.LeaveStatus; @@ -72,4 +73,26 @@ ResponseEntity getLeaves( @RequestParam(required = false) LeaveStatus status, @PageableDefault(size = 10, sort = "applicatedAt", direction = Sort.Direction.DESC) @ParameterObject Pageable pageable ); + + @Operation(summary = "승인된 휴가 수정", description = "승인된 휴가 정보를 수정하는 PATCH API") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "휴가 수정 성공" + ), + @ApiResponse( + responseCode = "400", + description = "승인된 휴가만 수정할 수 있습니다." + ), + @ApiResponse( + responseCode = "404", + description = "휴가 정보를 찾을 수 없습니다." + ) + } + ) + ResponseEntity updateLeave( + @PathVariable long leaveId, + AdminUpdateLeaveRequest request + ); } diff --git a/src/main/java/com/bmilab/backend/domain/leave/controller/AdminLeaveController.java b/src/main/java/com/bmilab/backend/domain/leave/controller/AdminLeaveController.java index b4820c5..3a09fec 100644 --- a/src/main/java/com/bmilab/backend/domain/leave/controller/AdminLeaveController.java +++ b/src/main/java/com/bmilab/backend/domain/leave/controller/AdminLeaveController.java @@ -1,5 +1,6 @@ package com.bmilab.backend.domain.leave.controller; +import com.bmilab.backend.domain.leave.dto.request.AdminUpdateLeaveRequest; import com.bmilab.backend.domain.leave.dto.request.RejectLeaveRequest; import com.bmilab.backend.domain.leave.dto.response.LeaveFindAllResponse; import com.bmilab.backend.domain.leave.enums.LeaveStatus; @@ -15,6 +16,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -57,4 +59,13 @@ public ResponseEntity getLeaves( ) { return ResponseEntity.ok(leaveService.getLeavesByAdmin(status, pageable)); } + + @PatchMapping("/{leaveId}") + public ResponseEntity updateLeave( + @PathVariable long leaveId, + @RequestBody @Valid AdminUpdateLeaveRequest request + ) { + leaveService.updateLeaveByAdmin(leaveId, request); + return ResponseEntity.ok().build(); + } } diff --git a/src/main/java/com/bmilab/backend/domain/leave/dto/request/AdminUpdateLeaveRequest.java b/src/main/java/com/bmilab/backend/domain/leave/dto/request/AdminUpdateLeaveRequest.java new file mode 100644 index 0000000..d367a65 --- /dev/null +++ b/src/main/java/com/bmilab/backend/domain/leave/dto/request/AdminUpdateLeaveRequest.java @@ -0,0 +1,30 @@ +package com.bmilab.backend.domain.leave.dto.request; + +import com.bmilab.backend.domain.leave.enums.LeaveType; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.annotation.Nullable; +import jakarta.validation.constraints.NotNull; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; + +public record AdminUpdateLeaveRequest( + @Schema(description = "휴가 시작일") + @NotNull(message = "휴가 시작일은 필수입니다.") + @DateTimeFormat(pattern = "yyyy-MM-dd") + LocalDate startDate, + + @Schema(description = "휴가 종료일") + @Nullable + @DateTimeFormat(pattern = "yyyy-MM-dd") + LocalDate endDate, + + @Schema(description = "휴가 종류", example = "ANNUAL") + @NotNull(message = "휴가 종류는 필수입니다.") + LeaveType type, + + @Schema(description = "휴가 사유", example = "가족 행사 참석") + @Nullable + String reason +) { +} diff --git a/src/main/java/com/bmilab/backend/domain/leave/entity/Leave.java b/src/main/java/com/bmilab/backend/domain/leave/entity/Leave.java index 6498dc2..e8f030d 100644 --- a/src/main/java/com/bmilab/backend/domain/leave/entity/Leave.java +++ b/src/main/java/com/bmilab/backend/domain/leave/entity/Leave.java @@ -103,6 +103,14 @@ public boolean isAnnualLeave() { return type == LeaveType.ANNUAL; } + public void update(LocalDate startDate, LocalDate endDate, LeaveType type, Double leaveCount, String reason) { + this.startDate = startDate; + this.endDate = endDate; + this.type = type; + this.leaveCount = leaveCount; + this.reason = reason; + } + public int countDaysInYearMonth(YearMonth yearMonth) { if (endDate == null) { return YearMonth.from(startDate).equals(yearMonth) ? 1 : 0; diff --git a/src/main/java/com/bmilab/backend/domain/leave/entity/UserLeave.java b/src/main/java/com/bmilab/backend/domain/leave/entity/UserLeave.java index 9c9549b..0c0e04a 100644 --- a/src/main/java/com/bmilab/backend/domain/leave/entity/UserLeave.java +++ b/src/main/java/com/bmilab/backend/domain/leave/entity/UserLeave.java @@ -60,4 +60,11 @@ public void increaseAnnualLeaveCount(Double leaveIncrement) { public void updateAnnualLeaveCount(Double annualLeaveCount) { this.annualLeaveCount = annualLeaveCount; } + + public void restoreLeave(Double leaveCount, boolean isAnnualLeave) { + if (isAnnualLeave) { + annualLeaveCount += leaveCount; + } + usedLeaveCount -= leaveCount; + } }