diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/admin/workspace/controller/AdminBusinessTypeController.java b/src/main/java/com/dreamteam/alter/adapter/inbound/admin/workspace/controller/AdminBusinessTypeController.java new file mode 100644 index 00000000..ece78169 --- /dev/null +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/admin/workspace/controller/AdminBusinessTypeController.java @@ -0,0 +1,94 @@ +package com.dreamteam.alter.adapter.inbound.admin.workspace.controller; + +import java.util.List; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.dreamteam.alter.adapter.inbound.admin.workspace.dto.AdminBusinessTypeRequestDto; +import com.dreamteam.alter.adapter.inbound.admin.workspace.dto.AdminBusinessTypeResponseDto; +import com.dreamteam.alter.adapter.inbound.common.dto.CommonApiResponse; +import com.dreamteam.alter.domain.workspace.command.AdminCreateBusinessTypeCommand; +import com.dreamteam.alter.domain.workspace.command.AdminUpdateBusinessTypeCommand; +import com.dreamteam.alter.domain.workspace.port.inbound.AdminCreateBusinessTypeUseCase; +import com.dreamteam.alter.domain.workspace.port.inbound.AdminDeleteBusinessTypeUseCase; +import com.dreamteam.alter.domain.workspace.port.inbound.AdminGetBusinessTypeListUseCase; +import com.dreamteam.alter.domain.workspace.port.inbound.AdminUpdateBusinessTypeUseCase; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/admin/business-types") +@PreAuthorize("hasAnyRole('ADMIN')") +@RequiredArgsConstructor +@Validated +public class AdminBusinessTypeController implements AdminBusinessTypeControllerSpec { + + @Resource(name = "adminGetBusinessTypeList") + private final AdminGetBusinessTypeListUseCase adminGetBusinessTypeList; + + @Resource(name = "adminCreateBusinessType") + private final AdminCreateBusinessTypeUseCase adminCreateBusinessType; + + @Resource(name = "adminUpdateBusinessType") + private final AdminUpdateBusinessTypeUseCase adminUpdateBusinessType; + + @Resource(name = "adminDeleteBusinessType") + private final AdminDeleteBusinessTypeUseCase adminDeleteBusinessType; + + @Override + @GetMapping + public ResponseEntity>> getBusinessTypeList() { + List result = adminGetBusinessTypeList.execute().stream() + .map(AdminBusinessTypeResponseDto::from) + .toList(); + return ResponseEntity.ok(CommonApiResponse.of(result)); + } + + @Override + @PostMapping + public ResponseEntity> createBusinessType( + @Valid @RequestBody AdminBusinessTypeRequestDto request + ) { + Long id = adminCreateBusinessType.execute( + new AdminCreateBusinessTypeCommand(request.getName(), request.getDescription()) + ); + return ResponseEntity.status(HttpStatus.CREATED) + .body(CommonApiResponse.of( + AdminBusinessTypeResponseDto.of(id, request.getName(), request.getDescription()) + )); + } + + @Override + @PutMapping("/{id}") + public ResponseEntity> updateBusinessType( + @PathVariable Long id, + @Valid @RequestBody AdminBusinessTypeRequestDto request + ) { + adminUpdateBusinessType.execute( + id, new AdminUpdateBusinessTypeCommand(request.getName(), request.getDescription()) + ); + return ResponseEntity.ok(CommonApiResponse.empty()); + } + + @Override + @DeleteMapping("/{id}") + public ResponseEntity> deleteBusinessType( + @PathVariable Long id + ) { + adminDeleteBusinessType.execute(id); + return ResponseEntity.ok(CommonApiResponse.empty()); + } +} diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/admin/workspace/controller/AdminBusinessTypeControllerSpec.java b/src/main/java/com/dreamteam/alter/adapter/inbound/admin/workspace/controller/AdminBusinessTypeControllerSpec.java new file mode 100644 index 00000000..1364dcbc --- /dev/null +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/admin/workspace/controller/AdminBusinessTypeControllerSpec.java @@ -0,0 +1,57 @@ +package com.dreamteam.alter.adapter.inbound.admin.workspace.controller; + +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; + +import com.dreamteam.alter.adapter.inbound.admin.workspace.dto.AdminBusinessTypeRequestDto; +import com.dreamteam.alter.adapter.inbound.admin.workspace.dto.AdminBusinessTypeResponseDto; +import com.dreamteam.alter.adapter.inbound.common.dto.CommonApiResponse; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; + +@Tag(name = "ADMIN - 업종(BusinessType) 관리 API") +public interface AdminBusinessTypeControllerSpec { + + @Operation(summary = "업종 목록 조회", description = "관리자가 등록된 업종 전체 목록을 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "업종 목록 조회 성공") + }) + ResponseEntity>> getBusinessTypeList(); + + @Operation(summary = "업종 생성", description = "관리자가 새 업종을 등록합니다. 이름은 중복될 수 없습니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "업종 생성 성공"), + @ApiResponse(responseCode = "409", description = "이미 존재하는 업종 (CONFLICT)") + }) + ResponseEntity> createBusinessType( + @Valid @RequestBody AdminBusinessTypeRequestDto request + ); + + @Operation(summary = "업종 수정", description = "관리자가 업종의 이름/설명을 수정합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "업종 수정 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 업종 (NOT_FOUND)"), + @ApiResponse(responseCode = "409", description = "이미 존재하는 업종 (CONFLICT)") + }) + ResponseEntity> updateBusinessType( + @PathVariable Long id, + @Valid @RequestBody AdminBusinessTypeRequestDto request + ); + + @Operation(summary = "업종 삭제", description = "관리자가 업종을 삭제합니다. '기타' 업종 또는 업장/신청에서 사용 중인 업종은 삭제할 수 없습니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "업종 삭제 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 업종 (NOT_FOUND)"), + @ApiResponse(responseCode = "409", description = "'기타' 업종 또는 사용 중인 업종 (CONFLICT)") + }) + ResponseEntity> deleteBusinessType( + @PathVariable Long id + ); +} diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/admin/workspace/dto/AdminBusinessTypeRequestDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/admin/workspace/dto/AdminBusinessTypeRequestDto.java new file mode 100644 index 00000000..f08d32dd --- /dev/null +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/admin/workspace/dto/AdminBusinessTypeRequestDto.java @@ -0,0 +1,24 @@ +package com.dreamteam.alter.adapter.inbound.admin.workspace.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "업종(BusinessType) 생성/수정 요청 DTO") +public class AdminBusinessTypeRequestDto { + + @NotBlank + @Size(max = 64) + @Schema(description = "업종명", example = "카페") + private String name; + + @Size(max = 255) + @Schema(description = "업종 설명", example = "카페/디저트 매장") + private String description; +} diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/admin/workspace/dto/AdminBusinessTypeResponseDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/admin/workspace/dto/AdminBusinessTypeResponseDto.java new file mode 100644 index 00000000..766d84fb --- /dev/null +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/admin/workspace/dto/AdminBusinessTypeResponseDto.java @@ -0,0 +1,49 @@ +package com.dreamteam.alter.adapter.inbound.admin.workspace.dto; + +import com.dreamteam.alter.domain.workspace.entity.BusinessType; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder(access = AccessLevel.PRIVATE) +@Schema(description = "업종(BusinessType) 응답 DTO") +public class AdminBusinessTypeResponseDto { + + @Schema(description = "업종 ID", example = "1") + private Long id; + + @Schema(description = "업종명", example = "카페") + private String name; + + @Schema(description = "업종 설명", example = "카페/디저트 매장") + private String description; + + @Schema(description = "상세 입력 요구 여부 ('기타' 업종이면 true, 삭제 불가)", example = "false") + private boolean requiresDetail; + + public static AdminBusinessTypeResponseDto from(BusinessType businessType) { + return AdminBusinessTypeResponseDto.builder() + .id(businessType.getId()) + .name(businessType.getName()) + .description(businessType.getDescription()) + .requiresDetail(businessType.isRequiresDetail()) + .build(); + } + + // 관리자 생성 업종은 항상 requiresDetail=false 이므로 재조회 없이 생성 결과를 구성한다. + public static AdminBusinessTypeResponseDto of(Long id, String name, String description) { + return AdminBusinessTypeResponseDto.builder() + .id(id) + .name(name) + .description(description) + .requiresDetail(false) + .build(); + } +} diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/admin/workspace/dto/AdminWorkspaceRequestResponseDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/admin/workspace/dto/AdminWorkspaceRequestResponseDto.java index 2accd27d..3e4a275a 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/admin/workspace/dto/AdminWorkspaceRequestResponseDto.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/admin/workspace/dto/AdminWorkspaceRequestResponseDto.java @@ -33,6 +33,9 @@ public class AdminWorkspaceRequestResponseDto { @Schema(description = "업종", example = "카페") private String businessType; + @Schema(description = "업종 상세 (업종이 '기타'인 경우)", example = "떡볶이 전문점") + private String businessTypeDetail; + @Schema(description = "연락처", example = "01012345678") private String contact; @@ -74,6 +77,7 @@ public static AdminWorkspaceRequestResponseDto from( .businessRegistrationNo(entity.getBusinessRegistrationNo()) .businessName(entity.getBusinessName()) .businessType(entity.getBusinessType()) + .businessTypeDetail(entity.getBusinessTypeDetail()) .contact(entity.getContact()) .status(DescribedEnumDto.of(entity.getStatus(), WorkspaceRequestStatus.describe())) .fullAddress(entity.getFullAddress()) diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/common/dto/BusinessTypeResponseDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/common/dto/BusinessTypeResponseDto.java new file mode 100644 index 00000000..c73ef69d --- /dev/null +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/common/dto/BusinessTypeResponseDto.java @@ -0,0 +1,41 @@ +package com.dreamteam.alter.adapter.inbound.common.dto; + +import com.dreamteam.alter.domain.workspace.entity.BusinessType; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Schema(title = "업종(BusinessType) 응답 DTO") +public class BusinessTypeResponseDto { + + @NotNull + @Schema(title = "업종 ID", example = "1") + private Long id; + + @NotBlank + @Schema(title = "업종 이름", example = "카페") + private String name; + + @Schema(title = "상세 입력 요구 여부 ('기타' 업종이면 true)", example = "false") + private boolean requiresDetail; + + @Schema(title = "업종 설명", example = "마스터에 없는 업종") + private String description; + + public static BusinessTypeResponseDto from(BusinessType businessType) { + return new BusinessTypeResponseDto( + businessType.getId(), + businessType.getName(), + businessType.isRequiresDetail(), + businessType.getDescription() + ); + } +} diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/CreatePostingRequestDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/CreatePostingRequestDto.java index e546c2a0..0fe9f6a8 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/CreatePostingRequestDto.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/CreatePostingRequestDto.java @@ -4,7 +4,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.Positive; import lombok.*; @@ -39,10 +38,6 @@ public class CreatePostingRequestDto { @NotNull private PaymentType paymentType; - @Schema(description = "키워드", example = "[2, 3, 1]") - @NotEmpty - private List keywords; - @Schema(description = "공고 스케줄", example = "[" + "{" + "\"workingDays\": [\"MONDAY\", \"WEDNESDAY\"], \"startTime\": \"09:00\", \"endTime\": \"18:00\", \"positionsNeeded\": 3, \"position\": \"설거지\"" + diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/ManagerPostingListWorkspaceResponseDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/ManagerPostingListWorkspaceResponseDto.java index be93bbba..45635b01 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/ManagerPostingListWorkspaceResponseDto.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/ManagerPostingListWorkspaceResponseDto.java @@ -21,10 +21,19 @@ public class ManagerPostingListWorkspaceResponseDto { @Schema(description = "업장 이름", example = "카페 알터") private String businessName; + @NotBlank + @Schema(description = "업종", example = "카페") + private String businessType; + + @Schema(description = "업종 상세 (업종이 '기타'인 경우)", example = "떡볶이 전문점") + private String businessTypeDetail; + public static ManagerPostingListWorkspaceResponseDto from(Workspace entity) { return ManagerPostingListWorkspaceResponseDto.builder() .id(entity.getId()) .businessName(entity.getBusinessName()) + .businessType(entity.getBusinessType().getName()) + .businessTypeDetail(entity.getBusinessTypeDetail()) .build(); } diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingDetailResponseDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingDetailResponseDto.java index b4f5863e..bfc26338 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingDetailResponseDto.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingDetailResponseDto.java @@ -47,10 +47,6 @@ public class PostingDetailResponseDto { @Schema(description = "생성일", example = "2023-10-01T12:00:00") private LocalDateTime createdAt; - @NotNull - @Schema(description = "키워드", example = "[{\"id\":1,\"name\":\"카페\"},{\"id\":4,\"name\":\"분식\"}]") - private List keywords; - @NotNull @Schema(description = "공고 스케줄", example = "[{\"id\":1,\"workingDays\":[\"MONDAY\",\"WEDNESDAY\"],\"startTime\":\"09:00\",\"endTime\":\"18:00\",\"positionsNeeded\":3,\"positionsAvailable\":2,\"position\":\"홀서빙\"}]") private List schedules; @@ -74,9 +70,6 @@ public static PostingDetailResponseDto from( .schedules(entity.getSchedules().stream() .map(PostingScheduleResponseDto::from) .toList()) - .keywords(entity.getPostingKeywords().stream() - .map(PostingKeywordListResponseDto::from) - .toList()) .scrapped(entity.isScrapped()) .build(); } diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingFilterOptionsResponseDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingFilterOptionsResponseDto.java index 05f61292..b8b66885 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingFilterOptionsResponseDto.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingFilterOptionsResponseDto.java @@ -1,7 +1,9 @@ package com.dreamteam.alter.adapter.inbound.general.posting.dto; +import com.dreamteam.alter.adapter.inbound.common.dto.BusinessTypeResponseDto; import com.dreamteam.alter.adapter.inbound.common.dto.DescribedEnumDto; import com.dreamteam.alter.domain.posting.type.PostingSortType; +import com.dreamteam.alter.domain.workspace.entity.BusinessType; import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; @@ -26,11 +28,15 @@ public class PostingFilterOptionsResponseDto { @Schema(description = "정렬 옵션 목록") private List> sortOptions; + @Schema(description = "업종 목록") + private List businessTypes; + public static PostingFilterOptionsResponseDto of( List provinces, List districts, List towns, - List sortOptions + List sortOptions, + List businessTypes ) { return PostingFilterOptionsResponseDto.builder() .provinces(provinces) @@ -40,6 +46,10 @@ public static PostingFilterOptionsResponseDto of( .map(sortType -> DescribedEnumDto.of(sortType, PostingSortType.describe())) .toList() ) + .businessTypes(businessTypes.stream() + .map(BusinessTypeResponseDto::from) + .toList() + ) .build(); } diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingKeywordListResponseDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingKeywordListResponseDto.java deleted file mode 100644 index 53c24126..00000000 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingKeywordListResponseDto.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.dreamteam.alter.adapter.inbound.general.posting.dto; - -import com.dreamteam.alter.domain.posting.entity.PostingKeyword; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor(access = AccessLevel.PRIVATE) -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@Schema(title = "키워드 리스트 응답 DTO") -public class PostingKeywordListResponseDto { - - @NotNull - @Schema(title = "키워드 ID", example = "1") - private Long id; - - @NotBlank - @Schema(title = "키워드 이름", example = "홀서빙") - private String name; - - public static PostingKeywordListResponseDto from(PostingKeyword postingKeyword) { - return new PostingKeywordListResponseDto( - postingKeyword.getId(), - postingKeyword.getName() - ); - } - -} diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingListFilterDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingListFilterDto.java index 3f832039..49783985 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingListFilterDto.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingListFilterDto.java @@ -6,6 +6,7 @@ import org.springdoc.core.annotations.ParameterObject; import java.time.LocalTime; +import java.util.List; @Data @NoArgsConstructor @@ -23,6 +24,9 @@ public class PostingListFilterDto { @Parameter(description = "읍/면/동 (town)") private String town; + @Parameter(description = "업종 ID 목록 (businessTypeIds) — 다중 선택 시 OR 조건") + private List businessTypeIds; + @Parameter(description = "최소 급여") private Integer minPayAmount; diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingListResponseDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingListResponseDto.java index 0d977a4a..f5f97ced 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingListResponseDto.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingListResponseDto.java @@ -37,10 +37,6 @@ public class PostingListResponseDto { @Schema(description = "생성일", example = "2023-10-01T12:00:00") private LocalDateTime createdAt; - @NotNull - @Schema(description = "키워드", example = "[{\"id\":1,\"name\":\"카페\"},{\"id\":4,\"name\":\"분식\"}]") - private List keywords; - @NotNull @Schema(description = "공고 스케줄", example = "[" + "{" + @@ -70,9 +66,6 @@ public static PostingListResponseDto from(PostingListResponse entity) { .schedules(entity.getSchedules().stream() .map(PostingScheduleResponseDto::from) .toList()) - .keywords(entity.getPostingKeywords().stream() - .map(PostingKeywordListResponseDto::from) - .toList()) .workspace(PostingListWorkspaceResponseDto.from(entity.getWorkspace())) .scrapped(entity.isScrapped()) .build(); diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingListWorkspaceResponseDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingListWorkspaceResponseDto.java index 1a9637a4..39e1ce54 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingListWorkspaceResponseDto.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingListWorkspaceResponseDto.java @@ -23,6 +23,13 @@ public class PostingListWorkspaceResponseDto { @Schema(description = "업장 이름", example = "카페 알터") private String businessName; + @NotBlank + @Schema(description = "업종", example = "카페") + private String businessType; + + @Schema(description = "업종 상세 (업종이 '기타'인 경우)", example = "떡볶이 전문점") + private String businessTypeDetail; + @NotNull @Schema(description = "위도", example = "37.5665") private BigDecimal latitude; @@ -35,6 +42,8 @@ public static PostingListWorkspaceResponseDto from(Workspace entity) { return PostingListWorkspaceResponseDto.builder() .id(entity.getId()) .businessName(entity.getBusinessName()) + .businessType(entity.getBusinessType().getName()) + .businessTypeDetail(entity.getBusinessTypeDetail()) .latitude(entity.getLatitude()) .longitude(entity.getLongitude()) .build(); diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingMapListResponseDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingMapListResponseDto.java index cad51c5f..858660d8 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingMapListResponseDto.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/general/posting/dto/PostingMapListResponseDto.java @@ -37,10 +37,6 @@ public class PostingMapListResponseDto { @Schema(description = "생성일", example = "2023-10-01T12:00:00") private LocalDateTime createdAt; - @NotNull - @Schema(description = "키워드", example = "[{\"id\":1,\"name\":\"카페\"},{\"id\":4,\"name\":\"분식\"}]") - private List keywords; - @NotNull @Schema(description = "공고 스케줄", example = "[" + "{" + @@ -67,9 +63,6 @@ public static PostingMapListResponseDto from(PostingListResponse entity) { .payAmount(entity.getPayAmount()) .paymentType(entity.getPaymentType()) .createdAt(entity.getCreatedAt()) - .keywords(entity.getPostingKeywords().stream() - .map(PostingKeywordListResponseDto::from) - .toList()) .schedules(entity.getSchedules().stream() .map(PostingScheduleResponseDto::from) .toList()) diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/general/workspace/controller/UserWorkspaceRequestController.java b/src/main/java/com/dreamteam/alter/adapter/inbound/general/workspace/controller/UserWorkspaceRequestController.java index 4d9b94d9..d9ef0717 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/general/workspace/controller/UserWorkspaceRequestController.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/general/workspace/controller/UserWorkspaceRequestController.java @@ -12,6 +12,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import com.dreamteam.alter.adapter.inbound.common.dto.BusinessTypeResponseDto; import com.dreamteam.alter.adapter.inbound.common.dto.CommonApiResponse; import com.dreamteam.alter.adapter.inbound.general.workspace.dto.CreateWorkspaceRequestDto; import com.dreamteam.alter.adapter.inbound.general.workspace.dto.WorkspaceRequestListResponseDto; @@ -20,6 +21,7 @@ import com.dreamteam.alter.domain.user.context.AppActor; import com.dreamteam.alter.domain.workspace.port.inbound.CancelWorkspaceRequestUseCase; import com.dreamteam.alter.domain.workspace.port.inbound.CreateWorkspaceRequestUseCase; +import com.dreamteam.alter.domain.workspace.port.inbound.GetBusinessTypeListUseCase; import com.dreamteam.alter.domain.workspace.port.inbound.GetWorkspaceRequestListUseCase; import com.dreamteam.alter.domain.workspace.port.inbound.GetWorkspaceRequestUseCase; @@ -45,6 +47,9 @@ public class UserWorkspaceRequestController implements UserWorkspaceRequestContr @Resource(name = "cancelWorkspaceRequest") private final CancelWorkspaceRequestUseCase cancelWorkspaceRequest; + @Resource(name = "getBusinessTypeList") + private final GetBusinessTypeListUseCase getBusinessTypeList; + @Override @PostMapping public ResponseEntity> createWorkspaceRequest( @@ -62,6 +67,15 @@ public ResponseEntity>> return ResponseEntity.ok(CommonApiResponse.of(getWorkspaceRequestList.execute(actor.getUser()))); } + @Override + @GetMapping("/business-types") + public ResponseEntity>> getBusinessTypeList() { + List businessTypes = getBusinessTypeList.execute().stream() + .map(BusinessTypeResponseDto::from) + .toList(); + return ResponseEntity.ok(CommonApiResponse.of(businessTypes)); + } + @Override @GetMapping("/{workspaceRequestId}") public ResponseEntity> getWorkspaceRequestDetail( diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/general/workspace/controller/UserWorkspaceRequestControllerSpec.java b/src/main/java/com/dreamteam/alter/adapter/inbound/general/workspace/controller/UserWorkspaceRequestControllerSpec.java index a8c0aed1..f47b057e 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/general/workspace/controller/UserWorkspaceRequestControllerSpec.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/general/workspace/controller/UserWorkspaceRequestControllerSpec.java @@ -2,6 +2,7 @@ import java.util.List; +import com.dreamteam.alter.adapter.inbound.common.dto.BusinessTypeResponseDto; import com.dreamteam.alter.adapter.inbound.common.dto.CommonApiResponse; import com.dreamteam.alter.adapter.inbound.general.workspace.dto.CreateWorkspaceRequestDto; import com.dreamteam.alter.adapter.inbound.general.workspace.dto.WorkspaceRequestListResponseDto; @@ -40,6 +41,12 @@ ResponseEntity> createWorkspaceRequest( }) ResponseEntity>> getWorkspaceRequestList(); + @Operation(summary = "업종(BusinessType) 목록 조회", description = "업장 등록 신청 폼에서 선택 가능한 업종 목록을 조회합니다. '기타'는 목록 마지막에 위치합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "업종 목록 조회 성공"), + }) + ResponseEntity>> getBusinessTypeList(); + @Operation(summary = "업장 등록 신청 상세 조회") @ApiResponses(value = { diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/general/workspace/dto/CreateWorkspaceRequestDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/general/workspace/dto/CreateWorkspaceRequestDto.java index 7a0c0f41..5a318d1a 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/general/workspace/dto/CreateWorkspaceRequestDto.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/general/workspace/dto/CreateWorkspaceRequestDto.java @@ -53,9 +53,13 @@ public class CreateWorkspaceRequestDto { @Schema(description = "경도", example = "129.115717") private BigDecimal longitude; - @NotBlank - @Schema(description = "업장 형태", example = "음식점") - private String type; + @NotNull + @Schema(description = "업종(BusinessType) ID", example = "1") + private Long businessTypeId; + + @Size(max = 128) + @Schema(description = "업종 상세 (업종이 '기타'인 경우 필수)", example = "떡볶이 전문점") + private String businessTypeDetail; @NotBlank @Schema(description = "업장 연락처", example = "02-1234-5678") diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/general/workspace/dto/PostingDetailWorkspaceResponseDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/general/workspace/dto/PostingDetailWorkspaceResponseDto.java index 6d9e25b9..88735542 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/general/workspace/dto/PostingDetailWorkspaceResponseDto.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/general/workspace/dto/PostingDetailWorkspaceResponseDto.java @@ -24,6 +24,13 @@ public class PostingDetailWorkspaceResponseDto { @Schema(description = "업장 이름", example = "카페 알터") private String name; + @NotBlank + @Schema(description = "업종", example = "카페") + private String businessType; + + @Schema(description = "업종 상세 (업종이 '기타'인 경우)", example = "떡볶이 전문점") + private String businessTypeDetail; + @NotBlank @Schema(description = "업장 상세 주소", example = "서울특별시 강남구 테헤란로 123") private String fullAddress; @@ -55,6 +62,8 @@ public static PostingDetailWorkspaceResponseDto from(Workspace workspace) { return PostingDetailWorkspaceResponseDto.builder() .id(workspace.getId()) .name(workspace.getBusinessName()) + .businessType(workspace.getBusinessType().getName()) + .businessTypeDetail(workspace.getBusinessTypeDetail()) .fullAddress(workspace.getFullAddress()) .province(workspace.getProvince()) .district(workspace.getDistrict()) @@ -68,6 +77,8 @@ public static PostingDetailWorkspaceResponseDto from(Workspace workspace, Reputa return PostingDetailWorkspaceResponseDto.builder() .id(workspace.getId()) .name(workspace.getBusinessName()) + .businessType(workspace.getBusinessType().getName()) + .businessTypeDetail(workspace.getBusinessTypeDetail()) .fullAddress(workspace.getFullAddress()) .province(workspace.getProvince()) .district(workspace.getDistrict()) diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/general/workspace/dto/WorkspaceRequestResponseDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/general/workspace/dto/WorkspaceRequestResponseDto.java index 63ba4d2f..5956e17e 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/general/workspace/dto/WorkspaceRequestResponseDto.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/general/workspace/dto/WorkspaceRequestResponseDto.java @@ -33,6 +33,9 @@ public class WorkspaceRequestResponseDto { @Schema(description = "업종", example = "카페") private String businessType; + @Schema(description = "업종 상세 (업종이 '기타'인 경우)", example = "떡볶이 전문점") + private String businessTypeDetail; + @Schema(description = "연락처", example = "01012345678") private String contact; @@ -74,6 +77,7 @@ public static WorkspaceRequestResponseDto of( .businessRegistrationNo(workspaceRequest.getBusinessRegistrationNo()) .businessName(workspaceRequest.getBusinessName()) .businessType(workspaceRequest.getBusinessType()) + .businessTypeDetail(workspaceRequest.getBusinessTypeDetail()) .contact(workspaceRequest.getContact()) .status(DescribedEnumDto.of(workspaceRequest.getStatus(), WorkspaceRequestStatus.describe())) .fullAddress(workspaceRequest.getFullAddress()) diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/controller/ManagerPostingController.java b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/controller/ManagerPostingController.java index ba9b941d..6d34852c 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/controller/ManagerPostingController.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/controller/ManagerPostingController.java @@ -17,7 +17,6 @@ import com.dreamteam.alter.adapter.inbound.common.dto.CursorPageRequestDto; import com.dreamteam.alter.adapter.inbound.common.dto.CursorPaginatedApiResponse; import com.dreamteam.alter.adapter.inbound.general.posting.dto.CreatePostingRequestDto; -import com.dreamteam.alter.adapter.inbound.general.posting.dto.PostingKeywordListResponseDto; import com.dreamteam.alter.adapter.inbound.manager.posting.dto.ManagerPostingDetailResponseDto; import com.dreamteam.alter.adapter.inbound.manager.posting.dto.ManagerPostingListFilterDto; import com.dreamteam.alter.adapter.inbound.manager.posting.dto.ManagerPostingListResponseDto; @@ -29,7 +28,6 @@ import com.dreamteam.alter.adapter.inbound.manager.posting.dto.UpdatePostingStatusRequestDto; import com.dreamteam.alter.application.aop.ManagerActionContext; import com.dreamteam.alter.domain.posting.port.inbound.CreatePostingUseCase; -import com.dreamteam.alter.domain.posting.port.inbound.GetPostingKeywordListUseCase; import com.dreamteam.alter.domain.posting.port.inbound.ManagerGetPostingApplicationDetailUseCase; import com.dreamteam.alter.domain.posting.port.inbound.ManagerGetPostingApplicationListWithCursorUseCase; import com.dreamteam.alter.domain.posting.port.inbound.ManagerGetPostingDetailUseCase; @@ -53,9 +51,6 @@ public class ManagerPostingController implements ManagerPostingControllerSpec { @Resource(name = "createPosting") private final CreatePostingUseCase createPosting; - @Resource(name = "getPostingKeywordList") - private final GetPostingKeywordListUseCase getPostingKeywordList; - @Resource(name = "managerGetPostingApplicationListWithCursor") private final ManagerGetPostingApplicationListWithCursorUseCase managerGetPostingApplicationListWithCursor; @@ -86,12 +81,6 @@ public ResponseEntity> createPosting( return ResponseEntity.ok(CommonApiResponse.empty()); } - @Override - @GetMapping("/available-keywords") - public ResponseEntity>> getAvailablePostingKeywords() { - return ResponseEntity.ok(CommonApiResponse.of(getPostingKeywordList.execute())); - } - @Override @GetMapping public ResponseEntity> getMyPostingsWithCursor( diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/controller/ManagerPostingControllerSpec.java b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/controller/ManagerPostingControllerSpec.java index a5d39900..f7437d7e 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/controller/ManagerPostingControllerSpec.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/controller/ManagerPostingControllerSpec.java @@ -6,7 +6,6 @@ import com.dreamteam.alter.adapter.inbound.common.dto.ErrorResponse; import com.dreamteam.alter.adapter.inbound.general.posting.dto.CreatePostingRequestDto; import com.dreamteam.alter.adapter.inbound.manager.posting.dto.ManagerPostingListResponseDto; -import com.dreamteam.alter.adapter.inbound.general.posting.dto.PostingKeywordListResponseDto; import com.dreamteam.alter.adapter.inbound.manager.posting.dto.ManagerPostingDetailResponseDto; import com.dreamteam.alter.adapter.inbound.manager.posting.dto.ManagerPostingListFilterDto; import com.dreamteam.alter.adapter.inbound.manager.posting.dto.PostingApplicationListFilterDto; @@ -48,22 +47,6 @@ public interface ManagerPostingControllerSpec { }) ResponseEntity> createPosting(@Valid @RequestBody CreatePostingRequestDto request); - @Operation(summary = "등록 가능한 공고 키워드 조회", description = "") - @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\" : \"B009\"}" - ), - })) - }) - ResponseEntity>> getAvailablePostingKeywords(); - @Operation(summary = "매니저 - 내가 등록한 공고 목록 조회 (커서 페이징)", description = "") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "공고 목록 조회 성공") diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/ManagerPostingDetailResponseDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/ManagerPostingDetailResponseDto.java index 026ec861..8222e996 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/ManagerPostingDetailResponseDto.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/ManagerPostingDetailResponseDto.java @@ -1,7 +1,6 @@ package com.dreamteam.alter.adapter.inbound.manager.posting.dto; import com.dreamteam.alter.adapter.inbound.common.dto.DescribedEnumDto; -import com.dreamteam.alter.adapter.inbound.general.posting.dto.PostingKeywordListResponseDto; import com.dreamteam.alter.adapter.inbound.general.posting.dto.PostingScheduleResponseDto; import com.dreamteam.alter.adapter.inbound.general.workspace.dto.PostingDetailWorkspaceResponseDto; import com.dreamteam.alter.adapter.outbound.posting.persistence.readonly.ManagerPostingDetailResponse; @@ -58,10 +57,6 @@ public class ManagerPostingDetailResponseDto { @Schema(description = "수정일", example = "2023-10-01T12:00:00") private LocalDateTime updatedAt; - @NotNull - @Schema(description = "키워드", example = "[{\"id\":1,\"name\":\"카페\"},{\"id\":4,\"name\":\"분식\"}]") - private List keywords; - @NotNull @Schema(description = "공고 스케줄", example = "[{\"id\":1,\"workingDays\":[\"MONDAY\",\"WEDNESDAY\"],\"startTime\":\"09:00\",\"endTime\":\"18:00\",\"positionsNeeded\":3,\"positionsAvailable\":2,\"position\":\"홀서빙\"}]") private List schedules; @@ -80,9 +75,6 @@ public static ManagerPostingDetailResponseDto from(ManagerPostingDetailResponse .schedules(entity.getSchedules().stream() .map(PostingScheduleResponseDto::from) .toList()) - .keywords(entity.getPostingKeywords().stream() - .map(PostingKeywordListResponseDto::from) - .toList()) .build(); } } diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/ManagerPostingListResponseDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/ManagerPostingListResponseDto.java index 7a146026..4500f749 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/ManagerPostingListResponseDto.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/ManagerPostingListResponseDto.java @@ -1,7 +1,6 @@ package com.dreamteam.alter.adapter.inbound.manager.posting.dto; import com.dreamteam.alter.adapter.inbound.general.posting.dto.ManagerPostingListWorkspaceResponseDto; -import com.dreamteam.alter.adapter.inbound.general.posting.dto.PostingKeywordListResponseDto; import com.dreamteam.alter.adapter.inbound.general.posting.dto.PostingScheduleResponseDto; import com.dreamteam.alter.adapter.outbound.posting.persistence.readonly.ManagerPostingListResponse; import com.dreamteam.alter.domain.posting.type.PaymentType; @@ -40,10 +39,6 @@ public class ManagerPostingListResponseDto { @Schema(description = "생성일", example = "2023-10-01T12:00:00") private LocalDateTime createdAt; - @NotNull - @Schema(description = "키워드", example = "[{\"id\":1,\"name\":\"카페\"},{\"id\":4,\"name\":\"분식\"}]") - private List keywords; - @NotNull @Schema(description = "공고 스케줄", example = "[" + "{" + @@ -66,9 +61,6 @@ public static ManagerPostingListResponseDto from(ManagerPostingListResponse resp .payAmount(response.getPayAmount()) .paymentType(response.getPaymentType()) .createdAt(response.getCreatedAt()) - .keywords(response.getPostingKeywords().stream() - .map(PostingKeywordListResponseDto::from) - .toList()) .schedules(response.getSchedules().stream() .map(PostingScheduleResponseDto::from) .toList()) diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/UpdatePostingRequestDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/UpdatePostingRequestDto.java index 63b30d7c..5c1af20a 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/UpdatePostingRequestDto.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/UpdatePostingRequestDto.java @@ -8,7 +8,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import lombok.AccessLevel; @@ -39,10 +38,6 @@ public class UpdatePostingRequestDto { @Schema(description = "급여 타입", example = "HOURLY") private PaymentType paymentType; - @NotEmpty - @Schema(description = "키워드", example = "[2, 3, 1]") - private List keywords; - @Valid @Schema(description = "새로 추가할 스케줄", example = "[{\"workingDays\": [\"FRIDAY\"], \"startTime\": \"13:00\", \"endTime\": \"21:00\", \"positionsNeeded\": 1, \"position\": \"설거지\"}]") private List createSchedules; diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/controller/ManagerWorkspaceRequestController.java b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/controller/ManagerWorkspaceRequestController.java index 5fd83589..3d4ab461 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/controller/ManagerWorkspaceRequestController.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/controller/ManagerWorkspaceRequestController.java @@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import com.dreamteam.alter.adapter.inbound.common.dto.BusinessTypeResponseDto; import com.dreamteam.alter.adapter.inbound.common.dto.CommonApiResponse; import com.dreamteam.alter.adapter.inbound.general.workspace.dto.CreateWorkspaceRequestDto; import com.dreamteam.alter.adapter.inbound.general.workspace.dto.WorkspaceRequestListResponseDto; @@ -21,6 +22,7 @@ import com.dreamteam.alter.domain.user.context.ManagerActor; import com.dreamteam.alter.domain.workspace.port.inbound.CancelWorkspaceRequestUseCase; import com.dreamteam.alter.domain.workspace.port.inbound.CreateWorkspaceRequestUseCase; +import com.dreamteam.alter.domain.workspace.port.inbound.GetBusinessTypeListUseCase; import com.dreamteam.alter.domain.workspace.port.inbound.GetWorkspaceRequestListUseCase; import com.dreamteam.alter.domain.workspace.port.inbound.GetWorkspaceRequestUseCase; @@ -47,6 +49,9 @@ public class ManagerWorkspaceRequestController implements ManagerWorkspaceReques @Resource(name = "cancelWorkspaceRequest") private final CancelWorkspaceRequestUseCase cancelWorkspaceRequest; + @Resource(name = "getBusinessTypeList") + private final GetBusinessTypeListUseCase getBusinessTypeList; + @Override @PostMapping public ResponseEntity> createWorkspaceRequest( @@ -64,6 +69,15 @@ public ResponseEntity>> return ResponseEntity.ok(CommonApiResponse.of(getWorkspaceRequestList.execute(actor.getManagerUser().getUser()))); } + @Override + @GetMapping("/business-types") + public ResponseEntity>> getBusinessTypeList() { + List businessTypes = getBusinessTypeList.execute().stream() + .map(BusinessTypeResponseDto::from) + .toList(); + return ResponseEntity.ok(CommonApiResponse.of(businessTypes)); + } + @Override @GetMapping("/{workspaceRequestId}") public ResponseEntity> getWorkspaceRequestDetail( diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/controller/ManagerWorkspaceRequestControllerSpec.java b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/controller/ManagerWorkspaceRequestControllerSpec.java index 73447cea..7ceb9c06 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/controller/ManagerWorkspaceRequestControllerSpec.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/controller/ManagerWorkspaceRequestControllerSpec.java @@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; +import com.dreamteam.alter.adapter.inbound.common.dto.BusinessTypeResponseDto; import com.dreamteam.alter.adapter.inbound.common.dto.CommonApiResponse; import com.dreamteam.alter.adapter.inbound.general.workspace.dto.CreateWorkspaceRequestDto; import com.dreamteam.alter.adapter.inbound.general.workspace.dto.WorkspaceRequestListResponseDto; @@ -38,6 +39,12 @@ ResponseEntity> createWorkspaceRequest( }) ResponseEntity>> getWorkspaceRequestList(); + @Operation(summary = "매니저 - 업종(BusinessType) 목록 조회", description = "업장 등록 신청 폼에서 선택 가능한 업종 목록을 조회합니다. '기타'는 목록 마지막에 위치합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "업종 목록 조회 성공"), + }) + ResponseEntity>> getBusinessTypeList(); + @Operation(summary = "매니저 - 업장 등록 신청 상세 조회") @ApiResponses(value = { diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/dto/ManagerWorkspaceListResponseDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/dto/ManagerWorkspaceListResponseDto.java index e2e9c7e5..9511607f 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/dto/ManagerWorkspaceListResponseDto.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/dto/ManagerWorkspaceListResponseDto.java @@ -29,6 +29,9 @@ public class ManagerWorkspaceListResponseDto { @Schema(description = "업종", example = "카페") private String businessType; + @Schema(description = "업종 상세 (업종이 '기타'인 경우)", example = "떡볶이 전문점") + private String businessTypeDetail; + @NotBlank @Schema(description = "업장 전체 주소", example = "서울특별시 강남구 테헤란로 123") private String fullAddress; @@ -46,6 +49,7 @@ public static ManagerWorkspaceListResponseDto of(ManagerWorkspaceListResponse en .id(entity.getId()) .businessName(entity.getBusinessName()) .businessType(entity.getBusinessType()) + .businessTypeDetail(entity.getBusinessTypeDetail()) .fullAddress(entity.getFullAddress()) .createdAt(entity.getCreatedAt()) .status(DescribedEnumDto.of(entity.getStatus(), WorkspaceStatus.describe())) diff --git a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/dto/ManagerWorkspaceResponseDto.java b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/dto/ManagerWorkspaceResponseDto.java index 6fd0f799..695c93e6 100644 --- a/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/dto/ManagerWorkspaceResponseDto.java +++ b/src/main/java/com/dreamteam/alter/adapter/inbound/manager/workspace/dto/ManagerWorkspaceResponseDto.java @@ -36,6 +36,9 @@ public class ManagerWorkspaceResponseDto { @Schema(description = "업종", example = "카페") private String businessType; + @Schema(description = "업종 상세 (업종이 '기타'인 경우)", example = "떡볶이 전문점") + private String businessTypeDetail; + @NotBlank @Schema(description = "연락처", example = "01012345678") private String contact; @@ -82,6 +85,7 @@ public static ManagerWorkspaceResponseDto of( .businessRegistrationNo(entity.getBusinessRegistrationNo()) .businessName(entity.getBusinessName()) .businessType(entity.getBusinessType()) + .businessTypeDetail(entity.getBusinessTypeDetail()) .contact(entity.getContact()) .description(entity.getDescription()) .status(DescribedEnumDto.of(entity.getStatus(), WorkspaceStatus.describe())) diff --git a/src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/PostingKeywordQueryRepositoryImpl.java b/src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/PostingKeywordQueryRepositoryImpl.java deleted file mode 100644 index 28a40bff..00000000 --- a/src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/PostingKeywordQueryRepositoryImpl.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.dreamteam.alter.adapter.outbound.posting.persistence; - -import com.dreamteam.alter.domain.posting.entity.PostingKeyword; -import com.dreamteam.alter.domain.posting.entity.QPostingKeyword; -import com.dreamteam.alter.domain.posting.port.outbound.PostingKeywordQueryRepository; -import com.querydsl.jpa.impl.JPAQueryFactory; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -@RequiredArgsConstructor -public class PostingKeywordQueryRepositoryImpl implements PostingKeywordQueryRepository { - - private final JPAQueryFactory queryFactory; - - @Override - public List findByIds(List keywordIds) { - QPostingKeyword qPostingKeyword = QPostingKeyword.postingKeyword; - - return queryFactory - .select(qPostingKeyword) - .from(qPostingKeyword) - .where(qPostingKeyword.id.in(keywordIds)) - .fetch(); - } - - @Override - public List findAll() { - QPostingKeyword qPostingKeyword = QPostingKeyword.postingKeyword; - - return queryFactory - .selectFrom(qPostingKeyword) - .fetch(); - } - -} diff --git a/src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/PostingQueryRepositoryImpl.java b/src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/PostingQueryRepositoryImpl.java index 309fde81..f859cdb3 100644 --- a/src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/PostingQueryRepositoryImpl.java +++ b/src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/PostingQueryRepositoryImpl.java @@ -15,6 +15,7 @@ import com.dreamteam.alter.domain.user.entity.QUserFavoritePosting; import com.dreamteam.alter.domain.user.entity.ManagerUser; import com.dreamteam.alter.domain.user.entity.User; +import com.dreamteam.alter.domain.workspace.entity.QBusinessType; import com.dreamteam.alter.domain.workspace.entity.QWorkspace; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Projections; @@ -27,7 +28,6 @@ import java.math.BigDecimal; import java.util.*; -import java.util.stream.Collectors; @Repository @RequiredArgsConstructor @@ -53,6 +53,7 @@ public long getCountOfPostings(PostingListFilterDto filter) { eqProvince(qWorkspace, filter.getProvince()), eqDistrict(qWorkspace, filter.getDistrict()), eqTown(qWorkspace, filter.getTown()), + businessTypeIn(qWorkspace, filter.getBusinessTypeIds()), gtePayAmount(qPosting, filter.getMinPayAmount()), ltePayAmount(qPosting, filter.getMaxPayAmount()), gteStartTime(qPostingSchedule, filter.getStartTime()), @@ -86,8 +87,6 @@ public long getCountOfPostingMapList(PostingMapListFilterDto filter) { public List getPostingsWithCursor(CursorPageRequest request, PostingListFilterDto filter, User user) { QPosting qPosting = QPosting.posting; QPostingSchedule qPostingSchedule = QPostingSchedule.postingSchedule; - QPostingKeywordMap qPostingKeywordMap = QPostingKeywordMap.postingKeywordMap; - QPostingKeyword qPostingKeyword = QPostingKeyword.postingKeyword; QWorkspace qWorkspace = QWorkspace.workspace; QUserFavoritePosting qUserFavoritePosting = QUserFavoritePosting.userFavoritePosting; @@ -102,6 +101,7 @@ public List getPostingsWithCursor(CursorPageRequest getPostingsWithCursor(CursorPageRequest getPostingsWithCursor(CursorPageRequest keywordMaps = queryFactory - .selectFrom(qPostingKeywordMap) - .leftJoin(qPostingKeywordMap.postingKeyword, qPostingKeyword).fetchJoin() - .where(qPostingKeywordMap.posting.id.in(postingIds)) - .fetch(); - - Map> postingIdToKeywords = keywordMaps.stream() - .collect( - Collectors.groupingBy( - pkMap -> pkMap.getPosting().getId(), - Collectors.mapping(PostingKeywordMap::getPostingKeyword, Collectors.toList()) - ) - ); - return postings.stream() .map( posting -> PostingListResponse.of( posting, - postingIdToKeywords, scrappedPostingIds.contains(posting.getId()) ) ) @@ -163,8 +149,6 @@ public List getPostingsWithCursor(CursorPageRequest getPostingMapListWithCursor(CursorPageRequest request, PostingMapListFilterDto filter, User user) { QPosting qPosting = QPosting.posting; QPostingSchedule qPostingSchedule = QPostingSchedule.postingSchedule; - QPostingKeywordMap qPostingKeywordMap = QPostingKeywordMap.postingKeywordMap; - QPostingKeyword qPostingKeyword = QPostingKeyword.postingKeyword; QWorkspace qWorkspace = QWorkspace.workspace; QUserFavoritePosting qUserFavoritePosting = QUserFavoritePosting.userFavoritePosting; @@ -191,6 +175,7 @@ public List getPostingMapListWithCursor(CursorPageRequest getPostingMapListWithCursor(CursorPageRequest keywordMaps = queryFactory - .selectFrom(qPostingKeywordMap) - .leftJoin(qPostingKeywordMap.postingKeyword, qPostingKeyword).fetchJoin() - .where(qPostingKeywordMap.posting.id.in(postingIds)) - .fetch(); - - Map> postingIdToKeywords = keywordMaps.stream() - .collect( - Collectors.groupingBy( - pkMap -> pkMap.getPosting().getId(), - java.util.stream.Collectors.mapping(PostingKeywordMap::getPostingKeyword, java.util.stream.Collectors.toList()) - ) - ); - return postings.stream() .map( posting -> PostingListResponse.of( posting, - postingIdToKeywords, scrappedPostingIds.contains(posting.getId()) ) ) @@ -266,14 +236,13 @@ public List getPostingListForMapMarker(PostingM public List getWorkspacePostingList(Long workspaceId, User user) { QPosting qPosting = QPosting.posting; QPostingSchedule qPostingSchedule = QPostingSchedule.postingSchedule; - QPostingKeywordMap qPostingKeywordMap = QPostingKeywordMap.postingKeywordMap; - QPostingKeyword qPostingKeyword = QPostingKeyword.postingKeyword; QWorkspace qWorkspace = QWorkspace.workspace; QUserFavoritePosting qUserFavoritePosting = QUserFavoritePosting.userFavoritePosting; List postings = queryFactory .selectFrom(qPosting) .leftJoin(qPosting.workspace, qWorkspace).fetchJoin() + .leftJoin(qWorkspace.businessType, QBusinessType.businessType).fetchJoin() .leftJoin(qPosting.schedules, qPostingSchedule).fetchJoin() .where( qPosting.status.eq(PostingStatus.OPEN), @@ -300,24 +269,9 @@ public List getWorkspacePostingList(Long workspaceId, User .fetch() ); - List keywordMaps = queryFactory - .selectFrom(qPostingKeywordMap) - .leftJoin(qPostingKeywordMap.postingKeyword, qPostingKeyword).fetchJoin() - .where(qPostingKeywordMap.posting.id.in(postingIds)) - .fetch(); - - Map> postingIdToKeywords = keywordMaps.stream() - .collect( - Collectors.groupingBy( - pkMap -> pkMap.getPosting().getId(), - Collectors.mapping(PostingKeywordMap::getPostingKeyword, Collectors.toList()) - ) - ); - return postings.stream() .map(posting -> PostingListResponse.of( posting, - postingIdToKeywords, scrappedPostingIds.contains(posting.getId()) )) .toList(); @@ -327,8 +281,6 @@ public List getWorkspacePostingList(Long workspaceId, User public PostingDetailResponse getPostingDetail(Long postingId, User user) { QPosting qPosting = QPosting.posting; QPostingSchedule qPostingSchedule = QPostingSchedule.postingSchedule; - QPostingKeywordMap qPostingKeywordMap = QPostingKeywordMap.postingKeywordMap; - QPostingKeyword qPostingKeyword = QPostingKeyword.postingKeyword; QWorkspace qWorkspace = QWorkspace.workspace; QUserFavoritePosting qUserFavoritePosting = QUserFavoritePosting.userFavoritePosting; @@ -336,6 +288,7 @@ public PostingDetailResponse getPostingDetail(Long postingId, User user) { .selectFrom(qPosting) .leftJoin(qPosting.schedules, qPostingSchedule).fetchJoin() .leftJoin(qPosting.workspace, qWorkspace).fetchJoin() + .leftJoin(qWorkspace.businessType, QBusinessType.businessType).fetchJoin() .where( qPosting.id.eq(postingId), qPosting.status.eq(PostingStatus.OPEN) @@ -346,13 +299,6 @@ public PostingDetailResponse getPostingDetail(Long postingId, User user) { return null; } - List postingKeywords = queryFactory - .select(qPostingKeyword) - .from(qPostingKeywordMap) - .leftJoin(qPostingKeywordMap.postingKeyword, qPostingKeyword) - .where(qPostingKeywordMap.posting.id.eq(postingId)) - .fetch(); - boolean scrapped = ObjectUtils.isNotEmpty( queryFactory .selectOne() @@ -364,7 +310,7 @@ public PostingDetailResponse getPostingDetail(Long postingId, User user) { .fetchFirst() ); - return PostingDetailResponse.of(posting, postingKeywords, scrapped); + return PostingDetailResponse.of(posting, scrapped); } @Override @@ -411,8 +357,6 @@ public List getManagerPostingsWithCursor( ) { QPosting qPosting = QPosting.posting; QPostingSchedule qPostingSchedule = QPostingSchedule.postingSchedule; - QPostingKeywordMap qPostingKeywordMap = QPostingKeywordMap.postingKeywordMap; - QPostingKeyword qPostingKeyword = QPostingKeyword.postingKeyword; QWorkspace qWorkspace = QWorkspace.workspace; List postingIds = queryFactory @@ -438,32 +382,14 @@ public List getManagerPostingsWithCursor( .selectFrom(qPosting) .leftJoin(qPosting.schedules, qPostingSchedule).fetchJoin() .leftJoin(qPosting.workspace, qWorkspace).fetchJoin() + .leftJoin(qWorkspace.businessType, QBusinessType.businessType).fetchJoin() .where(qPosting.id.in(postingIds)) .orderBy(qPosting.createdAt.desc(), qPosting.id.desc()) .distinct() .fetch(); - List keywordMaps = queryFactory - .selectFrom(qPostingKeywordMap) - .leftJoin(qPostingKeywordMap.postingKeyword, qPostingKeyword).fetchJoin() - .where(qPostingKeywordMap.posting.id.in(postingIds)) - .fetch(); - - Map> postingIdToKeywords = keywordMaps.stream() - .collect( - Collectors.groupingBy( - pkMap -> pkMap.getPosting().getId(), - Collectors.mapping(PostingKeywordMap::getPostingKeyword, Collectors.toList()) - ) - ); - return postings.stream() - .map( - posting -> ManagerPostingListResponse.of( - posting, - postingIdToKeywords - ) - ) + .map(ManagerPostingListResponse::of) .toList(); } @@ -551,6 +477,10 @@ private BooleanExpression eqTown(QWorkspace qWorkspace, String town) { return town != null ? qWorkspace.town.eq(town) : null; } + private BooleanExpression businessTypeIn(QWorkspace qWorkspace, java.util.List businessTypeIds) { + return ObjectUtils.isNotEmpty(businessTypeIds) ? qWorkspace.businessType.id.in(businessTypeIds) : null; + } + private BooleanExpression gtePayAmount(QPosting qPosting, Integer minPayAmount) { return minPayAmount != null ? qPosting.payAmount.goe(minPayAmount) : null; } @@ -636,14 +566,13 @@ private OrderSpecifier[] getOrderSpecifiers(QPosting qPosting, Boolean payAmo public Optional getManagerPostingDetail(Long postingId, ManagerUser managerUser) { QPosting qPosting = QPosting.posting; QPostingSchedule qPostingSchedule = QPostingSchedule.postingSchedule; - QPostingKeywordMap qPostingKeywordMap = QPostingKeywordMap.postingKeywordMap; - QPostingKeyword qPostingKeyword = QPostingKeyword.postingKeyword; QWorkspace qWorkspace = QWorkspace.workspace; Posting posting = queryFactory .selectFrom(qPosting) .leftJoin(qPosting.schedules, qPostingSchedule).fetchJoin() .leftJoin(qPosting.workspace, qWorkspace).fetchJoin() + .leftJoin(qWorkspace.businessType, QBusinessType.businessType).fetchJoin() .where( qPosting.id.eq(postingId), qWorkspace.managerUser.eq(managerUser) @@ -654,14 +583,7 @@ public Optional getManagerPostingDetail(Long posti return Optional.empty(); } - List postingKeywords = queryFactory - .select(qPostingKeyword) - .from(qPostingKeywordMap) - .leftJoin(qPostingKeywordMap.postingKeyword, qPostingKeyword) - .where(qPostingKeywordMap.posting.id.eq(postingId)) - .fetch(); - - return Optional.of(ManagerPostingDetailResponse.of(posting, postingKeywords)); + return Optional.of(ManagerPostingDetailResponse.of(posting)); } @Override diff --git a/src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/readonly/ManagerPostingDetailResponse.java b/src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/readonly/ManagerPostingDetailResponse.java index 4ac27a5f..d4e6052f 100644 --- a/src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/readonly/ManagerPostingDetailResponse.java +++ b/src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/readonly/ManagerPostingDetailResponse.java @@ -1,6 +1,5 @@ package com.dreamteam.alter.adapter.outbound.posting.persistence.readonly; -import com.dreamteam.alter.domain.posting.entity.PostingKeyword; import com.dreamteam.alter.domain.posting.entity.Posting; import com.dreamteam.alter.domain.posting.entity.PostingSchedule; import com.dreamteam.alter.domain.posting.type.PaymentType; @@ -34,13 +33,10 @@ public class ManagerPostingDetailResponse { private LocalDateTime updatedAt; - private List postingKeywords; - private List schedules; public static ManagerPostingDetailResponse of( - Posting posting, - List postingKeywords + Posting posting ) { return new ManagerPostingDetailResponse( posting.getId(), @@ -52,7 +48,6 @@ public static ManagerPostingDetailResponse of( posting.getStatus(), posting.getCreatedAt(), posting.getUpdatedAt(), - postingKeywords, posting.getSchedules() ); } diff --git a/src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/readonly/ManagerPostingListResponse.java b/src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/readonly/ManagerPostingListResponse.java index 6e24a67e..6c06539a 100644 --- a/src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/readonly/ManagerPostingListResponse.java +++ b/src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/readonly/ManagerPostingListResponse.java @@ -1,7 +1,6 @@ package com.dreamteam.alter.adapter.outbound.posting.persistence.readonly; import com.dreamteam.alter.domain.posting.entity.Posting; -import com.dreamteam.alter.domain.posting.entity.PostingKeyword; import com.dreamteam.alter.domain.posting.entity.PostingSchedule; import com.dreamteam.alter.domain.posting.type.PaymentType; import com.dreamteam.alter.domain.workspace.entity.Workspace; @@ -9,7 +8,6 @@ import java.time.LocalDateTime; import java.util.List; -import java.util.Map; @Getter @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -27,13 +25,11 @@ public class ManagerPostingListResponse { private LocalDateTime createdAt; - private List postingKeywords; - private List schedules; private Workspace workspace; - public static ManagerPostingListResponse of(Posting posting, Map> keywordsMap) { + public static ManagerPostingListResponse of(Posting posting) { return ManagerPostingListResponse.builder() .id(posting.getId()) .title(posting.getTitle()) @@ -41,7 +37,6 @@ public static ManagerPostingListResponse of(Posting posting, Map postingKeywords; - private List schedules; private boolean scrapped; public static PostingDetailResponse of( Posting posting, - List postingKeywords, boolean scrapped ) { return new PostingDetailResponse( @@ -48,7 +44,6 @@ public static PostingDetailResponse of( posting.getPayAmount(), posting.getPaymentType(), posting.getCreatedAt(), - postingKeywords, posting.getSchedules(), scrapped ); diff --git a/src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/readonly/PostingListResponse.java b/src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/readonly/PostingListResponse.java index c2424103..fd430394 100644 --- a/src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/readonly/PostingListResponse.java +++ b/src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/readonly/PostingListResponse.java @@ -1,6 +1,5 @@ package com.dreamteam.alter.adapter.outbound.posting.persistence.readonly; -import com.dreamteam.alter.domain.posting.entity.PostingKeyword; import com.dreamteam.alter.domain.posting.entity.Posting; import com.dreamteam.alter.domain.posting.entity.PostingSchedule; import com.dreamteam.alter.domain.posting.type.PaymentType; @@ -9,7 +8,6 @@ import java.time.LocalDateTime; import java.util.List; -import java.util.Map; @Getter @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -27,15 +25,13 @@ public class PostingListResponse { private LocalDateTime createdAt; - private List postingKeywords; - private List schedules; private Workspace workspace; private boolean scrapped; - public static PostingListResponse of(Posting posting, Map> keywordsMap, boolean scrapped) { + public static PostingListResponse of(Posting posting, boolean scrapped) { return PostingListResponse.builder() .id(posting.getId()) .title(posting.getTitle()) @@ -43,7 +39,6 @@ public static PostingListResponse of(Posting posting, Map { + boolean existsByName(String name); + + boolean existsByNameAndIdNot(String name, Long id); +} diff --git a/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/BusinessTypeQueryRepositoryImpl.java b/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/BusinessTypeQueryRepositoryImpl.java new file mode 100644 index 00000000..9d568d3a --- /dev/null +++ b/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/BusinessTypeQueryRepositoryImpl.java @@ -0,0 +1,58 @@ +package com.dreamteam.alter.adapter.outbound.workspace.persistence; + +import java.util.List; + +import org.springframework.stereotype.Repository; + +import com.dreamteam.alter.domain.workspace.entity.BusinessType; +import com.dreamteam.alter.domain.workspace.entity.QBusinessType; +import com.dreamteam.alter.domain.workspace.entity.QWorkspace; +import com.dreamteam.alter.domain.workspace.entity.QWorkspaceRequest; +import com.dreamteam.alter.domain.workspace.port.outbound.BusinessTypeQueryRepository; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class BusinessTypeQueryRepositoryImpl implements BusinessTypeQueryRepository { + + private final JPAQueryFactory queryFactory; + + @Override + public List findAll() { + QBusinessType qBusinessType = QBusinessType.businessType; + + // '기타'(requires_detail=true)를 목록 맨 뒤로 정렬 + return queryFactory + .selectFrom(qBusinessType) + .orderBy(qBusinessType.requiresDetail.asc(), qBusinessType.id.asc()) + .fetch(); + } + + @Override + public boolean existsWorkspaceUsingBusinessType(Long businessTypeId) { + QWorkspace qWorkspace = QWorkspace.workspace; + + Integer result = queryFactory + .selectOne() + .from(qWorkspace) + .where(qWorkspace.businessType.id.eq(businessTypeId)) + .fetchFirst(); + + return result != null; + } + + @Override + public boolean existsWorkspaceRequestUsingBusinessType(Long businessTypeId) { + QWorkspaceRequest qWorkspaceRequest = QWorkspaceRequest.workspaceRequest; + + Integer result = queryFactory + .selectOne() + .from(qWorkspaceRequest) + .where(qWorkspaceRequest.businessType.id.eq(businessTypeId)) + .fetchFirst(); + + return result != null; + } +} diff --git a/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/BusinessTypeRepositoryImpl.java b/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/BusinessTypeRepositoryImpl.java new file mode 100644 index 00000000..b5f9c90c --- /dev/null +++ b/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/BusinessTypeRepositoryImpl.java @@ -0,0 +1,42 @@ +package com.dreamteam.alter.adapter.outbound.workspace.persistence; + +import java.util.Optional; + +import org.springframework.stereotype.Repository; + +import com.dreamteam.alter.domain.workspace.entity.BusinessType; +import com.dreamteam.alter.domain.workspace.port.outbound.BusinessTypeRepository; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class BusinessTypeRepositoryImpl implements BusinessTypeRepository { + + private final BusinessTypeJpaRepository businessTypeJpaRepository; + + @Override + public Optional findById(Long id) { + return businessTypeJpaRepository.findById(id); + } + + @Override + public BusinessType save(BusinessType businessType) { + return businessTypeJpaRepository.save(businessType); + } + + @Override + public void delete(BusinessType businessType) { + businessTypeJpaRepository.delete(businessType); + } + + @Override + public boolean existsByName(String name) { + return businessTypeJpaRepository.existsByName(name); + } + + @Override + public boolean existsByNameAndIdNot(String name, Long id) { + return businessTypeJpaRepository.existsByNameAndIdNot(name, id); + } +} 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 76d79c54..900b7cf2 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 @@ -73,7 +73,8 @@ public List getManagerWorkspaceList(ManagerUser ma ManagerWorkspaceListResponse.class, qWorkspace.id, qWorkspace.businessName, - qWorkspace.businessType, + qWorkspace.businessType.name, + qWorkspace.businessTypeDetail, qWorkspace.fullAddress, qWorkspace.createdAt, qWorkspace.status @@ -96,7 +97,8 @@ public ManagerWorkspaceResponse getByManagerUserAndId(ManagerUser managerUser, L qWorkspace.id, qWorkspace.businessRegistrationNo, qWorkspace.businessName, - qWorkspace.businessType, + qWorkspace.businessType.name, + qWorkspace.businessTypeDetail, qWorkspace.contact, qWorkspace.description, qWorkspace.status, diff --git a/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/WorkspaceRequestQueryRepositoryImpl.java b/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/WorkspaceRequestQueryRepositoryImpl.java index 56a715de..59de9f64 100644 --- a/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/WorkspaceRequestQueryRepositoryImpl.java +++ b/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/WorkspaceRequestQueryRepositoryImpl.java @@ -80,7 +80,8 @@ public WorkspaceRequestResponse getWorkspaceRequest(Long userId, Long workspaceR qWorkspaceRequest.id, qWorkspaceRequest.businessRegistrationNo, qWorkspaceRequest.businessName, - qWorkspaceRequest.businessType, + qWorkspaceRequest.businessType.name, + qWorkspaceRequest.businessTypeDetail, qWorkspaceRequest.contact, qWorkspaceRequest.fullAddress, qWorkspaceRequest.latitude, @@ -149,7 +150,8 @@ public WorkspaceRequestResponse getWorkspaceRequest(Long workspaceRequestId) { qWorkspaceRequest.id, qWorkspaceRequest.businessRegistrationNo, qWorkspaceRequest.businessName, - qWorkspaceRequest.businessType, + qWorkspaceRequest.businessType.name, + qWorkspaceRequest.businessTypeDetail, qWorkspaceRequest.contact, qWorkspaceRequest.fullAddress, qWorkspaceRequest.latitude, diff --git a/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/readonly/ManagerWorkspaceListResponse.java b/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/readonly/ManagerWorkspaceListResponse.java index 5f0ac5db..eee450d5 100644 --- a/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/readonly/ManagerWorkspaceListResponse.java +++ b/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/readonly/ManagerWorkspaceListResponse.java @@ -18,6 +18,8 @@ public class ManagerWorkspaceListResponse { private String businessType; + private String businessTypeDetail; + private String fullAddress; private LocalDateTime createdAt; diff --git a/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/readonly/ManagerWorkspaceResponse.java b/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/readonly/ManagerWorkspaceResponse.java index 66b66119..bab9c3bd 100644 --- a/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/readonly/ManagerWorkspaceResponse.java +++ b/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/readonly/ManagerWorkspaceResponse.java @@ -24,6 +24,8 @@ public class ManagerWorkspaceResponse { private String businessType; + private String businessTypeDetail; + private String contact; private String description; diff --git a/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/readonly/WorkspaceRequestResponse.java b/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/readonly/WorkspaceRequestResponse.java index 28af8488..c8005bc6 100644 --- a/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/readonly/WorkspaceRequestResponse.java +++ b/src/main/java/com/dreamteam/alter/adapter/outbound/workspace/persistence/readonly/WorkspaceRequestResponse.java @@ -18,6 +18,7 @@ public class WorkspaceRequestResponse { private String businessRegistrationNo; private String businessName; private String businessType; + private String businessTypeDetail; private String contact; private String fullAddress; private BigDecimal latitude; diff --git a/src/main/java/com/dreamteam/alter/application/posting/usecase/CreatePosting.java b/src/main/java/com/dreamteam/alter/application/posting/usecase/CreatePosting.java index d299b006..c477d58d 100644 --- a/src/main/java/com/dreamteam/alter/application/posting/usecase/CreatePosting.java +++ b/src/main/java/com/dreamteam/alter/application/posting/usecase/CreatePosting.java @@ -1,7 +1,5 @@ package com.dreamteam.alter.application.posting.usecase; -import java.util.List; - import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -9,9 +7,7 @@ import com.dreamteam.alter.common.exception.CustomException; import com.dreamteam.alter.common.exception.ErrorCode; import com.dreamteam.alter.domain.posting.entity.Posting; -import com.dreamteam.alter.domain.posting.entity.PostingKeyword; import com.dreamteam.alter.domain.posting.port.inbound.CreatePostingUseCase; -import com.dreamteam.alter.domain.posting.port.outbound.PostingKeywordQueryRepository; import com.dreamteam.alter.domain.posting.port.outbound.PostingRepository; import com.dreamteam.alter.domain.workspace.entity.Workspace; import com.dreamteam.alter.domain.workspace.port.outbound.WorkspaceQueryRepository; @@ -24,20 +20,14 @@ public class CreatePosting implements CreatePostingUseCase { private final PostingRepository postingRepository; - private final PostingKeywordQueryRepository postingKeywordQueryRepository; private final WorkspaceQueryRepository workspaceQueryRepository; @Override public void execute(CreatePostingRequestDto request) { - List postingKeywords = postingKeywordQueryRepository.findByIds(request.getKeywords()); - if (postingKeywords.size() != request.getKeywords().size()) { - throw new CustomException(ErrorCode.INVALID_KEYWORD); - } - Workspace workspace = workspaceQueryRepository.findById(request.getWorkspaceId()) .orElseThrow(() -> new CustomException(ErrorCode.WORKSPACE_NOT_FOUND)); - Posting posting = Posting.create(request, workspace, postingKeywords); + Posting posting = Posting.create(request, workspace); postingRepository.save(posting); } diff --git a/src/main/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplication.java b/src/main/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplication.java index b625da98..14246ad1 100644 --- a/src/main/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplication.java +++ b/src/main/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplication.java @@ -40,7 +40,7 @@ public void execute(AppActor actor, Long postingId, CreatePostingApplicationRequ .orElseThrow(() -> new CustomException(ErrorCode.POSTING_SCHEDULE_NOT_FOUND)); Posting posting = postingSchedule.getPosting(); - if (PostingStatus.OPEN.equals(posting.getStatus())) { + if (!PostingStatus.OPEN.equals(posting.getStatus())) { throw new CustomException(ErrorCode.ILLEGAL_ARGUMENT, "모집이 종료된 공고입니다."); } diff --git a/src/main/java/com/dreamteam/alter/application/posting/usecase/GetPostingFilterOptions.java b/src/main/java/com/dreamteam/alter/application/posting/usecase/GetPostingFilterOptions.java index 6ccfbc10..b2ea477e 100644 --- a/src/main/java/com/dreamteam/alter/application/posting/usecase/GetPostingFilterOptions.java +++ b/src/main/java/com/dreamteam/alter/application/posting/usecase/GetPostingFilterOptions.java @@ -5,6 +5,7 @@ import com.dreamteam.alter.domain.posting.port.inbound.GetPostingFilterOptionsUseCase; import com.dreamteam.alter.domain.posting.port.outbound.PostingQueryRepository; import com.dreamteam.alter.domain.posting.type.PostingSortType; +import com.dreamteam.alter.domain.workspace.port.outbound.BusinessTypeQueryRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,6 +18,7 @@ public class GetPostingFilterOptions implements GetPostingFilterOptionsUseCase { private final PostingQueryRepository postingQueryRepository; + private final BusinessTypeQueryRepository businessTypeQueryRepository; @Override public PostingFilterOptionsResponseDto execute() { @@ -26,7 +28,8 @@ public PostingFilterOptionsResponseDto execute() { filterOptions.getProvinces(), filterOptions.getDistricts(), filterOptions.getTowns(), - Arrays.asList(PostingSortType.values()) + Arrays.asList(PostingSortType.values()), + businessTypeQueryRepository.findAll() ); } } diff --git a/src/main/java/com/dreamteam/alter/application/posting/usecase/GetPostingKeywordList.java b/src/main/java/com/dreamteam/alter/application/posting/usecase/GetPostingKeywordList.java deleted file mode 100644 index bacd35f2..00000000 --- a/src/main/java/com/dreamteam/alter/application/posting/usecase/GetPostingKeywordList.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.dreamteam.alter.application.posting.usecase; - -import com.dreamteam.alter.adapter.inbound.general.posting.dto.PostingKeywordListResponseDto; -import com.dreamteam.alter.common.exception.CustomException; -import com.dreamteam.alter.common.exception.ErrorCode; -import com.dreamteam.alter.domain.posting.entity.PostingKeyword; -import com.dreamteam.alter.domain.posting.port.inbound.GetPostingKeywordListUseCase; -import com.dreamteam.alter.domain.posting.port.outbound.PostingKeywordQueryRepository; -import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.ObjectUtils; -import org.springframework.stereotype.Service; - -import java.util.List; - -@Service("getPostingKeywordList") -@RequiredArgsConstructor -public class GetPostingKeywordList implements GetPostingKeywordListUseCase { - - private final PostingKeywordQueryRepository postingKeywordQueryRepository; - - @Override - public List execute() { - List postingKeywords = postingKeywordQueryRepository.findAll(); - - if (ObjectUtils.isEmpty(postingKeywords)) { - throw new CustomException(ErrorCode.POSTING_KEYWORDS_NOT_FOUND); - } - - return postingKeywords.stream() - .map(PostingKeywordListResponseDto::from) - .toList(); - } - -} diff --git a/src/main/java/com/dreamteam/alter/application/posting/usecase/ManagerUpdatePosting.java b/src/main/java/com/dreamteam/alter/application/posting/usecase/ManagerUpdatePosting.java index 2cbf2d6f..fcc24cee 100644 --- a/src/main/java/com/dreamteam/alter/application/posting/usecase/ManagerUpdatePosting.java +++ b/src/main/java/com/dreamteam/alter/application/posting/usecase/ManagerUpdatePosting.java @@ -1,7 +1,5 @@ package com.dreamteam.alter.application.posting.usecase; -import java.util.List; - import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -9,9 +7,7 @@ import com.dreamteam.alter.common.exception.CustomException; import com.dreamteam.alter.common.exception.ErrorCode; import com.dreamteam.alter.domain.posting.entity.Posting; -import com.dreamteam.alter.domain.posting.entity.PostingKeyword; import com.dreamteam.alter.domain.posting.port.inbound.ManagerUpdatePostingUseCase; -import com.dreamteam.alter.domain.posting.port.outbound.PostingKeywordQueryRepository; import com.dreamteam.alter.domain.posting.port.outbound.PostingQueryRepository; import com.dreamteam.alter.domain.user.context.ManagerActor; import com.dreamteam.alter.domain.user.entity.ManagerUser; @@ -24,7 +20,6 @@ public class ManagerUpdatePosting implements ManagerUpdatePostingUseCase { private final PostingQueryRepository postingQueryRepository; - private final PostingKeywordQueryRepository postingKeywordQueryRepository; @Override public void execute(Long postingId, UpdatePostingRequestDto request, ManagerActor actor) { @@ -33,17 +28,11 @@ public void execute(Long postingId, UpdatePostingRequestDto request, ManagerActo Posting posting = postingQueryRepository.findByManagerAndId(postingId, managerUser) .orElseThrow(() -> new CustomException(ErrorCode.POSTING_NOT_FOUND)); - List postingKeywords = postingKeywordQueryRepository.findByIds(request.getKeywords()); - if (postingKeywords.size() != request.getKeywords().size()) { - throw new CustomException(ErrorCode.INVALID_KEYWORD); - } - posting.updateContent( request.getTitle(), request.getDescription(), request.getPayAmount(), request.getPaymentType(), - postingKeywords, request.getCreateSchedules(), request.getUpdateSchedules(), request.getDeleteScheduleIds() diff --git a/src/main/java/com/dreamteam/alter/application/workspace/usecase/AdminCreateBusinessType.java b/src/main/java/com/dreamteam/alter/application/workspace/usecase/AdminCreateBusinessType.java new file mode 100644 index 00000000..ab6df940 --- /dev/null +++ b/src/main/java/com/dreamteam/alter/application/workspace/usecase/AdminCreateBusinessType.java @@ -0,0 +1,31 @@ +package com.dreamteam.alter.application.workspace.usecase; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.dreamteam.alter.common.exception.CustomException; +import com.dreamteam.alter.common.exception.ErrorCode; +import com.dreamteam.alter.domain.workspace.command.AdminCreateBusinessTypeCommand; +import com.dreamteam.alter.domain.workspace.entity.BusinessType; +import com.dreamteam.alter.domain.workspace.port.inbound.AdminCreateBusinessTypeUseCase; +import com.dreamteam.alter.domain.workspace.port.outbound.BusinessTypeRepository; + +import lombok.RequiredArgsConstructor; + +@Service("adminCreateBusinessType") +@RequiredArgsConstructor +@Transactional +public class AdminCreateBusinessType implements AdminCreateBusinessTypeUseCase { + + private final BusinessTypeRepository businessTypeRepository; + + @Override + public Long execute(AdminCreateBusinessTypeCommand command) { + if (businessTypeRepository.existsByName(command.name())) { + throw new CustomException(ErrorCode.CONFLICT, "이미 존재하는 업종입니다."); + } + + BusinessType businessType = BusinessType.create(command.name(), command.description()); + return businessTypeRepository.save(businessType).getId(); + } +} diff --git a/src/main/java/com/dreamteam/alter/application/workspace/usecase/AdminDeleteBusinessType.java b/src/main/java/com/dreamteam/alter/application/workspace/usecase/AdminDeleteBusinessType.java new file mode 100644 index 00000000..b06a4070 --- /dev/null +++ b/src/main/java/com/dreamteam/alter/application/workspace/usecase/AdminDeleteBusinessType.java @@ -0,0 +1,39 @@ +package com.dreamteam.alter.application.workspace.usecase; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.dreamteam.alter.common.exception.CustomException; +import com.dreamteam.alter.common.exception.ErrorCode; +import com.dreamteam.alter.domain.workspace.entity.BusinessType; +import com.dreamteam.alter.domain.workspace.port.inbound.AdminDeleteBusinessTypeUseCase; +import com.dreamteam.alter.domain.workspace.port.outbound.BusinessTypeQueryRepository; +import com.dreamteam.alter.domain.workspace.port.outbound.BusinessTypeRepository; + +import lombok.RequiredArgsConstructor; + +@Service("adminDeleteBusinessType") +@RequiredArgsConstructor +@Transactional +public class AdminDeleteBusinessType implements AdminDeleteBusinessTypeUseCase { + + private final BusinessTypeRepository businessTypeRepository; + private final BusinessTypeQueryRepository businessTypeQueryRepository; + + @Override + public void execute(Long id) { + BusinessType businessType = businessTypeRepository.findById(id) + .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND, "존재하지 않는 업종입니다.")); + + if (businessType.isRequiresDetail()) { + throw new CustomException(ErrorCode.CONFLICT, "'기타' 업종은 삭제할 수 없습니다."); + } + + if (businessTypeQueryRepository.existsWorkspaceUsingBusinessType(id) + || businessTypeQueryRepository.existsWorkspaceRequestUsingBusinessType(id)) { + throw new CustomException(ErrorCode.CONFLICT, "사용 중인 업종은 삭제할 수 없습니다."); + } + + businessTypeRepository.delete(businessType); + } +} diff --git a/src/main/java/com/dreamteam/alter/application/workspace/usecase/AdminGetBusinessTypeList.java b/src/main/java/com/dreamteam/alter/application/workspace/usecase/AdminGetBusinessTypeList.java new file mode 100644 index 00000000..dd8983dd --- /dev/null +++ b/src/main/java/com/dreamteam/alter/application/workspace/usecase/AdminGetBusinessTypeList.java @@ -0,0 +1,25 @@ +package com.dreamteam.alter.application.workspace.usecase; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.dreamteam.alter.domain.workspace.entity.BusinessType; +import com.dreamteam.alter.domain.workspace.port.inbound.AdminGetBusinessTypeListUseCase; +import com.dreamteam.alter.domain.workspace.port.outbound.BusinessTypeQueryRepository; + +import lombok.RequiredArgsConstructor; + +@Service("adminGetBusinessTypeList") +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class AdminGetBusinessTypeList implements AdminGetBusinessTypeListUseCase { + + private final BusinessTypeQueryRepository businessTypeQueryRepository; + + @Override + public List execute() { + return businessTypeQueryRepository.findAll(); + } +} diff --git a/src/main/java/com/dreamteam/alter/application/workspace/usecase/AdminUpdateBusinessType.java b/src/main/java/com/dreamteam/alter/application/workspace/usecase/AdminUpdateBusinessType.java new file mode 100644 index 00000000..f94b6673 --- /dev/null +++ b/src/main/java/com/dreamteam/alter/application/workspace/usecase/AdminUpdateBusinessType.java @@ -0,0 +1,33 @@ +package com.dreamteam.alter.application.workspace.usecase; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.dreamteam.alter.common.exception.CustomException; +import com.dreamteam.alter.common.exception.ErrorCode; +import com.dreamteam.alter.domain.workspace.command.AdminUpdateBusinessTypeCommand; +import com.dreamteam.alter.domain.workspace.entity.BusinessType; +import com.dreamteam.alter.domain.workspace.port.inbound.AdminUpdateBusinessTypeUseCase; +import com.dreamteam.alter.domain.workspace.port.outbound.BusinessTypeRepository; + +import lombok.RequiredArgsConstructor; + +@Service("adminUpdateBusinessType") +@RequiredArgsConstructor +@Transactional +public class AdminUpdateBusinessType implements AdminUpdateBusinessTypeUseCase { + + private final BusinessTypeRepository businessTypeRepository; + + @Override + public void execute(Long id, AdminUpdateBusinessTypeCommand command) { + BusinessType businessType = businessTypeRepository.findById(id) + .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND, "존재하지 않는 업종입니다.")); + + if (businessTypeRepository.existsByNameAndIdNot(command.name(), id)) { + throw new CustomException(ErrorCode.CONFLICT, "이미 존재하는 업종입니다."); + } + + businessType.update(command.name(), command.description()); + } +} diff --git a/src/main/java/com/dreamteam/alter/application/workspace/usecase/CreateWorkspaceRequest.java b/src/main/java/com/dreamteam/alter/application/workspace/usecase/CreateWorkspaceRequest.java index 8b741867..a8fbf4a4 100644 --- a/src/main/java/com/dreamteam/alter/application/workspace/usecase/CreateWorkspaceRequest.java +++ b/src/main/java/com/dreamteam/alter/application/workspace/usecase/CreateWorkspaceRequest.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Map; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -14,9 +15,11 @@ import com.dreamteam.alter.domain.file.port.inbound.AttachFilesUseCase; import com.dreamteam.alter.domain.file.type.FileTargetType; import com.dreamteam.alter.domain.user.entity.User; +import com.dreamteam.alter.domain.workspace.entity.BusinessType; import com.dreamteam.alter.domain.workspace.entity.WorkspaceRequest; import com.dreamteam.alter.domain.workspace.entity.WorkspaceRequestImage; import com.dreamteam.alter.domain.workspace.port.inbound.CreateWorkspaceRequestUseCase; +import com.dreamteam.alter.domain.workspace.port.outbound.BusinessTypeRepository; import com.dreamteam.alter.domain.workspace.port.outbound.WorkspaceRequestImageRepository; import com.dreamteam.alter.domain.workspace.port.outbound.WorkspaceRequestRepository; @@ -31,15 +34,21 @@ public class CreateWorkspaceRequest implements CreateWorkspaceRequestUseCase { private final WorkspaceRequestRepository workspaceRequestRepository; private final WorkspaceRequestImageRepository workspaceRequestImageRepository; + private final BusinessTypeRepository businessTypeRepository; private final AttachFilesUseCase attachFiles; @Override public void execute(User user, CreateWorkspaceRequestDto request) { + BusinessType businessType = businessTypeRepository.findById(request.getBusinessTypeId()) + .orElseThrow(() -> new CustomException(ErrorCode.ILLEGAL_ARGUMENT, "존재하지 않는 업종입니다.")); + String businessTypeDetail = resolveBusinessTypeDetail(businessType, request.getBusinessTypeDetail()); + WorkspaceRequest workspaceRequest = WorkspaceRequest.create( user, request.getBrn(), request.getBizName(), - request.getType(), + businessType, + businessTypeDetail, request.getContact(), request.getAddress(), request.getProvince(), @@ -78,4 +87,15 @@ public void execute(User user, CreateWorkspaceRequestDto request) { workspaceRequestImageRepository.saveAll(images); } } + + // '기타' 업종만 상세 입력을 요구·저장하고, 그 외 업종의 상세 입력은 무시한다. + private String resolveBusinessTypeDetail(BusinessType businessType, String rawDetail) { + if (!businessType.isRequiresDetail()) { + return null; + } + if (StringUtils.isBlank(rawDetail)) { + throw new CustomException(ErrorCode.ILLEGAL_ARGUMENT, "'기타' 업종은 상세 입력이 필요합니다."); + } + return StringUtils.trim(rawDetail); + } } diff --git a/src/main/java/com/dreamteam/alter/application/workspace/usecase/GetBusinessTypeList.java b/src/main/java/com/dreamteam/alter/application/workspace/usecase/GetBusinessTypeList.java new file mode 100644 index 00000000..fd4de3a6 --- /dev/null +++ b/src/main/java/com/dreamteam/alter/application/workspace/usecase/GetBusinessTypeList.java @@ -0,0 +1,25 @@ +package com.dreamteam.alter.application.workspace.usecase; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.dreamteam.alter.domain.workspace.entity.BusinessType; +import com.dreamteam.alter.domain.workspace.port.inbound.GetBusinessTypeListUseCase; +import com.dreamteam.alter.domain.workspace.port.outbound.BusinessTypeQueryRepository; + +import lombok.RequiredArgsConstructor; + +@Service("getBusinessTypeList") +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class GetBusinessTypeList implements GetBusinessTypeListUseCase { + + private final BusinessTypeQueryRepository businessTypeQueryRepository; + + @Override + public List execute() { + return businessTypeQueryRepository.findAll(); + } +} diff --git a/src/main/java/com/dreamteam/alter/application/workspace/usecase/UpdateWorkspaceRequestStatus.java b/src/main/java/com/dreamteam/alter/application/workspace/usecase/UpdateWorkspaceRequestStatus.java index e93f173c..3b879956 100644 --- a/src/main/java/com/dreamteam/alter/application/workspace/usecase/UpdateWorkspaceRequestStatus.java +++ b/src/main/java/com/dreamteam/alter/application/workspace/usecase/UpdateWorkspaceRequestStatus.java @@ -77,6 +77,7 @@ private void approve(WorkspaceRequest workspaceRequest, Long workspaceRequestId) workspaceRequest.getBusinessRegistrationNo(), workspaceRequest.getBusinessName(), workspaceRequest.getBusinessType(), + workspaceRequest.getBusinessTypeDetail(), workspaceRequest.getContact(), null, WorkspaceStatus.ACTIVATED, diff --git a/src/main/java/com/dreamteam/alter/domain/posting/entity/Posting.java b/src/main/java/com/dreamteam/alter/domain/posting/entity/Posting.java index 0f2bc054..62ab5471 100644 --- a/src/main/java/com/dreamteam/alter/domain/posting/entity/Posting.java +++ b/src/main/java/com/dreamteam/alter/domain/posting/entity/Posting.java @@ -67,10 +67,7 @@ public class Posting { @OneToMany(mappedBy = "posting", cascade = CascadeType.ALL, orphanRemoval = true) private List schedules; - @OneToMany(mappedBy = "posting", cascade = CascadeType.ALL, orphanRemoval = true) - private List keywords; - - public static Posting create(CreatePostingRequestDto request, Workspace workspace, List postingKeywords) { + public static Posting create(CreatePostingRequestDto request, Workspace workspace) { Posting posting = Posting.builder() .workspace(workspace) .title(request.getTitle()) @@ -80,11 +77,6 @@ public static Posting create(CreatePostingRequestDto request, Workspace workspac .status(PostingStatus.OPEN) .build(); - posting.keywords = postingKeywords - .stream() - .map(keyword -> PostingKeywordMap.create(keyword, posting)) - .toList(); - if (ObjectUtils.isNotEmpty(request.getSchedules())) { posting.schedules = request.getSchedules() .stream() @@ -111,7 +103,6 @@ public void updateContent( String description, int payAmount, PaymentType paymentType, - List postingKeywords, List createSchedules, List updateSchedules, List deleteScheduleIds @@ -121,9 +112,6 @@ public void updateContent( this.payAmount = payAmount; this.paymentType = paymentType; - // 키워드 업데이트 - updateKeyword(postingKeywords); - // 스케줄 삭제 처리 if (ObjectUtils.isNotEmpty(deleteScheduleIds)) deleteSchedules(deleteScheduleIds); @@ -137,19 +125,6 @@ public void updateContent( addSchedules(createSchedules); } - /** - * 키워드 수정 - * @param postingKeywords 수정 키워드 List - */ - public void updateKeyword(List postingKeywords) { - this.keywords.clear(); - - this.keywords.addAll(postingKeywords.stream() - .map(keywords -> PostingKeywordMap.create(keywords, this)) - .toList() - ); - } - /** * 스케줄 추가 * @param createSchedules 스케줄 추가 정보 List diff --git a/src/main/java/com/dreamteam/alter/domain/posting/entity/PostingKeywordMap.java b/src/main/java/com/dreamteam/alter/domain/posting/entity/PostingKeywordMap.java deleted file mode 100644 index a5ce18e3..00000000 --- a/src/main/java/com/dreamteam/alter/domain/posting/entity/PostingKeywordMap.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.dreamteam.alter.domain.posting.entity; - -import jakarta.persistence.*; -import lombok.*; - -@Entity -@Getter -@Table( - name = "posting_keyword_map", - uniqueConstraints = { - @UniqueConstraint( - name = "UK_posting_keyword_map", - columnNames = {"posting_id", "keyword_id"} - ) - } -) -@Builder(access = AccessLevel.PRIVATE) -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public class PostingKeywordMap { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id", nullable = false) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "posting_id", nullable = false) - private Posting posting; - - @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "keyword_id", nullable = false) - private PostingKeyword postingKeyword; - - public static PostingKeywordMap create(PostingKeyword postingKeyword, Posting posting) { - return PostingKeywordMap.builder() - .posting(posting) - .postingKeyword(postingKeyword) - .build(); - } - -} diff --git a/src/main/java/com/dreamteam/alter/domain/posting/port/inbound/GetPostingKeywordListUseCase.java b/src/main/java/com/dreamteam/alter/domain/posting/port/inbound/GetPostingKeywordListUseCase.java deleted file mode 100644 index c2cf163f..00000000 --- a/src/main/java/com/dreamteam/alter/domain/posting/port/inbound/GetPostingKeywordListUseCase.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.dreamteam.alter.domain.posting.port.inbound; - -import com.dreamteam.alter.adapter.inbound.general.posting.dto.PostingKeywordListResponseDto; - -import java.util.List; - -public interface GetPostingKeywordListUseCase { - List execute(); -} diff --git a/src/main/java/com/dreamteam/alter/domain/posting/port/outbound/PostingKeywordQueryRepository.java b/src/main/java/com/dreamteam/alter/domain/posting/port/outbound/PostingKeywordQueryRepository.java deleted file mode 100644 index 8cec6ec5..00000000 --- a/src/main/java/com/dreamteam/alter/domain/posting/port/outbound/PostingKeywordQueryRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.dreamteam.alter.domain.posting.port.outbound; - -import com.dreamteam.alter.domain.posting.entity.PostingKeyword; - -import java.util.List; - -public interface PostingKeywordQueryRepository { - List findByIds(List keywordIds); - List findAll(); -} diff --git a/src/main/java/com/dreamteam/alter/domain/workspace/command/AdminCreateBusinessTypeCommand.java b/src/main/java/com/dreamteam/alter/domain/workspace/command/AdminCreateBusinessTypeCommand.java new file mode 100644 index 00000000..a29f2910 --- /dev/null +++ b/src/main/java/com/dreamteam/alter/domain/workspace/command/AdminCreateBusinessTypeCommand.java @@ -0,0 +1,6 @@ +package com.dreamteam.alter.domain.workspace.command; + +public record AdminCreateBusinessTypeCommand( + String name, + String description +) {} diff --git a/src/main/java/com/dreamteam/alter/domain/workspace/command/AdminUpdateBusinessTypeCommand.java b/src/main/java/com/dreamteam/alter/domain/workspace/command/AdminUpdateBusinessTypeCommand.java new file mode 100644 index 00000000..6a082700 --- /dev/null +++ b/src/main/java/com/dreamteam/alter/domain/workspace/command/AdminUpdateBusinessTypeCommand.java @@ -0,0 +1,6 @@ +package com.dreamteam.alter.domain.workspace.command; + +public record AdminUpdateBusinessTypeCommand( + String name, + String description +) {} diff --git a/src/main/java/com/dreamteam/alter/domain/posting/entity/PostingKeyword.java b/src/main/java/com/dreamteam/alter/domain/workspace/entity/BusinessType.java similarity index 60% rename from src/main/java/com/dreamteam/alter/domain/posting/entity/PostingKeyword.java rename to src/main/java/com/dreamteam/alter/domain/workspace/entity/BusinessType.java index 7ebea4b2..2c4433b9 100644 --- a/src/main/java/com/dreamteam/alter/domain/posting/entity/PostingKeyword.java +++ b/src/main/java/com/dreamteam/alter/domain/workspace/entity/BusinessType.java @@ -1,4 +1,4 @@ -package com.dreamteam.alter.domain.posting.entity; +package com.dreamteam.alter.domain.workspace.entity; import jakarta.persistence.*; import lombok.*; @@ -10,24 +10,28 @@ @Entity @Getter -@Table(name = "posting_keywords") +@Table(name = "business_types") @Builder(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) @EntityListeners(AuditingEntityListener.class) -public class PostingKeyword { +public class BusinessType { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) private Long id; - @Column(name = "name", length = 128, nullable = false, unique = true) + @Column(name = "name", length = 64, nullable = false, unique = true) private String name; @Column(name = "description", length = 255, nullable = true) private String description; + // '기타' 등 상세 입력을 요구하는 업종 식별 플래그. 시드로만 생성되며 관리자 CRUD로는 항상 false. + @Column(name = "requires_detail", nullable = false) + private boolean requiresDetail; + @CreatedDate @Column(name = "created_at", nullable = false, updatable = false) private LocalDateTime createdAt; @@ -36,11 +40,17 @@ public class PostingKeyword { @Column(name = "updated_at", nullable = false) private LocalDateTime updatedAt; - public static PostingKeyword create(String name, String description) { - return PostingKeyword.builder() + public static BusinessType create(String name, String description) { + return BusinessType.builder() .name(name) .description(description) + .requiresDetail(false) .build(); } + public void update(String name, String description) { + this.name = name; + this.description = description; + } + } diff --git a/src/main/java/com/dreamteam/alter/domain/workspace/entity/Workspace.java b/src/main/java/com/dreamteam/alter/domain/workspace/entity/Workspace.java index fbf10a2d..bacdb48b 100644 --- a/src/main/java/com/dreamteam/alter/domain/workspace/entity/Workspace.java +++ b/src/main/java/com/dreamteam/alter/domain/workspace/entity/Workspace.java @@ -36,8 +36,12 @@ public class Workspace { @Column(name = "business_name", length = 128, nullable = false) private String businessName; - @Column(name = "business_type", length = 128, nullable = false) - private String businessType; // Enum 으로 정의 고려 + @JoinColumn(name = "business_type_id", nullable = false) + @ManyToOne(fetch = FetchType.LAZY) + private BusinessType businessType; + + @Column(name = "business_type_detail", length = 128, nullable = true) + private String businessTypeDetail; @Column(name = "contact", length = 11, nullable = false) private String contact; @@ -84,7 +88,8 @@ public static Workspace create( ManagerUser managerUser, String businessRegistrationNo, String businessName, - String businessType, + BusinessType businessType, + String businessTypeDetail, String contact, String description, WorkspaceStatus status, @@ -100,6 +105,7 @@ public static Workspace create( .businessRegistrationNo(businessRegistrationNo) .businessName(businessName) .businessType(businessType) + .businessTypeDetail(businessTypeDetail) .contact(contact) .description(description) .status(status) diff --git a/src/main/java/com/dreamteam/alter/domain/workspace/entity/WorkspaceRequest.java b/src/main/java/com/dreamteam/alter/domain/workspace/entity/WorkspaceRequest.java index 544cf671..6c091da2 100644 --- a/src/main/java/com/dreamteam/alter/domain/workspace/entity/WorkspaceRequest.java +++ b/src/main/java/com/dreamteam/alter/domain/workspace/entity/WorkspaceRequest.java @@ -57,8 +57,12 @@ public class WorkspaceRequest { @Column(name = "business_name", length = 128, nullable = false) private String businessName; - @Column(name = "business_type", length = 128, nullable = false) - private String businessType; + @JoinColumn(name = "business_type_id", nullable = false) + @ManyToOne(fetch = FetchType.LAZY) + private BusinessType businessType; + + @Column(name = "business_type_detail", length = 128, nullable = true) + private String businessTypeDetail; @Column(name = "contact", length = 13, nullable = false) private String contact; @@ -93,7 +97,8 @@ public static WorkspaceRequest create( User user, String brn, String bizName, - String type, + BusinessType businessType, + String businessTypeDetail, String contact, String address, String province, @@ -107,7 +112,8 @@ public static WorkspaceRequest create( .status(WorkspaceRequestStatus.PENDING) .businessRegistrationNo(brn) .businessName(bizName) - .businessType(type) + .businessType(businessType) + .businessTypeDetail(businessTypeDetail) .contact(contact) .fullAddress(address) .province(province) diff --git a/src/main/java/com/dreamteam/alter/domain/workspace/port/inbound/AdminCreateBusinessTypeUseCase.java b/src/main/java/com/dreamteam/alter/domain/workspace/port/inbound/AdminCreateBusinessTypeUseCase.java new file mode 100644 index 00000000..48a162ff --- /dev/null +++ b/src/main/java/com/dreamteam/alter/domain/workspace/port/inbound/AdminCreateBusinessTypeUseCase.java @@ -0,0 +1,7 @@ +package com.dreamteam.alter.domain.workspace.port.inbound; + +import com.dreamteam.alter.domain.workspace.command.AdminCreateBusinessTypeCommand; + +public interface AdminCreateBusinessTypeUseCase { + Long execute(AdminCreateBusinessTypeCommand command); +} diff --git a/src/main/java/com/dreamteam/alter/domain/workspace/port/inbound/AdminDeleteBusinessTypeUseCase.java b/src/main/java/com/dreamteam/alter/domain/workspace/port/inbound/AdminDeleteBusinessTypeUseCase.java new file mode 100644 index 00000000..0d236e01 --- /dev/null +++ b/src/main/java/com/dreamteam/alter/domain/workspace/port/inbound/AdminDeleteBusinessTypeUseCase.java @@ -0,0 +1,5 @@ +package com.dreamteam.alter.domain.workspace.port.inbound; + +public interface AdminDeleteBusinessTypeUseCase { + void execute(Long id); +} diff --git a/src/main/java/com/dreamteam/alter/domain/workspace/port/inbound/AdminGetBusinessTypeListUseCase.java b/src/main/java/com/dreamteam/alter/domain/workspace/port/inbound/AdminGetBusinessTypeListUseCase.java new file mode 100644 index 00000000..c2409f63 --- /dev/null +++ b/src/main/java/com/dreamteam/alter/domain/workspace/port/inbound/AdminGetBusinessTypeListUseCase.java @@ -0,0 +1,9 @@ +package com.dreamteam.alter.domain.workspace.port.inbound; + +import java.util.List; + +import com.dreamteam.alter.domain.workspace.entity.BusinessType; + +public interface AdminGetBusinessTypeListUseCase { + List execute(); +} diff --git a/src/main/java/com/dreamteam/alter/domain/workspace/port/inbound/AdminUpdateBusinessTypeUseCase.java b/src/main/java/com/dreamteam/alter/domain/workspace/port/inbound/AdminUpdateBusinessTypeUseCase.java new file mode 100644 index 00000000..5f8747de --- /dev/null +++ b/src/main/java/com/dreamteam/alter/domain/workspace/port/inbound/AdminUpdateBusinessTypeUseCase.java @@ -0,0 +1,7 @@ +package com.dreamteam.alter.domain.workspace.port.inbound; + +import com.dreamteam.alter.domain.workspace.command.AdminUpdateBusinessTypeCommand; + +public interface AdminUpdateBusinessTypeUseCase { + void execute(Long id, AdminUpdateBusinessTypeCommand command); +} diff --git a/src/main/java/com/dreamteam/alter/domain/workspace/port/inbound/GetBusinessTypeListUseCase.java b/src/main/java/com/dreamteam/alter/domain/workspace/port/inbound/GetBusinessTypeListUseCase.java new file mode 100644 index 00000000..20dbb608 --- /dev/null +++ b/src/main/java/com/dreamteam/alter/domain/workspace/port/inbound/GetBusinessTypeListUseCase.java @@ -0,0 +1,9 @@ +package com.dreamteam.alter.domain.workspace.port.inbound; + +import java.util.List; + +import com.dreamteam.alter.domain.workspace.entity.BusinessType; + +public interface GetBusinessTypeListUseCase { + List execute(); +} diff --git a/src/main/java/com/dreamteam/alter/domain/workspace/port/outbound/BusinessTypeQueryRepository.java b/src/main/java/com/dreamteam/alter/domain/workspace/port/outbound/BusinessTypeQueryRepository.java new file mode 100644 index 00000000..4daaeb36 --- /dev/null +++ b/src/main/java/com/dreamteam/alter/domain/workspace/port/outbound/BusinessTypeQueryRepository.java @@ -0,0 +1,13 @@ +package com.dreamteam.alter.domain.workspace.port.outbound; + +import java.util.List; + +import com.dreamteam.alter.domain.workspace.entity.BusinessType; + +public interface BusinessTypeQueryRepository { + List findAll(); + + boolean existsWorkspaceUsingBusinessType(Long businessTypeId); + + boolean existsWorkspaceRequestUsingBusinessType(Long businessTypeId); +} diff --git a/src/main/java/com/dreamteam/alter/domain/workspace/port/outbound/BusinessTypeRepository.java b/src/main/java/com/dreamteam/alter/domain/workspace/port/outbound/BusinessTypeRepository.java new file mode 100644 index 00000000..a627a8dd --- /dev/null +++ b/src/main/java/com/dreamteam/alter/domain/workspace/port/outbound/BusinessTypeRepository.java @@ -0,0 +1,17 @@ +package com.dreamteam.alter.domain.workspace.port.outbound; + +import java.util.Optional; + +import com.dreamteam.alter.domain.workspace.entity.BusinessType; + +public interface BusinessTypeRepository { + Optional findById(Long id); + + BusinessType save(BusinessType businessType); + + void delete(BusinessType businessType); + + boolean existsByName(String name); + + boolean existsByNameAndIdNot(String name, Long id); +} diff --git a/src/main/resources/db/migration/V4__create_business_types.sql b/src/main/resources/db/migration/V4__create_business_types.sql new file mode 100644 index 00000000..4cd41c10 --- /dev/null +++ b/src/main/resources/db/migration/V4__create_business_types.sql @@ -0,0 +1,26 @@ +-- 업종(BusinessType) 마스터 테이블 생성 및 기본 시드 +-- 업장(Workspace)의 업종을 free-text 대신 이 마스터 테이블 FK로 관리한다. +-- requires_detail = true 인 '기타' 는 시드로만 생성되며, 선택 시 상세 텍스트 입력을 요구한다. + +CREATE TABLE IF NOT EXISTS business_types ( + id bigserial PRIMARY KEY, + name varchar(64) NOT NULL UNIQUE, + description varchar(255), + requires_detail boolean NOT NULL DEFAULT false, + created_at timestamp NOT NULL, + updated_at timestamp NOT NULL +); + +-- now() 명시: Flyway 는 JPA auditing 을 우회하므로 NOT NULL 인 created_at/updated_at 을 직접 채운다. +INSERT INTO business_types (name, description, requires_detail, created_at, updated_at) +VALUES ('카페', NULL, false, now(), now()), + ('음식점', NULL, false, now(), now()), + ('고기집', NULL, false, now(), now()), + ('주점', NULL, false, now(), now()), + ('편의점', NULL, false, now(), now()), + ('베이커리', NULL, false, now(), now()), + ('패스트푸드', NULL, false, now(), now()), + ('분식', NULL, false, now(), now()), + ('판매/매장', NULL, false, now(), now()), + ('기타', '마스터에 없는 업종. 선택 시 상세 입력 필요', true, now(), now()) +ON CONFLICT (name) DO NOTHING; diff --git a/src/main/resources/db/migration/V5__migrate_workspace_business_type.sql b/src/main/resources/db/migration/V5__migrate_workspace_business_type.sql new file mode 100644 index 00000000..4cd29e0c --- /dev/null +++ b/src/main/resources/db/migration/V5__migrate_workspace_business_type.sql @@ -0,0 +1,47 @@ +-- 업장(workspaces)/업장 신청(workspace_requests)의 업종을 free-text 에서 business_types FK 로 이관한다. +-- 1) FK/상세 컬럼 추가(초기 nullable) → 2) 이름 정확 일치 백필 → 3) 미일치는 '기타' + 상세=원문 → 4) NOT NULL/FK → 5) 구 컬럼 제거 + +ALTER TABLE workspace_requests + ADD COLUMN business_type_id bigint, + ADD COLUMN business_type_detail varchar(128); + +ALTER TABLE workspaces + ADD COLUMN business_type_id bigint, + ADD COLUMN business_type_detail varchar(128); + +-- 이름 정확 일치 매핑 +UPDATE workspace_requests wr +SET business_type_id = bt.id +FROM business_types bt +WHERE btrim(wr.business_type) = bt.name; + +UPDATE workspaces w +SET business_type_id = bt.id +FROM business_types bt +WHERE btrim(w.business_type) = bt.name; + +-- 미일치분은 '기타'(requires_detail) FK 로 매핑하고 원문을 상세에 보존한다. +UPDATE workspace_requests +SET business_type_id = (SELECT id FROM business_types WHERE requires_detail LIMIT 1), + business_type_detail = btrim(business_type) +WHERE business_type_id IS NULL; + +UPDATE workspaces +SET business_type_id = (SELECT id FROM business_types WHERE requires_detail LIMIT 1), + business_type_detail = btrim(business_type) +WHERE business_type_id IS NULL; + +-- NOT NULL 제약 및 FK 설정 +ALTER TABLE workspace_requests + ALTER COLUMN business_type_id SET NOT NULL, + ADD CONSTRAINT fk_workspace_requests_business_type + FOREIGN KEY (business_type_id) REFERENCES business_types (id); + +ALTER TABLE workspaces + ALTER COLUMN business_type_id SET NOT NULL, + ADD CONSTRAINT fk_workspaces_business_type + FOREIGN KEY (business_type_id) REFERENCES business_types (id); + +-- 구 free-text 컬럼 제거 (미일치분은 '기타' 상세로 보존됨) +ALTER TABLE workspace_requests DROP COLUMN business_type; +ALTER TABLE workspaces DROP COLUMN business_type; diff --git a/src/main/resources/db/migration/V6__drop_posting_keyword_tables.sql b/src/main/resources/db/migration/V6__drop_posting_keyword_tables.sql new file mode 100644 index 00000000..4952894e --- /dev/null +++ b/src/main/resources/db/migration/V6__drop_posting_keyword_tables.sql @@ -0,0 +1,6 @@ +-- PostingKeyword 폐기: 업종(業種)은 이제 업장(Workspace)의 business_type FK 로 관리한다. +-- ⚠️ 파괴적: posting_keywords / posting_keyword_map 의 데이터가 영구 삭제된다. +-- FK 순서상 join 테이블(posting_keyword_map)을 먼저 제거한다. + +DROP TABLE IF EXISTS posting_keyword_map; +DROP TABLE IF EXISTS posting_keywords; diff --git a/src/test/java/com/dreamteam/alter/application/posting/usecase/GetPostingFilterOptionsTests.java b/src/test/java/com/dreamteam/alter/application/posting/usecase/GetPostingFilterOptionsTests.java new file mode 100644 index 00000000..b2eafe96 --- /dev/null +++ b/src/test/java/com/dreamteam/alter/application/posting/usecase/GetPostingFilterOptionsTests.java @@ -0,0 +1,49 @@ +package com.dreamteam.alter.application.posting.usecase; + +import com.dreamteam.alter.adapter.inbound.general.posting.dto.PostingFilterOptionsResponseDto; +import com.dreamteam.alter.adapter.outbound.posting.persistence.readonly.PostingFilterOptionsResponse; +import com.dreamteam.alter.domain.posting.port.outbound.PostingQueryRepository; +import com.dreamteam.alter.domain.workspace.entity.BusinessType; +import com.dreamteam.alter.domain.workspace.port.outbound.BusinessTypeQueryRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +@DisplayName("GetPostingFilterOptions 테스트") +class GetPostingFilterOptionsTests { + + @Mock + private PostingQueryRepository postingQueryRepository; + + @Mock + private BusinessTypeQueryRepository businessTypeQueryRepository; + + @InjectMocks + private GetPostingFilterOptions getPostingFilterOptions; + + @Test + @DisplayName("필터 옵션에 지역/정렬과 함께 업종 목록이 포함된다") + void execute_업종포함() { + // given + given(postingQueryRepository.getPostingFilterOptions()) + .willReturn(PostingFilterOptionsResponse.of(List.of("서울특별시"), List.of("강남구"), List.of("역삼동"))); + given(businessTypeQueryRepository.findAll()) + .willReturn(List.of(BusinessType.create("카페", null), BusinessType.create("음식점", null))); + + // when + PostingFilterOptionsResponseDto result = getPostingFilterOptions.execute(); + + // then + assertThat(result.getProvinces()).containsExactly("서울특별시"); + assertThat(result.getBusinessTypes()).extracting("name").containsExactly("카페", "음식점"); + } +} diff --git a/src/test/java/com/dreamteam/alter/application/workspace/usecase/AdminCreateBusinessTypeTests.java b/src/test/java/com/dreamteam/alter/application/workspace/usecase/AdminCreateBusinessTypeTests.java new file mode 100644 index 00000000..ca64aab1 --- /dev/null +++ b/src/test/java/com/dreamteam/alter/application/workspace/usecase/AdminCreateBusinessTypeTests.java @@ -0,0 +1,66 @@ +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.workspace.command.AdminCreateBusinessTypeCommand; +import com.dreamteam.alter.domain.workspace.entity.BusinessType; +import com.dreamteam.alter.domain.workspace.port.outbound.BusinessTypeRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; + +@ExtendWith(MockitoExtension.class) +@DisplayName("AdminCreateBusinessType 테스트") +class AdminCreateBusinessTypeTests { + + @Mock + private BusinessTypeRepository businessTypeRepository; + + @InjectMocks + private AdminCreateBusinessType adminCreateBusinessType; + + @Test + @DisplayName("이름이 중복되면 CONFLICT 예외가 발생하고 저장하지 않는다") + void execute_이름중복_예외() { + // given + given(businessTypeRepository.existsByName("카페")).willReturn(true); + + // when & then + assertThatThrownBy(() -> adminCreateBusinessType.execute(new AdminCreateBusinessTypeCommand("카페", null))) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()).isEqualTo(ErrorCode.CONFLICT)); + then(businessTypeRepository).should(never()).save(any()); + } + + @Test + @DisplayName("중복이 없으면 업종을 저장하고 id 를 반환한다") + void execute_성공() { + // given + given(businessTypeRepository.existsByName("고기집")).willReturn(false); + BusinessType saved = mock(BusinessType.class); + given(saved.getId()).willReturn(7L); + given(businessTypeRepository.save(any(BusinessType.class))).willReturn(saved); + + // when + Long id = adminCreateBusinessType.execute(new AdminCreateBusinessTypeCommand("고기집", "고기 전문점")); + + // then + assertThat(id).isEqualTo(7L); + ArgumentCaptor captor = ArgumentCaptor.forClass(BusinessType.class); + then(businessTypeRepository).should().save(captor.capture()); + assertThat(captor.getValue().getName()).isEqualTo("고기집"); + assertThat(captor.getValue().isRequiresDetail()).isFalse(); + } +} diff --git a/src/test/java/com/dreamteam/alter/application/workspace/usecase/AdminDeleteBusinessTypeTests.java b/src/test/java/com/dreamteam/alter/application/workspace/usecase/AdminDeleteBusinessTypeTests.java new file mode 100644 index 00000000..89cd6af8 --- /dev/null +++ b/src/test/java/com/dreamteam/alter/application/workspace/usecase/AdminDeleteBusinessTypeTests.java @@ -0,0 +1,115 @@ +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.workspace.entity.BusinessType; +import com.dreamteam.alter.domain.workspace.port.outbound.BusinessTypeQueryRepository; +import com.dreamteam.alter.domain.workspace.port.outbound.BusinessTypeRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; + +@ExtendWith(MockitoExtension.class) +@DisplayName("AdminDeleteBusinessType 테스트") +class AdminDeleteBusinessTypeTests { + + @Mock + private BusinessTypeRepository businessTypeRepository; + + @Mock + private BusinessTypeQueryRepository businessTypeQueryRepository; + + @InjectMocks + private AdminDeleteBusinessType adminDeleteBusinessType; + + @Test + @DisplayName("존재하지 않는 업종이면 NOT_FOUND 예외가 발생한다") + void execute_미존재_예외() { + // given + given(businessTypeRepository.findById(1L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> adminDeleteBusinessType.execute(1L)) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()).isEqualTo(ErrorCode.NOT_FOUND)); + then(businessTypeRepository).should(never()).delete(any()); + } + + @Test + @DisplayName("'기타'(requiresDetail) 업종은 삭제할 수 없어 CONFLICT 예외가 발생한다") + void execute_기타_예외() { + // given + BusinessType etc = mock(BusinessType.class); + given(etc.isRequiresDetail()).willReturn(true); + given(businessTypeRepository.findById(1L)).willReturn(Optional.of(etc)); + + // when & then + assertThatThrownBy(() -> adminDeleteBusinessType.execute(1L)) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()).isEqualTo(ErrorCode.CONFLICT)); + then(businessTypeRepository).should(never()).delete(any()); + } + + @Test + @DisplayName("업장에서 사용 중이면 CONFLICT 예외가 발생한다") + void execute_업장사용중_예외() { + // given + BusinessType businessType = mock(BusinessType.class); + given(businessType.isRequiresDetail()).willReturn(false); + given(businessTypeRepository.findById(1L)).willReturn(Optional.of(businessType)); + given(businessTypeQueryRepository.existsWorkspaceUsingBusinessType(1L)).willReturn(true); + + // when & then + assertThatThrownBy(() -> adminDeleteBusinessType.execute(1L)) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()).isEqualTo(ErrorCode.CONFLICT)); + then(businessTypeRepository).should(never()).delete(any()); + } + + @Test + @DisplayName("업장 신청에서 사용 중이면 CONFLICT 예외가 발생한다") + void execute_신청사용중_예외() { + // given + BusinessType businessType = mock(BusinessType.class); + given(businessType.isRequiresDetail()).willReturn(false); + given(businessTypeRepository.findById(1L)).willReturn(Optional.of(businessType)); + given(businessTypeQueryRepository.existsWorkspaceUsingBusinessType(1L)).willReturn(false); + given(businessTypeQueryRepository.existsWorkspaceRequestUsingBusinessType(1L)).willReturn(true); + + // when & then + assertThatThrownBy(() -> adminDeleteBusinessType.execute(1L)) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()).isEqualTo(ErrorCode.CONFLICT)); + then(businessTypeRepository).should(never()).delete(any()); + } + + @Test + @DisplayName("사용 중이 아닌 일반 업종은 삭제된다") + void execute_성공() { + // given + BusinessType businessType = mock(BusinessType.class); + given(businessType.isRequiresDetail()).willReturn(false); + given(businessTypeRepository.findById(1L)).willReturn(Optional.of(businessType)); + given(businessTypeQueryRepository.existsWorkspaceUsingBusinessType(1L)).willReturn(false); + given(businessTypeQueryRepository.existsWorkspaceRequestUsingBusinessType(1L)).willReturn(false); + + // when + adminDeleteBusinessType.execute(1L); + + // then + then(businessTypeRepository).should().delete(businessType); + } +} diff --git a/src/test/java/com/dreamteam/alter/application/workspace/usecase/AdminUpdateBusinessTypeTests.java b/src/test/java/com/dreamteam/alter/application/workspace/usecase/AdminUpdateBusinessTypeTests.java new file mode 100644 index 00000000..dc8a06de --- /dev/null +++ b/src/test/java/com/dreamteam/alter/application/workspace/usecase/AdminUpdateBusinessTypeTests.java @@ -0,0 +1,76 @@ +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.workspace.command.AdminUpdateBusinessTypeCommand; +import com.dreamteam.alter.domain.workspace.entity.BusinessType; +import com.dreamteam.alter.domain.workspace.port.outbound.BusinessTypeRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; + +@ExtendWith(MockitoExtension.class) +@DisplayName("AdminUpdateBusinessType 테스트") +class AdminUpdateBusinessTypeTests { + + @Mock + private BusinessTypeRepository businessTypeRepository; + + @InjectMocks + private AdminUpdateBusinessType adminUpdateBusinessType; + + @Test + @DisplayName("존재하지 않는 업종이면 NOT_FOUND 예외가 발생한다") + void execute_미존재_예외() { + // given + given(businessTypeRepository.findById(1L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> adminUpdateBusinessType.execute(1L, new AdminUpdateBusinessTypeCommand("카페", null))) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()).isEqualTo(ErrorCode.NOT_FOUND)); + } + + @Test + @DisplayName("자신을 제외한 다른 업종과 이름이 중복되면 CONFLICT 예외가 발생한다") + void execute_이름중복_예외() { + // given + BusinessType businessType = mock(BusinessType.class); + given(businessTypeRepository.findById(1L)).willReturn(Optional.of(businessType)); + given(businessTypeRepository.existsByNameAndIdNot("카페", 1L)).willReturn(true); + + // when & then + assertThatThrownBy(() -> adminUpdateBusinessType.execute(1L, new AdminUpdateBusinessTypeCommand("카페", null))) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()).isEqualTo(ErrorCode.CONFLICT)); + then(businessType).should(never()).update(any(), any()); + } + + @Test + @DisplayName("중복이 없으면 이름/설명을 수정한다") + void execute_성공() { + // given + BusinessType businessType = mock(BusinessType.class); + given(businessTypeRepository.findById(1L)).willReturn(Optional.of(businessType)); + given(businessTypeRepository.existsByNameAndIdNot("카페", 1L)).willReturn(false); + + // when + adminUpdateBusinessType.execute(1L, new AdminUpdateBusinessTypeCommand("카페", "카페/디저트")); + + // then + then(businessType).should().update("카페", "카페/디저트"); + } +} diff --git a/src/test/java/com/dreamteam/alter/application/workspace/usecase/CreateWorkspaceRequestTests.java b/src/test/java/com/dreamteam/alter/application/workspace/usecase/CreateWorkspaceRequestTests.java index cc68513c..3467a2a0 100644 --- a/src/test/java/com/dreamteam/alter/application/workspace/usecase/CreateWorkspaceRequestTests.java +++ b/src/test/java/com/dreamteam/alter/application/workspace/usecase/CreateWorkspaceRequestTests.java @@ -7,7 +7,10 @@ import com.dreamteam.alter.domain.file.port.inbound.AttachFilesUseCase; import com.dreamteam.alter.domain.file.type.FileTargetType; import com.dreamteam.alter.domain.user.entity.User; +import com.dreamteam.alter.domain.workspace.entity.BusinessType; +import com.dreamteam.alter.domain.workspace.entity.WorkspaceRequest; import com.dreamteam.alter.domain.workspace.entity.WorkspaceRequestImage; +import com.dreamteam.alter.domain.workspace.port.outbound.BusinessTypeRepository; import com.dreamteam.alter.domain.workspace.port.outbound.WorkspaceRequestImageRepository; import com.dreamteam.alter.domain.workspace.port.outbound.WorkspaceRequestRepository; import org.junit.jupiter.api.DisplayName; @@ -21,6 +24,7 @@ import java.util.LinkedHashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; @@ -42,6 +46,9 @@ class CreateWorkspaceRequestTests { @Mock private WorkspaceRequestImageRepository workspaceRequestImageRepository; + @Mock + private BusinessTypeRepository businessTypeRepository; + @Mock private AttachFilesUseCase attachFiles; @@ -54,6 +61,9 @@ class CreateWorkspaceRequestTests { @Captor private ArgumentCaptor> imagesCaptor; + @Captor + private ArgumentCaptor workspaceRequestCaptor; + private CreateWorkspaceRequestDto baseRequest() { CreateWorkspaceRequestDto dto = new CreateWorkspaceRequestDto(); dto.setBizName("세븐일레븐"); @@ -62,19 +72,27 @@ private CreateWorkspaceRequestDto baseRequest() { dto.setProvince("서울특별시"); dto.setDistrict("구로구"); dto.setTown("고척동"); - dto.setType("음식점"); + dto.setBusinessTypeId(1L); dto.setContact("02-1234-5678"); dto.setWorkspaceCertFileId("cert-file"); dto.setWorkspaceOwnIdentityFileId("identity-file"); return dto; } + private BusinessType businessType(boolean requiresDetail) { + BusinessType businessType = mock(BusinessType.class); + given(businessType.isRequiresDetail()).willReturn(requiresDetail); + return businessType; + } + @Test @DisplayName("대표이미지 없이도 정상 처리되며 대표이미지 저장은 호출되지 않는다") void execute_대표이미지없음() { // given User user = mock(User.class); given(user.getId()).willReturn(100L); + BusinessType businessType = businessType(false); + given(businessTypeRepository.findById(1L)).willReturn(Optional.of(businessType)); given(workspaceRequestRepository.save(any())).willReturn(1L); // when @@ -92,6 +110,8 @@ private CreateWorkspaceRequestDto baseRequest() { // given User user = mock(User.class); given(user.getId()).willReturn(100L); + BusinessType businessType = businessType(false); + given(businessTypeRepository.findById(1L)).willReturn(Optional.of(businessType)); given(workspaceRequestRepository.save(any())).willReturn(1L); CreateWorkspaceRequestDto dto = baseRequest(); @@ -126,6 +146,8 @@ private CreateWorkspaceRequestDto baseRequest() { // given User user = mock(User.class); given(user.getId()).willReturn(100L); + BusinessType businessType = businessType(false); + given(businessTypeRepository.findById(1L)).willReturn(Optional.of(businessType)); given(workspaceRequestRepository.save(any())).willReturn(1L); CreateWorkspaceRequestDto dto = baseRequest(); @@ -141,4 +163,80 @@ private CreateWorkspaceRequestDto baseRequest() { .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()).isEqualTo(ErrorCode.ILLEGAL_ARGUMENT)); then(workspaceRequestImageRepository).should(never()).saveAll(any()); } + + @Test + @DisplayName("존재하지 않는 업종 ID 면 ILLEGAL_ARGUMENT 예외가 발생하고 신청을 저장하지 않는다") + void execute_존재하지않는업종_예외() { + // given + User user = mock(User.class); + CreateWorkspaceRequestDto dto = baseRequest(); + dto.setBusinessTypeId(99L); + given(businessTypeRepository.findById(99L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> createWorkspaceRequest.execute(user, dto)) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()).isEqualTo(ErrorCode.ILLEGAL_ARGUMENT)); + then(workspaceRequestRepository).should(never()).save(any()); + } + + @Test + @DisplayName("'기타' 업종인데 상세 입력이 없으면 ILLEGAL_ARGUMENT 예외가 발생한다") + void execute_기타_상세없음_예외() { + // given + User user = mock(User.class); + CreateWorkspaceRequestDto dto = baseRequest(); + dto.setBusinessTypeDetail(" "); + BusinessType businessType = businessType(true); + given(businessTypeRepository.findById(1L)).willReturn(Optional.of(businessType)); + + // when & then + assertThatThrownBy(() -> createWorkspaceRequest.execute(user, dto)) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()).isEqualTo(ErrorCode.ILLEGAL_ARGUMENT)); + then(workspaceRequestRepository).should(never()).save(any()); + } + + @Test + @DisplayName("'기타' 업종이면 상세 입력을 trim 하여 신청에 저장한다") + void execute_기타_상세있음_저장() { + // given + User user = mock(User.class); + given(user.getId()).willReturn(100L); + BusinessType etc = businessType(true); + given(businessTypeRepository.findById(1L)).willReturn(Optional.of(etc)); + given(workspaceRequestRepository.save(any())).willReturn(1L); + + CreateWorkspaceRequestDto dto = baseRequest(); + dto.setBusinessTypeDetail(" 떡볶이 전문점 "); + + // when + createWorkspaceRequest.execute(user, dto); + + // then + then(workspaceRequestRepository).should().save(workspaceRequestCaptor.capture()); + assertThat(workspaceRequestCaptor.getValue().getBusinessType()).isEqualTo(etc); + assertThat(workspaceRequestCaptor.getValue().getBusinessTypeDetail()).isEqualTo("떡볶이 전문점"); + } + + @Test + @DisplayName("'기타'가 아닌 업종이면 상세 입력이 들어와도 무시(null)한다") + void execute_비기타_상세무시() { + // given + User user = mock(User.class); + given(user.getId()).willReturn(100L); + BusinessType businessType = businessType(false); + given(businessTypeRepository.findById(1L)).willReturn(Optional.of(businessType)); + given(workspaceRequestRepository.save(any())).willReturn(1L); + + CreateWorkspaceRequestDto dto = baseRequest(); + dto.setBusinessTypeDetail("무시되어야 함"); + + // when + createWorkspaceRequest.execute(user, dto); + + // then + then(workspaceRequestRepository).should().save(workspaceRequestCaptor.capture()); + assertThat(workspaceRequestCaptor.getValue().getBusinessTypeDetail()).isNull(); + } } diff --git a/src/test/java/com/dreamteam/alter/application/workspace/usecase/UpdateWorkspaceRequestStatusTests.java b/src/test/java/com/dreamteam/alter/application/workspace/usecase/UpdateWorkspaceRequestStatusTests.java index 0f3b294a..3211cba3 100644 --- a/src/test/java/com/dreamteam/alter/application/workspace/usecase/UpdateWorkspaceRequestStatusTests.java +++ b/src/test/java/com/dreamteam/alter/application/workspace/usecase/UpdateWorkspaceRequestStatusTests.java @@ -10,6 +10,7 @@ import com.dreamteam.alter.domain.user.entity.User; import com.dreamteam.alter.domain.user.port.outbound.ManagerUserQueryRepository; import com.dreamteam.alter.domain.user.port.outbound.ManagerUserRepository; +import com.dreamteam.alter.domain.workspace.entity.BusinessType; import com.dreamteam.alter.domain.workspace.entity.Workspace; import com.dreamteam.alter.domain.workspace.entity.WorkspaceImage; import com.dreamteam.alter.domain.workspace.entity.WorkspaceRequest; @@ -75,6 +76,9 @@ class UpdateWorkspaceRequestStatusTests { @Captor private ArgumentCaptor> imagesCaptor; + @Captor + private ArgumentCaptor workspaceCaptor; + @Nested @DisplayName("execute") class ExecuteTests { @@ -161,6 +165,31 @@ class ExecuteTests { then(fileDeleteService).should(never()).delete(any()); } + @Test + @DisplayName("승인 시 신청의 업종 FK 와 상세를 업장으로 복사한다") + void execute_ACTIVATED_업종복사() { + // given + User user = mock(User.class); + WorkspaceRequest request = mock(WorkspaceRequest.class); + BusinessType businessType = mock(BusinessType.class); + given(request.getUser()).willReturn(user); + given(user.getId()).willReturn(1L); + given(request.getBusinessType()).willReturn(businessType); + given(request.getBusinessTypeDetail()).willReturn("떡볶이 전문점"); + given(workspaceRequestQueryRepository.findByIdWithUser(1L)).willReturn(Optional.of(request)); + given(managerUserQueryRepository.findByUserId(1L)).willReturn(Optional.of(mock(ManagerUser.class))); + given(workspaceRequestImageQueryRepository.findAllByWorkspaceRequestId(1L)).willReturn(List.of()); + given(fileQueryRepository.findByTargetTypeAndTargetId(any(), any())).willReturn(Optional.empty()); + + // when + updateWorkspaceRequestStatus.execute(1L, WorkspaceRequestStatus.ACTIVATED); + + // then + then(workspaceRepository).should().save(workspaceCaptor.capture()); + assertThat(workspaceCaptor.getValue().getBusinessType()).isEqualTo(businessType); + assertThat(workspaceCaptor.getValue().getBusinessTypeDetail()).isEqualTo("떡볶이 전문점"); + } + @Test @DisplayName("REVOKED 면 reject 만 처리하고 업장/매니저는 생성하지 않는다") void execute_REVOKED_반려() { diff --git a/src/test/java/com/dreamteam/alter/domain/workspace/entity/WorkspaceRequestTests.java b/src/test/java/com/dreamteam/alter/domain/workspace/entity/WorkspaceRequestTests.java index 4f3f2a92..4ed9d992 100644 --- a/src/test/java/com/dreamteam/alter/domain/workspace/entity/WorkspaceRequestTests.java +++ b/src/test/java/com/dreamteam/alter/domain/workspace/entity/WorkspaceRequestTests.java @@ -22,7 +22,8 @@ private WorkspaceRequest pendingRequest() { mock(User.class), "1234567890", "비즈니스", - "CAFE", + mock(BusinessType.class), + null, "01012345678", "서울특별시 강남구 역삼동", "서울특별시",