diff --git a/src/main/java/clap/server/adapter/inbound/web/dto/admin/response/MemberDetailsResponse.java b/src/main/java/clap/server/adapter/inbound/web/dto/admin/response/MemberDetailsResponse.java index 77cb3384..b5eb4d99 100644 --- a/src/main/java/clap/server/adapter/inbound/web/dto/admin/response/MemberDetailsResponse.java +++ b/src/main/java/clap/server/adapter/inbound/web/dto/admin/response/MemberDetailsResponse.java @@ -21,6 +21,8 @@ public record MemberDetailsResponse( @Schema(description = "부서") String departmentName, @Schema(description = "직무") - String departmentRole + String departmentRole, + @Schema(description = "잔여 작업, 등록이 되지 않은 회원은 Null로 출력됩니다.") + Integer remainingTasks ) {} \ No newline at end of file diff --git a/src/main/java/clap/server/adapter/inbound/web/dto/member/response/MemberDetailInfoResponse.java b/src/main/java/clap/server/adapter/inbound/web/dto/member/response/MemberDetailInfoResponse.java index 59a20a9c..6ec4754c 100644 --- a/src/main/java/clap/server/adapter/inbound/web/dto/member/response/MemberDetailInfoResponse.java +++ b/src/main/java/clap/server/adapter/inbound/web/dto/member/response/MemberDetailInfoResponse.java @@ -20,7 +20,9 @@ public record MemberDetailInfoResponse( @Schema(description = "직책") String departmentRole, @Schema(description = "알림 수신 여부") - NotificationSettingInfoResponse notificationSettingInfo + NotificationSettingInfoResponse notificationSettingInfo, + @Schema(description = "진행/검토 작업 수, 담당자가 아닐 경우에는 null입니다.") + MemberRemainingTaskCountsResponse remainingTaskCounts ) { public static record NotificationSettingInfoResponse( @Schema(description = "이메일 알림 수신 여부") @@ -29,6 +31,14 @@ public static record NotificationSettingInfoResponse( boolean kakaoWork ) { } + + public static record MemberRemainingTaskCountsResponse( + @Schema(description = "진행중 작업 수") + int totalInProgressTaskCount, + @Schema(description = "검토중 작업 수") + int totalInReviewingTaskCount + ) { + } } diff --git a/src/main/java/clap/server/application/mapper/response/MemberResponseMapper.java b/src/main/java/clap/server/application/mapper/response/MemberResponseMapper.java index ec21a431..5bdc8dd1 100644 --- a/src/main/java/clap/server/application/mapper/response/MemberResponseMapper.java +++ b/src/main/java/clap/server/application/mapper/response/MemberResponseMapper.java @@ -4,6 +4,7 @@ import clap.server.adapter.inbound.web.dto.admin.response.MemberDetailsResponse; import clap.server.adapter.inbound.web.dto.member.response.MemberDetailInfoResponse; import clap.server.adapter.inbound.web.dto.member.response.MemberProfileResponse; +import clap.server.adapter.outbound.persistense.entity.member.constant.MemberRole; import clap.server.domain.model.member.Member; import clap.server.domain.model.member.MemberInfo; @@ -32,7 +33,15 @@ public static MemberDetailInfoResponse toMemberDetailInfoResponse(Member member) member.getMemberInfo().getRole(), member.getMemberInfo().getDepartment().getName(), member.getMemberInfo().getDepartmentRole(), - toNotificationSettingInfoResponse(member) + toNotificationSettingInfoResponse(member), + member.getMemberInfo().getRole()!= MemberRole.ROLE_MANAGER ? null : toMemberRemainingTaskCountsResponse(member) + ); + } + + public static MemberDetailInfoResponse.MemberRemainingTaskCountsResponse toMemberRemainingTaskCountsResponse(Member member){ + return new MemberDetailInfoResponse.MemberRemainingTaskCountsResponse( + member.getInProgressTaskCount(), + member.getInReviewingTaskCount() ); } @@ -54,7 +63,8 @@ public static MemberDetailsResponse toMemberDetailsResponse(Member member) { member.getMemberInfo().getRole(), member.getMemberInfo().getDepartment().getDepartmentId(), member.getMemberInfo().getDepartment().getName(), - member.getMemberInfo().getDepartmentRole() + member.getMemberInfo().getDepartmentRole(), + member.getInProgressTaskCount() + member.getInReviewingTaskCount() ); } diff --git a/src/main/java/clap/server/application/service/admin/CsvParseService.java b/src/main/java/clap/server/application/service/admin/CsvParseService.java index ff3f8be7..710c7303 100644 --- a/src/main/java/clap/server/application/service/admin/CsvParseService.java +++ b/src/main/java/clap/server/application/service/admin/CsvParseService.java @@ -5,7 +5,7 @@ import clap.server.domain.model.member.Department; import clap.server.domain.model.member.Member; import clap.server.domain.model.member.MemberInfo; -import clap.server.domain.policy.member.ManagerDepartmentPolicy; +import clap.server.domain.policy.member.ManagerInfoUpdatePolicy; import clap.server.exception.ApplicationException; import clap.server.exception.code.DepartmentErrorCode; import clap.server.exception.code.MemberErrorCode; @@ -30,7 +30,7 @@ public class CsvParseService { private final LoadDepartmentPort loadDepartmentPort; - private final ManagerDepartmentPolicy managerDepartmentPolicy; + private final ManagerInfoUpdatePolicy managerInfoUpdatePolicy; public List parseDataAndMapToMember(MultipartFile file) { List members = new ArrayList<>(); @@ -67,7 +67,7 @@ private Member mapToMember(String[] fields, List departments) { .findFirst() .orElseThrow(() -> new ApplicationException(DepartmentErrorCode.DEPARTMENT_NOT_FOUND)); - managerDepartmentPolicy.validateDepartment(department, MemberRole.valueOf(fields[5].trim())); + managerInfoUpdatePolicy.validateDepartment(department, MemberRole.valueOf(fields[5].trim())); MemberInfo memberInfo = toMemberInfo( fields[0].trim(), // name fields[4].trim(), // email diff --git a/src/main/java/clap/server/application/service/admin/DeleteMemberService.java b/src/main/java/clap/server/application/service/admin/DeleteMemberService.java index 42095227..286b0412 100644 --- a/src/main/java/clap/server/application/service/admin/DeleteMemberService.java +++ b/src/main/java/clap/server/application/service/admin/DeleteMemberService.java @@ -1,10 +1,12 @@ package clap.server.application.service.admin; +import clap.server.adapter.outbound.persistense.entity.member.constant.MemberRole; import clap.server.application.port.inbound.admin.DeleteMemberUsecase; import clap.server.application.port.outbound.member.CommandMemberPort; import clap.server.application.port.outbound.member.LoadMemberPort; import clap.server.common.annotation.architecture.ApplicationService; import clap.server.domain.model.member.Member; +import clap.server.domain.policy.member.ManagerInfoUpdatePolicy; import clap.server.exception.ApplicationException; import clap.server.exception.code.MemberErrorCode; import lombok.RequiredArgsConstructor; @@ -16,6 +18,7 @@ public class DeleteMemberService implements DeleteMemberUsecase { private final LoadMemberPort loadMemberPort; private final CommandMemberPort commandMemberPort; + private final ManagerInfoUpdatePolicy managerInfoUpdatePolicy; @Transactional @Override @@ -23,10 +26,11 @@ public void deleteMember(Long memberId) { Member member = loadMemberPort.findById(memberId) .orElseThrow(() -> new ApplicationException(MemberErrorCode.MEMBER_NOT_FOUND)); + if(member.getMemberInfo().getRole().equals(MemberRole.ROLE_MANAGER)){ + managerInfoUpdatePolicy.validateNoRemainingTasks(member); + } Hibernate.initialize(member.getDepartment()); - - member.setStatusDeleted(); - + member.softDelete(); commandMemberPort.save(member); } } diff --git a/src/main/java/clap/server/application/service/admin/ManageMemberService.java b/src/main/java/clap/server/application/service/admin/ManageMemberService.java index 45be8dbe..da49a104 100644 --- a/src/main/java/clap/server/application/service/admin/ManageMemberService.java +++ b/src/main/java/clap/server/application/service/admin/ManageMemberService.java @@ -2,6 +2,7 @@ import clap.server.adapter.inbound.web.dto.admin.request.UpdateMemberRequest; import clap.server.adapter.inbound.web.dto.admin.response.MemberDetailsResponse; +import clap.server.adapter.outbound.persistense.entity.member.constant.MemberRole; import clap.server.application.mapper.response.MemberResponseMapper; import clap.server.application.port.inbound.admin.MemberDetailUsecase; import clap.server.application.port.inbound.admin.UpdateMemberUsecase; @@ -11,6 +12,7 @@ import clap.server.common.annotation.architecture.ApplicationService; import clap.server.domain.model.member.Department; import clap.server.domain.model.member.Member; +import clap.server.domain.policy.member.ManagerInfoUpdatePolicy; import clap.server.exception.ApplicationException; import clap.server.exception.code.DepartmentErrorCode; import lombok.RequiredArgsConstructor; @@ -22,6 +24,7 @@ class ManageMemberService implements UpdateMemberUsecase, MemberDetailUsecase { private final MemberService memberService; private final CommandMemberPort commandMemberPort; private final LoadDepartmentPort loadDepartmentPort; + private final ManagerInfoUpdatePolicy managerInfoUpdatePolicy; @Override @Transactional @@ -29,7 +32,13 @@ public void updateMemberInfo(Long adminId, Long memberId, UpdateMemberRequest re Member member = memberService.findById(memberId); Department department = loadDepartmentPort.findById(request.departmentId()).orElseThrow(() -> new ApplicationException(DepartmentErrorCode.DEPARTMENT_NOT_FOUND)); - + + managerInfoUpdatePolicy.validateDepartment(department, request.role()); + if(member.getMemberInfo().getRole().equals(MemberRole.ROLE_MANAGER) && + !request.role().equals(MemberRole.ROLE_MANAGER)){ + managerInfoUpdatePolicy.validateNoRemainingTasks(member); + } + member.getMemberInfo().updateMemberInfoByAdmin( request.name(), request.isReviewer(), department, request.role(), request.departmentRole()); diff --git a/src/main/java/clap/server/application/service/admin/RegisterMemberService.java b/src/main/java/clap/server/application/service/admin/RegisterMemberService.java index 4934f74d..7d392c57 100644 --- a/src/main/java/clap/server/application/service/admin/RegisterMemberService.java +++ b/src/main/java/clap/server/application/service/admin/RegisterMemberService.java @@ -10,7 +10,7 @@ import clap.server.domain.model.member.Department; import clap.server.domain.model.member.Member; import clap.server.domain.model.member.MemberInfo; -import clap.server.domain.policy.member.ManagerDepartmentPolicy; +import clap.server.domain.policy.member.ManagerInfoUpdatePolicy; import clap.server.exception.ApplicationException; import clap.server.exception.code.DepartmentErrorCode; import clap.server.exception.code.MemberErrorCode; @@ -26,7 +26,7 @@ class RegisterMemberService implements RegisterMemberUsecase { private final CommandMemberPort commandMemberPort; private final LoadDepartmentPort loadDepartmentPort; private final LoadMemberPort loadMemberPort; - private final ManagerDepartmentPolicy managerDepartmentPolicy; + private final ManagerInfoUpdatePolicy managerInfoUpdatePolicy; @Override @Transactional @@ -39,7 +39,7 @@ public void registerMember(Long adminId, RegisterMemberRequest request) { throw new ApplicationException(MemberErrorCode.DUPLICATE_NICKNAME_OR_EMAIL); } - managerDepartmentPolicy.validateDepartment(department, request.role()); + managerInfoUpdatePolicy.validateDepartment(department, request.role()); MemberInfo memberInfo = MemberInfo.toMemberInfo(request.name(), request.email(), request.nickname(), request.isReviewer(), department, request.role(), request.departmentRole()); Member member = Member.createMember(admin, memberInfo); diff --git a/src/main/java/clap/server/application/service/task/TerminateTaskService.java b/src/main/java/clap/server/application/service/task/TerminateTaskService.java index d0d3700d..c481f0cc 100644 --- a/src/main/java/clap/server/application/service/task/TerminateTaskService.java +++ b/src/main/java/clap/server/application/service/task/TerminateTaskService.java @@ -27,7 +27,10 @@ public class TerminateTaskService implements TerminateTaskUsecase { public void terminateTask(Long memberId, Long taskId, String reason) { Task task = taskService.findById(taskId); - updateProcessorTaskCountService.handleTaskStatusChange(task.getProcessor(), task.getTaskStatus(), TaskStatus.TERMINATED); + // 작업 종료의 경우. 작업 반려는 count를 업데이트를 하지 않음 + if(task.getProcessor()!=null) { + updateProcessorTaskCountService.handleTaskStatusChange(task.getProcessor(), task.getTaskStatus(), TaskStatus.TERMINATED); + } task.terminateTask(); taskService.upsert(task); diff --git a/src/main/java/clap/server/domain/model/member/Member.java b/src/main/java/clap/server/domain/model/member/Member.java index 237ba301..b9026112 100644 --- a/src/main/java/clap/server/domain/model/member/Member.java +++ b/src/main/java/clap/server/domain/model/member/Member.java @@ -2,7 +2,6 @@ import clap.server.adapter.outbound.persistense.entity.member.constant.MemberStatus; import clap.server.domain.model.common.BaseTime; -import jakarta.persistence.Column; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; @@ -87,7 +86,7 @@ public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; } - public void setStatusDeleted() { + public void softDelete() { this.status = MemberStatus.DELETED; } diff --git a/src/main/java/clap/server/domain/policy/member/ManagerDepartmentPolicy.java b/src/main/java/clap/server/domain/policy/member/ManagerInfoUpdatePolicy.java similarity index 53% rename from src/main/java/clap/server/domain/policy/member/ManagerDepartmentPolicy.java rename to src/main/java/clap/server/domain/policy/member/ManagerInfoUpdatePolicy.java index 4b92d3f1..271bed85 100644 --- a/src/main/java/clap/server/domain/policy/member/ManagerDepartmentPolicy.java +++ b/src/main/java/clap/server/domain/policy/member/ManagerInfoUpdatePolicy.java @@ -3,11 +3,14 @@ import clap.server.adapter.outbound.persistense.entity.member.constant.MemberRole; import clap.server.common.annotation.architecture.Policy; import clap.server.domain.model.member.Department; +import clap.server.domain.model.member.Member; import clap.server.exception.DomainException; import clap.server.exception.code.MemberErrorCode; @Policy -public class ManagerDepartmentPolicy { +public class ManagerInfoUpdatePolicy { + + // 담당자 권한이 있는 부서의 인원만 담당자의 역할이 허용됨 public void validateDepartment(final Department department, final MemberRole memberRole) { if (!department.isManager() ){ if(memberRole == MemberRole.ROLE_MANAGER){ @@ -15,4 +18,11 @@ public void validateDepartment(final Department department, final MemberRole mem } } } + + // 담당자의 잔여 작업이 남아있는 경우 해당 회원의 데이터 수정이 허용되지 않음 + public void validateNoRemainingTasks(final Member member){ + if(member.getInReviewingTaskCount()>0 || member.getInProgressTaskCount()> 0){ + throw new DomainException(MemberErrorCode.MANAGER_MEMBER_UPDATE_NOT_ALLOWED_WITH_TASKS); + } + } } diff --git a/src/main/java/clap/server/exception/code/MemberErrorCode.java b/src/main/java/clap/server/exception/code/MemberErrorCode.java index f5246b1a..a86b01e5 100644 --- a/src/main/java/clap/server/exception/code/MemberErrorCode.java +++ b/src/main/java/clap/server/exception/code/MemberErrorCode.java @@ -13,13 +13,14 @@ public enum MemberErrorCode implements BaseErrorCode { COMMENT_PERMISSION_DENIED(HttpStatus.FORBIDDEN, "MEMBER_005", "댓글 작성 권한이 없습니다."), PASSWORD_VERIFY_FAILED(HttpStatus.BAD_REQUEST, "MEMBER_006", "비밀번호 검증에 실패하였습니다"), INVALID_CSV_FORMAT(HttpStatus.BAD_REQUEST, "MEMBER_007", "CSV 파일 형식이 잘못되었습니다."), - CSV_PARSING_ERROR(HttpStatus.BAD_REQUEST, "MEMBER_008", "CSV 데이터 파싱 중 오류가 발생했습니다."), + CSV_PARSING_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "MEMBER_008", "CSV 데이터 파싱 중 오류가 발생했습니다."), REVIEW_PERMISSION_DENIED(HttpStatus.BAD_REQUEST, "MEMBER_009", "담당자만 리뷰 권한이 있습니다."), NAME_CANNOT_BE_EMPTY(HttpStatus.BAD_REQUEST, "MEMBER_010", "이름은 공백일 수 없습니다."), DUPLICATE_NICKNAME(HttpStatus.BAD_REQUEST,"MEMBER_011", "중복된 닉네임입니다"), DUPLICATE_NICKNAME_OR_EMAIL(HttpStatus.BAD_REQUEST, "MEMBER_012", "중복된 닉네임이나 email이 존재합니다"), MANAGER_PERMISSION_DENIED(HttpStatus.BAD_REQUEST, "MEMBER_013", "담당자 권한이 없는 부서입니다."), INVALID_EMAIL_NICKNAME_MATCH(HttpStatus.BAD_REQUEST, "MEMBER_014", "닉네임과 이메일이 일치하지 않습니다"), + MANAGER_MEMBER_UPDATE_NOT_ALLOWED_WITH_TASKS(HttpStatus.BAD_REQUEST, "MEMBER_015", "잔여 작업이 남아있어 수정이 불가합니다."), ; private final HttpStatus httpStatus;