Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ ResponseEntity<LeaveFindAllResponse> getLeaves(
@PageableDefault(size = 10, sort = "applicatedAt", direction = Sort.Direction.DESC) @ParameterObject Pageable pageable
);

@Operation(summary = "승인된 휴가 수정", description = "승인된 휴가 정보를 수정하는 PATCH API")
@Operation(summary = "승인된 휴가 수정", description = "승인된 휴가 정보를 수정하는 PATCH API (시작일이 지나지 않은 휴가만 수정 가능)")
@ApiResponses(
value = {
@ApiResponse(
Expand All @@ -83,7 +83,7 @@ ResponseEntity<LeaveFindAllResponse> getLeaves(
),
@ApiResponse(
responseCode = "400",
description = "승인된 휴가만 수정할 수 있습니다."
description = "승인된 휴가만 수정할 수 있습니다. / 이미 지난 휴가는 수정할 수 없습니다."
),
@ApiResponse(
responseCode = "404",
Expand All @@ -95,4 +95,19 @@ ResponseEntity<Void> updateLeave(
@PathVariable long leaveId,
AdminUpdateLeaveRequest request
);

@Operation(summary = "휴가 삭제", description = "휴가를 삭제하는 DELETE API (승인된 휴가 삭제 시 휴가 일수 복원)")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "휴가 삭제 성공"
),
@ApiResponse(
responseCode = "404",
description = "휴가 정보를 찾을 수 없습니다."
)
}
)
ResponseEntity<Void> deleteLeave(@PathVariable long leaveId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand Down Expand Up @@ -68,4 +69,11 @@ public ResponseEntity<Void> updateLeave(
leaveService.updateLeaveByAdmin(leaveId, request);
return ResponseEntity.ok().build();
}

@Override
@DeleteMapping("/{leaveId}")
public ResponseEntity<Void> deleteLeave(@PathVariable long leaveId) {
leaveService.deleteLeaveByAdmin(leaveId);
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

@Tag(name = "Leave", description = "휴가 API")
Expand Down Expand Up @@ -62,4 +63,26 @@ ResponseEntity<Void> applyLeave(
@AuthenticationPrincipal UserAuthInfo userAuthInfo,
ApplyLeaveRequest request
);

@Operation(summary = "휴가 취소", description = "대기 상태인 휴가를 취소하는 DELETE API")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "취소 성공"
),
@ApiResponse(
responseCode = "400",
description = "대기 상태인 휴가만 취소할 수 있습니다."
),
@ApiResponse(
responseCode = "404",
description = "휴가 정보를 찾을 수 없습니다."
)
}
)
ResponseEntity<Void> cancelLeave(
@AuthenticationPrincipal UserAuthInfo userAuthInfo,
@PathVariable long leaveId
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
Expand Down Expand Up @@ -53,4 +55,13 @@ public ResponseEntity<Void> applyLeave(
leaveService.applyLeave(userAuthInfo.getUserId(), request);
return ResponseEntity.ok().build();
}

@DeleteMapping("/{leaveId}")
public ResponseEntity<Void> cancelLeave(
@AuthenticationPrincipal UserAuthInfo userAuthInfo,
@PathVariable long leaveId
) {
leaveService.cancelLeaveByUser(userAuthInfo.getUserId(), leaveId);
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ public void reject(String rejectReason, User processor, LocalDateTime now) {
processedAt = now;
}

public boolean isPending() {
return status == LeaveStatus.PENDING;
}

public boolean isNotPending() {
return status != LeaveStatus.PENDING;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public enum LeaveErrorCode implements ErrorCode {
USER_LEAVE_NOT_FOUND("사용자의 휴가 정보를 찾을 수 없습니다.", HttpStatus.NOT_FOUND),
HOLIDAY_API_ERROR("공휴일 API 조회 중 오류가 발생했습니다.", HttpStatus.INTERNAL_SERVER_ERROR),
ACCESS_DENIED("신청 권한이 없습니다.", HttpStatus.FORBIDDEN),
LEAVE_CANNOT_CANCEL_NOT_PENDING("대기 상태인 휴가만 취소할 수 있습니다.", HttpStatus.BAD_REQUEST),
LEAVE_ALREADY_PASSED("이미 지난 휴가는 수정할 수 없습니다.", HttpStatus.BAD_REQUEST),
;

private final String message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ public void updateLeaveByAdmin(long leaveId, AdminUpdateLeaveRequest request) {
throw new ApiException(LeaveErrorCode.LEAVE_NOT_APPROVED);
}

if (leave.getStartDate().isBefore(LocalDate.now())) {
throw new ApiException(LeaveErrorCode.LEAVE_ALREADY_PASSED);
}

User user = leave.getUser();
UserLeave userLeave = userLeaveRepository.findByUserId(user.getId())
.orElseThrow(() -> new ApiException(LeaveErrorCode.USER_LEAVE_NOT_FOUND));
Expand Down Expand Up @@ -179,6 +183,22 @@ public void updateLeaveByAdmin(long leaveId, AdminUpdateLeaveRequest request) {
leave.update(startDate, request.endDate(), newType, newLeaveCount, request.reason());
}

@Transactional
public void cancelLeaveByUser(Long userId, long leaveId) {
Leave leave = leaveRepository.findById(leaveId)
.orElseThrow(() -> new ApiException(LeaveErrorCode.LEAVE_NOT_FOUND));

if (!leave.getUser().getId().equals(userId)) {
throw new ApiException(LeaveErrorCode.ACCESS_DENIED);
}

if (!leave.isPending()) {
throw new ApiException(LeaveErrorCode.LEAVE_CANNOT_CANCEL_NOT_PENDING);
}

leaveRepository.delete(leave);
}

public int countDatesByUserIdWithMonth(long userId, YearMonth yearMonth) {
return leaveRepository.findAllByUserId(userId)
.stream()
Expand All @@ -187,6 +207,23 @@ public int countDatesByUserIdWithMonth(long userId, YearMonth yearMonth) {
.sum();
}

@Transactional
public void deleteLeaveByAdmin(long leaveId) {
Leave leave = leaveRepository.findById(leaveId)
.orElseThrow(() -> new ApiException(LeaveErrorCode.LEAVE_NOT_FOUND));

// 승인된 휴가인 경우 휴가 일수 복원
if (leave.isApproved()) {
User user = leave.getUser();
UserLeave userLeave = userLeaveRepository.findByUserId(user.getId())
.orElseThrow(() -> new ApiException(LeaveErrorCode.USER_LEAVE_NOT_FOUND));

userLeave.restoreLeave(leave.getLeaveCount(), leave.isAnnualLeave());
}

leaveRepository.delete(leave);
}

private void validateLeaveTypeAccessPermission(User user, LeaveType type) {
if ((!user.isAdmin()) && type == LeaveType.ALL) {
throw new ApiException(LeaveErrorCode.ACCESS_DENIED);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ public record CreatePaperRequest(
ProfessorRole professorRole,
@Schema(description = "대표 실적 여부", example = "true")
boolean isRepresentative,
@Schema(description = "연계 과제 ID (선택적)", example = "1")
@Schema(description = "연계 과제 ID (선택)", example = "1")
Long taskId,
@Schema(description = "연계 프로젝트 ID (선택)", example = "1")
Long projectId,
@Schema(description = "첨부 파일 ID 목록", example = "[\"a1b2c3d4-e5f6-7890-1234-567890abcdef\"]")
List<UUID> fileIds
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ public record UpdatePaperRequest(
ProfessorRole professorRole,
@Schema(description = "대표 실적 여부", example = "false")
boolean isRepresentative,
@Schema(description = "연계 과제 ID (선택적)", example = "1")
@Schema(description = "연계 과제 ID (선택)", example = "1")
Long taskId,
@Schema(description = "연계 프로젝트 ID (선택)", example = "1")
Long projectId,
@Schema(description = "첨부 파일 ID 목록", example = "[\"a1b2c3d4-e5f6-7890-1234-567890abcdef\"]")
List<UUID> fileIds
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ public record PaperResponse(
boolean isRepresentative,
@Schema(description = "연계 과제 ID")
Long taskId,
@Schema(description = "연계 과제명")
String taskName,
@Schema(description = "연계 프로젝트 ID")
Long projectId,
@Schema(description = "연계 프로젝트명")
String projectName,
@Schema(description = "첨부 파일 목록")
List<FileSummary> files
) {
Expand All @@ -76,6 +82,9 @@ public PaperResponse(Paper paper, List<PaperCorrespondingAuthor> correspondingAu
paper.getProfessorRole().getDescription(),
paper.getIsRepresentative(),
paper.getTask() != null ? paper.getTask().getId() : null,
paper.getTask() != null ? paper.getTask().getTitle() : null,
paper.getProject() != null ? paper.getProject().getId() : null,
paper.getProject() != null ? paper.getProject().getTitle() : null,
files
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.bmilab.backend.domain.research.paper.entity;

import com.bmilab.backend.domain.project.entity.Project;
import com.bmilab.backend.domain.research.paper.enums.ProfessorRole;
import com.bmilab.backend.domain.task.entity.Task;
import com.bmilab.backend.global.entity.BaseTimeEntity;
Expand Down Expand Up @@ -73,8 +74,12 @@ public class Paper extends BaseTimeEntity {
@JoinColumn(name = "task_id")
private Task task;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "project_id")
private Project project;

@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, Task task) {
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, Project project) {
this.acceptDate = acceptDate;
this.publishDate = publishDate;
this.journal = journal;
Expand All @@ -92,9 +97,10 @@ public Paper(LocalDate acceptDate, LocalDate publishDate, Journal journal, Strin
this.professorRole = professorRole;
this.isRepresentative = isRepresentative;
this.task = task;
this.project = project;
}

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) {
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, Project project) {
this.acceptDate = acceptDate;
this.publishDate = publishDate;
this.journal = journal;
Expand All @@ -112,6 +118,7 @@ public void update(LocalDate acceptDate, LocalDate publishDate, Journal journal,
this.professorRole = professorRole;
this.isRepresentative = isRepresentative;
this.task = task;
this.project = project;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
import com.bmilab.backend.domain.file.enums.FileDomainType;
import com.bmilab.backend.domain.file.service.FileService;
import com.bmilab.backend.domain.project.entity.ExternalProfessor;
import com.bmilab.backend.domain.project.entity.Project;
import com.bmilab.backend.domain.project.exception.ProjectErrorCode;
import com.bmilab.backend.domain.project.repository.ExternalProfessorRepository;
import com.bmilab.backend.domain.project.repository.ProjectRepository;
import com.bmilab.backend.domain.research.paper.exception.PaperErrorCode;
import com.bmilab.backend.domain.research.paper.dto.request.CreatePaperRequest;
import com.bmilab.backend.domain.research.paper.dto.request.UpdatePaperRequest;
Expand Down Expand Up @@ -47,6 +50,7 @@ public class PaperService {
private final JournalRepository journalRepository;
private final ExternalProfessorRepository externalProfessorRepository;
private final TaskRepository taskRepository;
private final ProjectRepository projectRepository;
private final FileService fileService;
private final AuthorSyncService authorSyncService;

Expand All @@ -56,6 +60,9 @@ public PaperResponse createPaper(CreatePaperRequest dto) {
Task task = dto.taskId() != null
? taskRepository.findById(dto.taskId()).orElseThrow(() -> new ApiException(TaskErrorCode.TASK_NOT_FOUND))
: null;
Project project = dto.projectId() != null
? projectRepository.findById(dto.projectId()).orElseThrow(() -> new ApiException(ProjectErrorCode.PROJECT_NOT_FOUND))
: null;
int authorCount = (dto.allAuthors() != null) ? dto.allAuthors().split(",").length : 0;
Paper newPaper = Paper.builder()
.acceptDate(dto.acceptDate())
Expand All @@ -75,6 +82,7 @@ public PaperResponse createPaper(CreatePaperRequest dto) {
.professorRole(dto.professorRole())
.isRepresentative(dto.isRepresentative())
.task(task)
.project(project)
.build();
paperRepository.save(newPaper);

Expand Down Expand Up @@ -154,8 +162,11 @@ public PaperResponse updatePaper(Long paperId, UpdatePaperRequest dto) {
Task task = dto.taskId() != null
? taskRepository.findById(dto.taskId()).orElseThrow(() -> new ApiException(TaskErrorCode.TASK_NOT_FOUND))
: null;
Project project = dto.projectId() != null
? projectRepository.findById(dto.projectId()).orElseThrow(() -> new ApiException(ProjectErrorCode.PROJECT_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(), task);
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, project);

// Handle PaperCorrespondingAuthor linking
paperCorrespondingAuthorRepository.deleteAllByPaperId(paperId);
Expand Down
Loading
Loading