diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/common/dto/WorkerSummaryDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/common/dto/WorkerSummaryDto.java index 750ff327..dfcae9b5 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/common/dto/WorkerSummaryDto.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/common/dto/WorkerSummaryDto.java @@ -11,21 +11,25 @@ @Builder(access = AccessLevel.PRIVATE) @Schema(description = "근무자 요약 정보") public class WorkerSummaryDto { - + @Schema(description = "근무자 ID", example = "1") private Long workerId; - + @Schema(description = "근무자명", example = "김알바") private String workerName; + @Schema(description = "근무자 표시 색상 (hex)", example = "#9CA3AF") + private String colorCode; + public static WorkerSummaryDto of(WorkspaceWorker workspaceWorker) { if (ObjectUtils.isEmpty(workspaceWorker)) { return null; } - + return WorkerSummaryDto.builder() .workerId(workspaceWorker.getId()) .workerName(workspaceWorker.getUser().getName()) + .colorCode(workspaceWorker.getColorCode()) .build(); } } diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/controller/ManagerWorkspaceController.java b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/controller/ManagerWorkspaceController.java index 56234cfa..d9aa69ea 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/controller/ManagerWorkspaceController.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/controller/ManagerWorkspaceController.java @@ -21,13 +21,16 @@ import com.dreamteam.alter.adapter.inbound.manager.workspace.dto.ManagerWorkspaceWorkerListFilterDto; import com.dreamteam.alter.adapter.inbound.manager.workspace.dto.ManagerWorkspaceWorkerListResponseDto; import com.dreamteam.alter.adapter.inbound.manager.workspace.dto.UpdateFixedScheduleDateRequestDto; +import com.dreamteam.alter.adapter.inbound.manager.workspace.dto.UpdateWorkspaceWorkerColorRequestDto; import com.dreamteam.alter.application.aop.ManagerActionContext; import com.dreamteam.alter.domain.user.context.ManagerActor; +import com.dreamteam.alter.domain.workspace.command.UpdateWorkspaceWorkerColorCommand; import com.dreamteam.alter.domain.workspace.port.inbound.ManagerGetWorkspaceListUseCase; import com.dreamteam.alter.domain.workspace.port.inbound.ManagerGetWorkspaceManagerListUseCase; import com.dreamteam.alter.domain.workspace.port.inbound.ManagerGetWorkspaceUseCase; import com.dreamteam.alter.domain.workspace.port.inbound.ManagerGetWorkspaceWorkerListUseCase; import com.dreamteam.alter.domain.workspace.port.inbound.ManagerUpdateFixedScheduleDateUseCase; +import com.dreamteam.alter.domain.workspace.port.inbound.ManagerUpdateWorkspaceWorkerColorCodeUseCase; import jakarta.annotation.Resource; import jakarta.validation.Valid; @@ -55,6 +58,9 @@ public class ManagerWorkspaceController implements ManagerWorkspaceControllerSpe @Resource(name = "managerUpdateFixedScheduleDate") private final ManagerUpdateFixedScheduleDateUseCase managerUpdateFixedScheduleDate; + @Resource(name = "managerUpdateWorkspaceWorkerColorCode") + private final ManagerUpdateWorkspaceWorkerColorCodeUseCase managerUpdateWorkspaceWorkerColor; + @Override @GetMapping public ResponseEntity>> getWorkspaceList() { @@ -106,4 +112,21 @@ public ResponseEntity> updateFixedScheduleDate( managerUpdateFixedScheduleDate.execute(actor, workspaceId, request); return ResponseEntity.ok(CommonApiResponse.empty()); } + + @Override + @PatchMapping("/{workspaceId}/workers/{workerId}/color") + public ResponseEntity> updateWorkspaceWorkerColorCode( + @PathVariable Long workspaceId, + @PathVariable Long workerId, + @RequestBody @Valid UpdateWorkspaceWorkerColorRequestDto request + ) { + ManagerActor actor = ManagerActionContext.getInstance().getActor(); + managerUpdateWorkspaceWorkerColor.execute( + actor, + workspaceId, + workerId, + UpdateWorkspaceWorkerColorCommand.of(request.getColorCode()) + ); + return ResponseEntity.ok(CommonApiResponse.empty()); + } } diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/controller/ManagerWorkspaceControllerSpec.java b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/controller/ManagerWorkspaceControllerSpec.java index 8aa504c4..eee42223 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/controller/ManagerWorkspaceControllerSpec.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/controller/ManagerWorkspaceControllerSpec.java @@ -86,6 +86,50 @@ ResponseEntity> updateFixedScheduleDate( @RequestBody @Valid UpdateFixedScheduleDateRequestDto request ); + @Operation(summary = "매니저 - 근무자 색상 변경", description = "근무자 캘린더에서 사용할 표시 색상을 변경합니다. 같은 업장의 ACTIVATED 근무자끼리 색상은 unique 합니다 (기본 색상 #9CA3AF 제외).") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "색상 변경 성공"), + @ApiResponse(responseCode = "400", description = "잘못된 색상 형식", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = { + @ExampleObject( + name = "잘못된 요청", + value = "{\"code\" : \"B001\"}" + ), + })), + @ApiResponse(responseCode = "404", description = "존재하지 않는 업장 또는 근무자", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = { + @ExampleObject( + name = "존재하지 않는 업장입니다.", + value = "{\"code\" : \"B008\"}" + ), + @ExampleObject( + name = "근무자를 찾을 수 없습니다.", + value = "{\"code\" : \"B019\"}" + ), + })), + @ApiResponse(responseCode = "409", description = "이미 사용 중인 색상", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = { + @ExampleObject( + name = "이미 사용 중인 근무자 색상입니다.", + value = "{\"code\" : \"B020\"}" + ), + })), + }) + ResponseEntity> updateWorkspaceWorkerColorCode( + @PathVariable Long workspaceId, + @PathVariable Long workerId, + @RequestBody @Valid UpdateWorkspaceWorkerColorRequestDto request + ); + // 업장 근무자 상세 정보 조회 // 업장 근무자 상태 변경 diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/dto/ManagerWorkspaceWorkerListResponseDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/dto/ManagerWorkspaceWorkerListResponseDto.java index bc32c277..290f90c9 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/dto/ManagerWorkspaceWorkerListResponseDto.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/dto/ManagerWorkspaceWorkerListResponseDto.java @@ -34,6 +34,10 @@ public class ManagerWorkspaceWorkerListResponseDto { @Schema(description = "직책") private WorkerPositionResponseDto position; + @NotNull + @Schema(description = "근무자 표시 색상 (hex)", example = "#9CA3AF") + private String colorCode; + @NotNull @Schema(description = "채용일자", example = "2023-10-01T12:00:00") private LocalDate employedAt; @@ -50,6 +54,7 @@ public static ManagerWorkspaceWorkerListResponseDto of(ManagerWorkspaceWorkerLis .user(WorkspaceWorkerResponseDto.of(entity.getUser())) .status(DescribedEnumDto.of(entity.getStatus(), WorkspaceWorkerStatus.describe())) .position(WorkerPositionResponseDto.from(entity.getPosition())) + .colorCode(entity.getColorCode()) .employedAt(entity.getEmployedAt()) .resignedAt(entity.getResignedAt()) .nextShiftDateTime(entity.getNextShiftDateTime()) diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/dto/UpdateWorkspaceWorkerColorRequestDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/dto/UpdateWorkspaceWorkerColorRequestDto.java new file mode 100644 index 00000000..cff7059c --- /dev/null +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/dto/UpdateWorkspaceWorkerColorRequestDto.java @@ -0,0 +1,20 @@ +package com.dreamteam.alter.adapter.inbound.manager.workspace.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "매니저 - 근무자 색상 변경 요청 DTO") +public class UpdateWorkspaceWorkerColorRequestDto { + + @NotBlank + @Pattern(regexp = "^#[0-9A-Fa-f]{6}$", message = "색상은 #로 시작하는 6자리 16진수여야 합니다.") + @Schema(description = "변경할 색상 (hex)", example = "#93B0A4") + private String colorCode; +} diff --git a/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/WorkspaceQueryRepositoryImpl.java b/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/WorkspaceQueryRepositoryImpl.java index 21f9644e..7063653e 100644 --- a/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/WorkspaceQueryRepositoryImpl.java +++ b/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/WorkspaceQueryRepositoryImpl.java @@ -173,6 +173,7 @@ public List getWorkspaceWorkerListWithCursor ), qWorkspaceWorker.status, Expressions.constant(WorkerPositionType.WORKER), + qWorkspaceWorker.colorCode, qWorkspaceWorker.employedAt, qWorkspaceWorker.resignedAt, qWorkspaceWorker.createdAt, @@ -205,6 +206,7 @@ public List getWorkspaceWorkerListWithCursor qUser.contact, qUser.gender, qWorkspaceWorker.status, + qWorkspaceWorker.colorCode, qWorkspaceWorker.employedAt, qWorkspaceWorker.resignedAt, qWorkspaceWorker.createdAt diff --git a/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/WorkspaceWorkerQueryRepositoryImpl.java b/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/WorkspaceWorkerQueryRepositoryImpl.java index e7fd6cb4..8beff8ae 100644 --- a/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/WorkspaceWorkerQueryRepositoryImpl.java +++ b/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/WorkspaceWorkerQueryRepositoryImpl.java @@ -131,8 +131,30 @@ public List getUserActiveWorkspaceListWithCursor( } + @Override + public boolean existsActivatedByWorkspaceAndColorCode(Long workspaceId, String colorCode, Long excludeWorkerId) { + QWorkspaceWorker qWorkspaceWorker = QWorkspaceWorker.workspaceWorker; + + BooleanExpression excludeCondition = ObjectUtils.isNotEmpty(excludeWorkerId) + ? qWorkspaceWorker.id.ne(excludeWorkerId) + : null; + + Integer result = queryFactory + .selectOne() + .from(qWorkspaceWorker) + .where( + qWorkspaceWorker.workspace.id.eq(workspaceId), + qWorkspaceWorker.colorCode.eq(colorCode), + qWorkspaceWorker.status.eq(WorkspaceWorkerStatus.ACTIVATED), + excludeCondition + ) + .fetchFirst(); + + return result != null; + } + private BooleanExpression cursorConditions( - QWorkspaceWorker qWorkspaceWorker, + QWorkspaceWorker qWorkspaceWorker, CursorDto cursor ) { if (ObjectUtils.isEmpty(cursor)) { diff --git a/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/readonly/ManagerWorkspaceWorkerListResponse.java b/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/readonly/ManagerWorkspaceWorkerListResponse.java index aa303712..75abfe67 100644 --- a/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/readonly/ManagerWorkspaceWorkerListResponse.java +++ b/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/readonly/ManagerWorkspaceWorkerListResponse.java @@ -26,6 +26,8 @@ public class ManagerWorkspaceWorkerListResponse { @Enumerated(EnumType.STRING) private WorkerPositionType position; + private String colorCode; + private LocalDate employedAt; private LocalDate resignedAt; diff --git a/src/main/java/com/dreamteam/alter/application/workspace/usecase/ManagerUpdateWorkspaceWorkerColorCode.java b/src/main/java/com/dreamteam/alter/application/workspace/usecase/ManagerUpdateWorkspaceWorkerColorCode.java new file mode 100644 index 00000000..0c1fa4dd --- /dev/null +++ b/src/main/java/com/dreamteam/alter/application/workspace/usecase/ManagerUpdateWorkspaceWorkerColorCode.java @@ -0,0 +1,52 @@ +package com.dreamteam.alter.application.workspace.usecase; + +import com.dreamteam.alter.common.exception.CustomException; +import com.dreamteam.alter.common.exception.ErrorCode; +import com.dreamteam.alter.domain.user.context.ManagerActor; +import com.dreamteam.alter.domain.workspace.command.UpdateWorkspaceWorkerColorCommand; +import com.dreamteam.alter.domain.workspace.entity.WorkspaceWorker; +import com.dreamteam.alter.domain.workspace.port.inbound.ManagerUpdateWorkspaceWorkerColorCodeUseCase; +import com.dreamteam.alter.domain.workspace.port.outbound.WorkspaceQueryRepository; +import com.dreamteam.alter.domain.workspace.port.outbound.WorkspaceWorkerQueryRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service("managerUpdateWorkspaceWorkerColorCode") +@RequiredArgsConstructor +@Transactional +public class ManagerUpdateWorkspaceWorkerColorCode implements ManagerUpdateWorkspaceWorkerColorCodeUseCase { + + private final WorkspaceQueryRepository workspaceQueryRepository; + private final WorkspaceWorkerQueryRepository workspaceWorkerQueryRepository; + + @Override + public void execute( + ManagerActor actor, + Long workspaceId, + Long workerId, + UpdateWorkspaceWorkerColorCommand command + ) { + if (!workspaceQueryRepository.existsByIdAndManagerUser(workspaceId, actor.getManagerUser())) { + throw new CustomException(ErrorCode.WORKSPACE_NOT_FOUND); + } + + WorkspaceWorker worker = workspaceWorkerQueryRepository.findById(workerId) + .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND, "근무자를 찾을 수 없습니다")); + + if (!worker.getWorkspace().getId().equals(workspaceId)) { + throw new CustomException(ErrorCode.WORKSPACE_NOT_FOUND); + } + + if (!WorkspaceWorker.DEFAULT_COLOR_CODE.equals(command.getColorCode()) + && workspaceWorkerQueryRepository.existsActivatedByWorkspaceAndColorCode( + workspaceId, + command.getColorCode(), + workerId + )) { + throw new CustomException(ErrorCode.CONFLICT, "이미 사용 중인 근무자 색상입니다."); + } + + worker.updateColorCode(command.getColorCode()); + } +} diff --git a/src/main/java/com/dreamteam/alter/domain/workspace/command/UpdateWorkspaceWorkerColorCommand.java b/src/main/java/com/dreamteam/alter/domain/workspace/command/UpdateWorkspaceWorkerColorCommand.java new file mode 100644 index 00000000..63817416 --- /dev/null +++ b/src/main/java/com/dreamteam/alter/domain/workspace/command/UpdateWorkspaceWorkerColorCommand.java @@ -0,0 +1,20 @@ +package com.dreamteam.alter.domain.workspace.command; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class UpdateWorkspaceWorkerColorCommand { + + private String colorCode; + + public static UpdateWorkspaceWorkerColorCommand of(String colorCode) { + return UpdateWorkspaceWorkerColorCommand.builder() + .colorCode(colorCode) + .build(); + } +} diff --git a/src/main/java/com/dreamteam/alter/domain/workspace/entity/WorkspaceWorker.java b/src/main/java/com/dreamteam/alter/domain/workspace/entity/WorkspaceWorker.java index 1c264fc0..f0004377 100644 --- a/src/main/java/com/dreamteam/alter/domain/workspace/entity/WorkspaceWorker.java +++ b/src/main/java/com/dreamteam/alter/domain/workspace/entity/WorkspaceWorker.java @@ -20,6 +20,8 @@ @EntityListeners(AuditingEntityListener.class) public class WorkspaceWorker { + public static final String DEFAULT_COLOR_CODE = "#9CA3AF"; + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -36,6 +38,10 @@ public class WorkspaceWorker { @Column(name = "status", nullable = false) private WorkspaceWorkerStatus status; + @Builder.Default + @Column(name = "color_code", nullable = false, length = 7) + private String colorCode = DEFAULT_COLOR_CODE; + @Column(name = "employed_at", nullable = false) private LocalDate employedAt; @@ -67,4 +73,8 @@ public void resign() { this.resignedAt = LocalDate.now(); } + public void updateColorCode(String colorCode) { + this.colorCode = colorCode; + } + } diff --git a/src/main/java/com/dreamteam/alter/domain/workspace/port/inbound/ManagerUpdateWorkspaceWorkerColorCodeUseCase.java b/src/main/java/com/dreamteam/alter/domain/workspace/port/inbound/ManagerUpdateWorkspaceWorkerColorCodeUseCase.java new file mode 100644 index 00000000..07c65fc1 --- /dev/null +++ b/src/main/java/com/dreamteam/alter/domain/workspace/port/inbound/ManagerUpdateWorkspaceWorkerColorCodeUseCase.java @@ -0,0 +1,8 @@ +package com.dreamteam.alter.domain.workspace.port.inbound; + +import com.dreamteam.alter.domain.user.context.ManagerActor; +import com.dreamteam.alter.domain.workspace.command.UpdateWorkspaceWorkerColorCommand; + +public interface ManagerUpdateWorkspaceWorkerColorCodeUseCase { + void execute(ManagerActor actor, Long workspaceId, Long workerId, UpdateWorkspaceWorkerColorCommand command); +} diff --git a/src/main/java/com/dreamteam/alter/domain/workspace/port/outbound/WorkspaceWorkerQueryRepository.java b/src/main/java/com/dreamteam/alter/domain/workspace/port/outbound/WorkspaceWorkerQueryRepository.java index 5bd94ecc..c6bdc9c4 100644 --- a/src/main/java/com/dreamteam/alter/domain/workspace/port/outbound/WorkspaceWorkerQueryRepository.java +++ b/src/main/java/com/dreamteam/alter/domain/workspace/port/outbound/WorkspaceWorkerQueryRepository.java @@ -15,11 +15,13 @@ public interface WorkspaceWorkerQueryRepository { Optional findById(Long id); List findAllById(List ids); List findAllActiveByUserId(Long userId); - + long getUserActiveWorkspaceCount(User user); - + List getUserActiveWorkspaceListWithCursor( - CursorPageRequest request, + CursorPageRequest request, User user ); + + boolean existsActivatedByWorkspaceAndColorCode(Long workspaceId, String colorCode, Long excludeWorkerId); }