diff --git a/HIGOODS-Api/build.gradle.kts b/HIGOODS-Api/build.gradle.kts index e0fa00a..b123476 100644 --- a/HIGOODS-Api/build.gradle.kts +++ b/HIGOODS-Api/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } openapi3 { - setServer("http://localhost:8080") + setServer("http://localhost:8080/api") title = "HIGOODS API Documentation" description = "Spring REST Docs with SwaggerUI" version = "0.0.1" diff --git a/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/controller/OrderAdminController.kt b/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/controller/OrderAdminController.kt new file mode 100644 index 0000000..5e2f270 --- /dev/null +++ b/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/controller/OrderAdminController.kt @@ -0,0 +1,51 @@ +package com.higoods.api.order.controller + +import com.higoods.api.order.dto.response.OrderAdminResponse +import com.higoods.api.order.usecase.OrderApproveUseCase +import com.higoods.api.order.usecase.OrderCancelUseCase +import com.higoods.api.order.usecase.OrderReadUseCase +import com.higoods.domain.order.domain.OrderState +import org.springframework.data.domain.Page +import org.springframework.data.domain.PageRequest +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/v1/admins/orders") +class OrderAdminController( + private val orderCancelUseCase: OrderCancelUseCase, + private val orderApproveUseCase: OrderApproveUseCase, + private val orderReadUseCase: OrderReadUseCase +) { + // 주문 취소 + @PostMapping("/{order_id}/cancellations") + fun create( + @PathVariable("order_id") orderId: Long + ): Long { + return orderCancelUseCase.execute(orderId) + } + + // 입금 승인 + @PostMapping("/{order_id}/approvals") + fun approve( + @PathVariable("order_id") orderId: Long + ): Long { + return orderApproveUseCase.execute(orderId) + } + + // 명단 관리-입금 확인 목록 + @PostMapping("/{project_id}") + fun get( + @PathVariable("project_id") projectId: Long, + @RequestParam(value = "state", defaultValue = "PENDING") state: OrderState, + @RequestParam(value = "name", required = false) name: String?, + @RequestParam("page") page: Int, + @RequestParam("size") size: Int + ): Page { + // TODO: 페이지네이션 정상 작동 api 테스트하면서 다시 확인하기 + return orderReadUseCase.findByStateAndName(projectId, state, name, PageRequest.of(page, size)) + } +} diff --git a/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/dto/response/OrderAdminResponse.kt b/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/dto/response/OrderAdminResponse.kt new file mode 100644 index 0000000..cc63ede --- /dev/null +++ b/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/dto/response/OrderAdminResponse.kt @@ -0,0 +1,29 @@ +package com.higoods.api.order.dto.response + +import com.higoods.domain.order.domain.Order +import com.higoods.domain.order.domain.OrderState +import java.time.format.DateTimeFormatter + +data class OrderAdminResponse( + val orderNo: String, // 주문 번호 + val name: String, // 이름 + val depositName: String, // 입금자명 + val createdAt: String, // 주문 일시 + val phoneNum: String, // 전화번호 + val totalCost: Int, // 총 금액 + val orderState: OrderState // 주문 상태 +) { + companion object { + fun of(order: Order): OrderAdminResponse { + return OrderAdminResponse( + orderNo = order.orderNo, + name = order.name, + depositName = order.depositName, + createdAt = order.createdAt.format(DateTimeFormatter.ofPattern("yy.MM.dd HH:mm")), + phoneNum = order.phoneNum, + totalCost = order.totalCost, + orderState = order.orderState + ) + } + } +} diff --git a/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/dto/response/OrderProjectsResponse.kt b/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/dto/response/OrderProjectsResponse.kt index 8673547..9cabe83 100644 --- a/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/dto/response/OrderProjectsResponse.kt +++ b/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/dto/response/OrderProjectsResponse.kt @@ -1,24 +1,27 @@ package com.higoods.api.order.dto.response +import com.higoods.domain.order.domain.OrderState import com.higoods.domain.project.domain.Project data class OrderProjectsResponse( val orderId: Long, val title: String, val titleImage: String, - val subTitle: String + val subTitle: String, + val orderState: OrderState // TODO: 프로젝트단 관련 작업 완료되면 추가 작업 필요 // val category: String, // TODO: 프로젝트 상채 도메인 개발 후 추가 작업 필요 // val projectStatus: String ) { companion object { - fun of(orderId: Long, project: Project): OrderProjectsResponse { + fun of(orderId: Long, orderState: OrderState, project: Project): OrderProjectsResponse { return OrderProjectsResponse( orderId = orderId, title = project.title, titleImage = project.titleImage, - subTitle = project.subTitle + subTitle = project.subTitle, + orderState = orderState ) } } diff --git a/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/usecase/OrderApproveUseCase.kt b/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/usecase/OrderApproveUseCase.kt new file mode 100644 index 0000000..3f75ddd --- /dev/null +++ b/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/usecase/OrderApproveUseCase.kt @@ -0,0 +1,24 @@ +package com.higoods.api.order.usecase + +import com.higoods.api.config.security.SecurityUtils +import com.higoods.common.annotation.UseCase +import com.higoods.domain.order.adapter.OrderAdapter +import com.higoods.domain.order.service.OrderDomainService +import com.higoods.domain.project.adapter.ProjectAdapter +import com.higoods.domain.project.exception.ProjectNotHostException +import org.springframework.transaction.annotation.Transactional + +@UseCase +class OrderApproveUseCase( + private val orderDomainService: OrderDomainService, + private val orderAdapter: OrderAdapter, + private val projectAdapter: ProjectAdapter +) { + @Transactional + fun execute(orderId: Long): Long { + val order = orderAdapter.queryById(orderId) + val project = projectAdapter.queryById(order.projectId) + if (project.userId != SecurityUtils.currentUserId) throw ProjectNotHostException.EXCEPTION + return orderDomainService.approveOrder(order) + } +} diff --git a/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/usecase/OrderCancelUseCase.kt b/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/usecase/OrderCancelUseCase.kt new file mode 100644 index 0000000..554f43c --- /dev/null +++ b/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/usecase/OrderCancelUseCase.kt @@ -0,0 +1,27 @@ +package com.higoods.api.order.usecase + +import com.higoods.api.config.security.SecurityUtils +import com.higoods.common.annotation.UseCase +import com.higoods.domain.order.adapter.OrderAdapter +import com.higoods.domain.order.service.OrderDomainService +import com.higoods.domain.project.adapter.ProjectAdapter +import com.higoods.domain.project.exception.ProjectNotHostException +import com.higoods.domain.project.service.ProjectDomainService +import org.springframework.transaction.annotation.Transactional + +@UseCase +class OrderCancelUseCase( + private val orderDomainService: OrderDomainService, + private val orderAdapter: OrderAdapter, + private val projectAdapter: ProjectAdapter, + private val projectDomainService: ProjectDomainService +) { + @Transactional + fun execute(orderId: Long): Long { + val order = orderAdapter.queryById(orderId) + val project = projectAdapter.queryById(order.projectId) + if (project.userId != SecurityUtils.currentUserId) throw ProjectNotHostException.EXCEPTION + projectDomainService.decreasePurchaseNum(project) + return orderDomainService.cancelOrder(order) + } +} diff --git a/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/usecase/OrderCreateUseCase.kt b/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/usecase/OrderCreateUseCase.kt index 3a89d88..45d8b48 100644 --- a/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/usecase/OrderCreateUseCase.kt +++ b/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/usecase/OrderCreateUseCase.kt @@ -8,6 +8,7 @@ import com.higoods.domain.order.domain.OrderAnswer import com.higoods.domain.order.service.OrderDomainService import com.higoods.domain.project.adapter.ProjectAdapter import com.higoods.domain.project.service.ProjectDomainService +import org.springframework.transaction.annotation.Transactional @UseCase class OrderCreateUseCase( @@ -15,10 +16,12 @@ class OrderCreateUseCase( private val projectAdapter: ProjectAdapter, private val projectDomainService: ProjectDomainService ) { + @Transactional fun execute(projectId: Long, orderCreateRequest: OrderCreateRequest): OrderResponse { val currentUserId = SecurityUtils.currentUserId val project = projectAdapter.queryById(projectId) // TODO: 리스폰스에 주문번호 업데이트 되는지 확인 필요 + // 시간 남으면 createOrder 함수에서 옵션, 주문폽 생성 같이 하도록 리팩토링 val newOrder = orderDomainService.createOrder(orderCreateRequest.toOrder(project.id, currentUserId)) val newOrderOptions = orderDomainService.createOrderOptions(orderCreateRequest.toOrderOptionItems(newOrder.id)) var newOrderAnswers: List? = null diff --git a/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/usecase/OrderReadUseCase.kt b/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/usecase/OrderReadUseCase.kt index 8741ae0..84d725d 100644 --- a/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/usecase/OrderReadUseCase.kt +++ b/HIGOODS-Api/src/main/kotlin/com/higoods/api/order/usecase/OrderReadUseCase.kt @@ -1,27 +1,35 @@ package com.higoods.api.order.usecase import com.higoods.api.config.security.SecurityUtils +import com.higoods.api.order.dto.response.OrderAdminResponse import com.higoods.api.order.dto.response.OrderProjectsResponse import com.higoods.api.order.dto.response.OrderResponse import com.higoods.common.annotation.UseCase import com.higoods.domain.order.adapter.OrderAdapter +import com.higoods.domain.order.domain.OrderState import com.higoods.domain.order.exception.OrderNotUserException import com.higoods.domain.project.adapter.ProjectAdapter +import com.higoods.domain.project.exception.ProjectNotHostException +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable +import org.springframework.transaction.annotation.Transactional @UseCase class OrderReadUseCase( private val orderAdapter: OrderAdapter, private val projectAdapter: ProjectAdapter ) { + @Transactional(readOnly = true) fun findAll(): List { val orders = orderAdapter.findAll(SecurityUtils.currentUserId) if (orders.isNullOrEmpty()) return emptyList() return orders.map { order -> val project = projectAdapter.queryById(order.projectId) - OrderProjectsResponse.of(order.id, project) + OrderProjectsResponse.of(order.id, order.orderState, project) } } + @Transactional(readOnly = true) fun findById(orderId: Long): OrderResponse { val order = orderAdapter.queryById(orderId) if (order.userId != SecurityUtils.currentUserId) throw OrderNotUserException.EXCEPTION @@ -29,4 +37,18 @@ class OrderReadUseCase( val orderAnswers = orderAdapter.findAllOrderAnswerByOrderIdOrNull(orderId) return OrderResponse.of(order, orderOptions, orderAnswers) } + + fun findByStateAndName( + projectId: Long, + state: OrderState, + name: String?, + pageable: Pageable + ): Page { + val project = projectAdapter.queryById(projectId) + if (project.userId != SecurityUtils.currentUserId) throw ProjectNotHostException.EXCEPTION + val orders = orderAdapter.queryOrders(project.id, state, name, pageable) + return orders.map { order -> + OrderAdminResponse.of(order) + } + } } diff --git a/HIGOODS-Api/src/test/kotlin/com/higoods/api/common/BaseControllerTest.kt b/HIGOODS-Api/src/test/kotlin/com/higoods/api/common/BaseControllerTest.kt index 39252e9..d21e77e 100644 --- a/HIGOODS-Api/src/test/kotlin/com/higoods/api/common/BaseControllerTest.kt +++ b/HIGOODS-Api/src/test/kotlin/com/higoods/api/common/BaseControllerTest.kt @@ -1,5 +1,6 @@ package com.higoods.api.common +import com.fasterxml.jackson.databind.ObjectMapper import io.kotest.core.spec.style.FunSpec import org.springframework.restdocs.ManualRestDocumentation import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation @@ -11,6 +12,7 @@ import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder abstract class BaseControllerTest : FunSpec() { protected abstract val controller: Any protected lateinit var mockMvc: MockMvc + protected lateinit var objectMapper: ObjectMapper private val restDocumentation = ManualRestDocumentation() init { beforeSpec { diff --git a/HIGOODS-Api/src/test/kotlin/com/higoods/api/order/OrderAdminControllerTest.kt b/HIGOODS-Api/src/test/kotlin/com/higoods/api/order/OrderAdminControllerTest.kt new file mode 100644 index 0000000..8e19cdd --- /dev/null +++ b/HIGOODS-Api/src/test/kotlin/com/higoods/api/order/OrderAdminControllerTest.kt @@ -0,0 +1,135 @@ +package com.higoods.api.order + +import com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper +import com.epages.restdocs.apispec.ResourceDocumentation.parameterWithName +import com.epages.restdocs.apispec.ResourceSnippetParametersBuilder +import com.higoods.api.common.BaseControllerTest +import com.higoods.api.order.controller.OrderAdminController +import com.higoods.api.order.dto.response.OrderAdminResponse +import com.higoods.api.order.usecase.OrderApproveUseCase +import com.higoods.api.order.usecase.OrderCancelUseCase +import com.higoods.api.order.usecase.OrderReadUseCase +import com.higoods.domain.order.domain.OrderState +import io.mockk.every +import io.mockk.mockk +import org.springframework.data.domain.PageImpl +import org.springframework.data.domain.PageRequest +import org.springframework.http.MediaType +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders +import org.springframework.restdocs.payload.JsonFieldType +import org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers + +class OrderAdminControllerTest : BaseControllerTest() { + private val orderCancelUseCase: OrderCancelUseCase = mockk() + private val orderApproveUseCase: OrderApproveUseCase = mockk() + private val orderReadUseCase: OrderReadUseCase = mockk() + override val controller: OrderAdminController = OrderAdminController(orderCancelUseCase, orderApproveUseCase, orderReadUseCase) + + init { + test("주문 취소") { + val orderId = 1L + + every { orderCancelUseCase.execute(1) } returns orderId + + mockMvc.perform( + RestDocumentationRequestBuilders.post("/v1/admins/orders/{order_id}/cancellations", 1) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andDo( + MockMvcRestDocumentationWrapper.document( + "[어드민] 주문 취소 API", + ResourceSnippetParametersBuilder() + .description("[어드민] 주문 취소 API") + .pathParameters( + parameterWithName("order_id").description("주문 id") + ) + .responseFields( + fieldWithPath("orderId").type(JsonFieldType.NUMBER).description("주문 id") + ) + ) + ) + } + + test("입금 승인") { + val orderId = 1L + + every { orderApproveUseCase.execute(1) } returns orderId + + mockMvc.perform( + RestDocumentationRequestBuilders.post("/v1/admins/orders/{order_id}/approvals", 1) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andDo( + MockMvcRestDocumentationWrapper.document( + "[어드민] 입금 승인 API", + ResourceSnippetParametersBuilder() + .description("[어드민] 입금 승인 API") + .pathParameters( + parameterWithName("order_id").description("주문 id") + ) + .responseFields( + fieldWithPath("orderId").type(JsonFieldType.NUMBER).description("주문 id") + ) + ) + ) + } + + test("명단 관리-입금 확인 목록") { + val orderAdminResponse = PageImpl( + listOf( + OrderAdminResponse( + orderNo = "H100001", + name = "이홍익", + depositName = "이홍익", + createdAt = "23.08.27 03:15", + phoneNum = "010-1111-2222", + totalCost = 10000, + orderState = OrderState.PENDING + ) + ), + PageRequest.of(1, 6), + 10 // 전체 아이템 개수 + ) + + every { orderReadUseCase.findByStateAndName(1, OrderState.PENDING, null, PageRequest.of(1, 6)) } returns orderAdminResponse + + mockMvc.perform( + RestDocumentationRequestBuilders.post("/v1/admins/orders/{project_id}", 1) + .param("state", OrderState.PENDING.toString()) + .param("name", null) + .param("page", "1") + .param("size", "6") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andDo( + MockMvcRestDocumentationWrapper.document( + "[어드민] 명단 관리-입금 확인 목록 API", + ResourceSnippetParametersBuilder() + .description("[어드민] 명단 관리-입금 확인 목록 API") + .pathParameters( + parameterWithName("project_id").description("프로젝트 id") + ) + .responseFields( + fieldWithPath("content[]").description("주문 목록"), + fieldWithPath("content[].orderNo").description("주문 번호"), + fieldWithPath("content[].name").description("이름"), + fieldWithPath("content[].depositName").description("입금자명"), + fieldWithPath("content[].createdAt").description("주문 일시"), + fieldWithPath("content[].phoneNum").description("전화번호"), + fieldWithPath("content[].totalCost").description("총 금액"), + fieldWithPath("content[].orderState").description("주문 상태"), + fieldWithPath("pageable").description("페이지 정보"), + fieldWithPath("totalElements").description("전체 주문 개수") + ) + ) + ) + } + } +} diff --git a/HIGOODS-Api/src/test/kotlin/com/higoods/api/order/OrderControllerTest.kt b/HIGOODS-Api/src/test/kotlin/com/higoods/api/order/OrderControllerTest.kt index 534cf66..7aae758 100644 --- a/HIGOODS-Api/src/test/kotlin/com/higoods/api/order/OrderControllerTest.kt +++ b/HIGOODS-Api/src/test/kotlin/com/higoods/api/order/OrderControllerTest.kt @@ -1,72 +1,78 @@ -// package com.higoods.api.order -// -// import com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper -// import com.epages.restdocs.apispec.ResourceDocumentation.parameterWithName -// import com.epages.restdocs.apispec.ResourceSnippetParametersBuilder -// import com.higoods.api.common.BaseControllerTest -// import com.higoods.api.order.controller.OrderController -// import com.higoods.api.order.dto.request.OrderAnswerDto -// import com.higoods.api.order.dto.request.OrderCreateRequest -// import com.higoods.api.order.dto.request.OrderOptionDto -// import com.higoods.api.order.dto.response.OrderResponse -// import com.higoods.api.order.usecase.OrderCreateUseCase -// import com.higoods.domain.order.domain.AnswerType -// import com.higoods.domain.order.domain.ReceiveType -// import io.mockk.every -// import io.mockk.mockk -// import org.springframework.http.MediaType -// import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders -// import org.springframework.restdocs.payload.JsonFieldType -// import org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath -// import org.springframework.test.web.servlet.result.MockMvcResultMatchers -// -// class OrderControllerTest : BaseControllerTest() { -// private val orderCreateUseCase: OrderCreateUseCase = mockk() -// override val controller: OrderController = OrderController(orderCreateUseCase) -// -// init { +package com.higoods.api.order + +import com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper +import com.epages.restdocs.apispec.ResourceSnippetParametersBuilder +import com.higoods.api.common.BaseControllerTest +import com.higoods.api.order.controller.OrderController +import com.higoods.api.order.dto.request.OrderAnswerDto +import com.higoods.api.order.dto.request.OrderCreateRequest +import com.higoods.api.order.dto.request.OrderOptionDto +import com.higoods.api.order.dto.response.OrderProjectsResponse +import com.higoods.api.order.dto.response.OrderResponse +import com.higoods.api.order.usecase.OrderCreateUseCase +import com.higoods.api.order.usecase.OrderReadUseCase +import com.higoods.domain.order.domain.AnswerType +import com.higoods.domain.order.domain.OrderState +import com.higoods.domain.order.domain.ReceiveType +import io.mockk.every +import io.mockk.mockk +import org.springframework.http.MediaType +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders +import org.springframework.restdocs.payload.JsonFieldType +import org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers + +class OrderControllerTest : BaseControllerTest() { + private val orderCreateUseCase: OrderCreateUseCase = mockk() + private val orderReadUseCase: OrderReadUseCase = mockk() + override val controller: OrderController = OrderController(orderCreateUseCase, orderReadUseCase) + + companion object { + val orderResponse = OrderResponse( + orderNo = "H100001", // 주문번호 + name = "이홍익", // 이름 + studentId = "B811112", // 학번 + phoneNum = "010-1111-2222", // 전화번호 + receiveType = ReceiveType.DISTRIBUTION, // 상품 수령 방법, DELIVERY or DISTRIBUTION + refundBank = "신한은행", // 환불 은행 + refundAccount = "1234567890", // 환불 계좌 + depositName = "이홍익", // 입금자명, + totalCost = 10000, // 총 금액 + orderOptions = listOf(OrderOptionDto(1, 2), OrderOptionDto(2, 1)), + orderAnswers = listOf(OrderAnswerDto(1, AnswerType.SHORT, 0, "아니요"), OrderAnswerDto(2, AnswerType.MULTIPLE_CHOICE, 1, "")) + + ) + + val orderCreateRequest = OrderCreateRequest( + name = "이홍익", + studentId = "B811112", + phoneNum = "010-1111-2222", + receiveType = ReceiveType.DISTRIBUTION, + depositName = "이홍익", + refundBank = "신한은행", + refundAccount = "1234567890", + totalCost = 10000, + orderOptions = listOf(OrderOptionDto(1, 2), OrderOptionDto(2, 1)), + orderAnswers = listOf(OrderAnswerDto(1, AnswerType.SHORT, 0, "아니요"), OrderAnswerDto(2, AnswerType.MULTIPLE_CHOICE, 1, "")) + ) + } + + init { // test("주문 생성 테스트") { -// val orderResponse = OrderResponse( -// orderNo = "H100001", // 주문번호 -// name = "이홍익", // 이름 -// studentId = "B811112", // 학번 -// phoneNum = "01011112222", // 전화번호 -// receiveType = ReceiveType.DISTRIBUTION, // 상품 수령 방법, DELIVERY or DISTRIBUTION -// refundBank = "신한은행", // 환불 은행 -// refundAccount = "1234567890", // 환불 계좌 -// depositName = "이홍익", // 입금자명, -// totalCost = 10000, // 총 금액 -// orderOptions = listOf(OrderOptionDto(1, 2), OrderOptionDto(2, 1)), -// orderAnswers = listOf(OrderAnswerDto(1, AnswerType.SHORT, 0, "아니요"), OrderAnswerDto(2, AnswerType.MULTIPLE_CHOICE, 1, "")) -// -// ) -// -// val orderCreateRequest = OrderCreateRequest( -// name = "이홍익", -// studentId = "B811112", -// phoneNum = "01011112222", -// receiveType = ReceiveType.DISTRIBUTION, -// depositName = "이홍익", -// refundBank = "신한은행", -// refundAccount = "1234567890", -// totalCost = 10000, -// orderOptions = listOf(OrderOptionDto(1, 2), OrderOptionDto(2, 1)), -// orderAnswers = listOf(OrderAnswerDto(1, AnswerType.SHORT, 0, "아니요"), OrderAnswerDto(2, AnswerType.MULTIPLE_CHOICE, 1, "")) -// ) -// // every { orderCreateUseCase.execute(1, orderCreateRequest) } returns orderResponse // // mockMvc.perform( -// RestDocumentationRequestBuilders.post("/v1/orders/{project_id}",1) +// RestDocumentationRequestBuilders.post("/v1/orders/{project_id}", 1) // .contentType(MediaType.APPLICATION_JSON) // .accept(MediaType.APPLICATION_JSON) +// .content(objectMapper.writeValueAsString(orderCreateRequest)) // ) // .andExpect(MockMvcResultMatchers.status().isOk()) // .andDo( // MockMvcRestDocumentationWrapper.document( // "주문 생성 API", // ResourceSnippetParametersBuilder() -// .description("유저 주문을 생성합니다.") +// .description("주문 생성 API") // .pathParameters( // parameterWithName("project_id").description("프로젝트 id") // ) @@ -79,15 +85,91 @@ // fieldWithPath("refundBank").type(JsonFieldType.STRING).description("환불 은행"), // fieldWithPath("refundAccount").type(JsonFieldType.STRING).description("환불 계좌"), // fieldWithPath("totalCost").type(JsonFieldType.NUMBER).description("총 금액"), -// fieldWithPath("orderOptions[]").description("상품 옵션 정보"), +// fieldWithPath("orderOptions[]").type(JsonFieldType.ARRAY).description("상품 옵션 정보"), +// fieldWithPath("orderOptions[].optionId").type(JsonFieldType.NUMBER).description("상품 옵션 id"), +// fieldWithPath("orderOptions[].count").type(JsonFieldType.NUMBER).description("상품 옵션 수량"), +// fieldWithPath("orderAnswers[]").type(JsonFieldType.ARRAY).description("커스텀 주문폼 정보").optional(), +// fieldWithPath("orderAnswers[].optionId").type(JsonFieldType.NUMBER).description("커스텀 주문폼 정보").optional(), +// fieldWithPath("orderAnswers[].answerType").type(JsonFieldType.STRING).description("커스텀 주문폼 정보").optional(), +// fieldWithPath("orderAnswers[].multipleChoiceId").type(JsonFieldType.NUMBER).description("커스텀 주문폼 정보").optional(), +// fieldWithPath("orderAnswers[].shortAnswer").type(JsonFieldType.STRING).description("커스텀 주문폼 정보").optional() +// ) +// .responseFields( +// fieldWithPath("orderNo").type(JsonFieldType.STRING).description("주문 번호"), +// fieldWithPath("name").type(JsonFieldType.STRING).description("이름"), +// fieldWithPath("studentId").type(JsonFieldType.STRING).description("학번"), +// fieldWithPath("phoneNum").type(JsonFieldType.STRING).description("전화번호"), +// fieldWithPath("receiveType").type(JsonFieldType.STRING).description("상품 수령 방법"), +// fieldWithPath("depositName").type(JsonFieldType.STRING).description("입금자명"), +// fieldWithPath("refundBank").type(JsonFieldType.STRING).description("환불 은행"), +// fieldWithPath("refundAccount").type(JsonFieldType.STRING).description("환불 계좌"), +// fieldWithPath("totalCost").type(JsonFieldType.NUMBER).description("총 금액"), +// fieldWithPath("orderOptions[]").type(JsonFieldType.ARRAY).description("상품 옵션 정보"), // fieldWithPath("orderOptions[].optionId").type(JsonFieldType.NUMBER).description("상품 옵션 id"), // fieldWithPath("orderOptions[].count").type(JsonFieldType.NUMBER).description("상품 옵션 수량"), -// fieldWithPath("orderAnswers[]").description("커스텀 주문폼 정보").optional(), +// fieldWithPath("orderAnswers[]").type(JsonFieldType.ARRAY).description("커스텀 주문폼 정보"), // fieldWithPath("orderAnswers[].optionId").type(JsonFieldType.NUMBER).description("커스텀 주문폼 정보").optional(), // fieldWithPath("orderAnswers[].answerType").type(JsonFieldType.STRING).description("커스텀 주문폼 정보").optional(), // fieldWithPath("orderAnswers[].multipleChoiceId").type(JsonFieldType.NUMBER).description("커스텀 주문폼 정보").optional(), // fieldWithPath("orderAnswers[].shortAnswer").type(JsonFieldType.STRING).description("커스텀 주문폼 정보").optional() // ) +// ) +// ) +// } + + test("마이페이지-내 주문 목록 조회") { + val orderProjectResponse = listOf( + OrderProjectsResponse( + orderId = 1, + title = "제목", + titleImage = "image", + subTitle = "부제목", + orderState = OrderState.PENDING + ) + ) + + every { orderReadUseCase.findAll() } returns orderProjectResponse + + mockMvc.perform( + RestDocumentationRequestBuilders.get("/v1/orders") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andDo( + MockMvcRestDocumentationWrapper.document( + "마이페이지-내 주문 목록 조회 API", + ResourceSnippetParametersBuilder() + .description("마이페이지-내 주문 목록 조회 API") + .responseFields( + fieldWithPath("orderId").type(JsonFieldType.NUMBER).description("주문 id"), + fieldWithPath("title").type(JsonFieldType.STRING).description("제목"), + fieldWithPath("titleImage").type(JsonFieldType.STRING).description("이미지"), + fieldWithPath("subTitle").type(JsonFieldType.STRING).description("부제목"), + fieldWithPath("orderState").type(JsonFieldType.STRING).description("주문 상태") + ) + ) + ) + } + +// test("내 주문 상세 조회") { +// +// every { orderReadUseCase.findById(1) } returns orderResponse +// +// mockMvc.perform( +// RestDocumentationRequestBuilders.post("/v1/orders/{order_id}", 1) +// .contentType(MediaType.APPLICATION_JSON) +// .accept(MediaType.APPLICATION_JSON) +// ) +// .andExpect(MockMvcResultMatchers.status().isOk()) +// .andDo( +// MockMvcRestDocumentationWrapper.document( +// "내 주문 상세 조회 API", +// ResourceSnippetParametersBuilder() +// .description("내 주문 상세 조회 API") +// .pathParameters( +// parameterWithName("order_id").description("주문 id") +// ) // .responseFields( // fieldWithPath("orderNo").type(JsonFieldType.STRING).description("주문 번호"), // fieldWithPath("name").type(JsonFieldType.STRING).description("이름"), @@ -98,6 +180,7 @@ // fieldWithPath("refundBank").type(JsonFieldType.STRING).description("환불 은행"), // fieldWithPath("refundAccount").type(JsonFieldType.STRING).description("환불 계좌"), // fieldWithPath("totalCost").type(JsonFieldType.NUMBER).description("총 금액"), +// fieldWithPath("orderOptions[]").description("상품 옵션 정보"), // fieldWithPath("orderOptions[].optionId").type(JsonFieldType.NUMBER).description("상품 옵션 id"), // fieldWithPath("orderOptions[].count").type(JsonFieldType.NUMBER).description("상품 옵션 수량"), // fieldWithPath("orderAnswers[]").description("커스텀 주문폼 정보"), @@ -109,5 +192,5 @@ // ) // ) // } -// } -// } + } +} diff --git a/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/order/adapter/OrderAdapter.kt b/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/order/adapter/OrderAdapter.kt index be63e75..093be1f 100644 --- a/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/order/adapter/OrderAdapter.kt +++ b/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/order/adapter/OrderAdapter.kt @@ -4,16 +4,21 @@ import com.higoods.common.annotation.Adapter import com.higoods.domain.order.domain.Order import com.higoods.domain.order.domain.OrderAnswer import com.higoods.domain.order.domain.OrderOptionItem +import com.higoods.domain.order.domain.OrderState import com.higoods.domain.order.exception.OrderNotFoundException import com.higoods.domain.order.exception.OrderOptionItemNotFoundException import com.higoods.domain.order.repository.OrderAnswerRepository import com.higoods.domain.order.repository.OrderOptionItemRepository +import com.higoods.domain.order.repository.OrderQuerydslRepository import com.higoods.domain.order.repository.OrderRepository +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable import org.springframework.data.repository.findByIdOrNull @Adapter class OrderAdapter( private val orderRepository: OrderRepository, + private val orderQuerydslRepository: OrderQuerydslRepository, private val optionItemRepository: OrderOptionItemRepository, private val answerRepository: OrderAnswerRepository ) { @@ -44,4 +49,8 @@ class OrderAdapter( fun findAll(userId: Long): List? { return orderRepository.findAllByUserIdOrderByCreatedAtDesc(userId) } + + fun queryOrders(projectId: Long, state: OrderState, name: String?, pageable: Pageable): Page { + return orderQuerydslRepository.findAllByProjectIdAndStateAndName(projectId, state, name, pageable) + } } diff --git a/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/order/domain/Order.kt b/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/order/domain/Order.kt index 5f76059..0779509 100644 --- a/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/order/domain/Order.kt +++ b/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/order/domain/Order.kt @@ -33,4 +33,12 @@ class Order( fun createOrder() { this.orderNo = "H" + (NO_START_NUMBER + id) } + + fun cancel() { + this.orderState = OrderState.CANCELED + } + + fun approve() { + this.orderState = OrderState.APPROVAL + } } diff --git a/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/order/domain/OrderState.kt b/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/order/domain/OrderState.kt index 03830b2..1fce7c1 100644 --- a/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/order/domain/OrderState.kt +++ b/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/order/domain/OrderState.kt @@ -2,5 +2,6 @@ package com.higoods.domain.order.domain enum class OrderState { PENDING, // 입금 대기 - APPROVAL // 입금 확인 + APPROVAL, // 입금 확인 + CANCELED // 주문 취소 } diff --git a/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/order/repository/OrderQuerydslRepository.kt b/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/order/repository/OrderQuerydslRepository.kt new file mode 100644 index 0000000..5ec1308 --- /dev/null +++ b/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/order/repository/OrderQuerydslRepository.kt @@ -0,0 +1,31 @@ +package com.higoods.domain.order.repository + +import com.higoods.domain.order.domain.Order +import com.higoods.domain.order.domain.OrderState +import com.higoods.domain.order.domain.QOrder.order +import com.querydsl.jpa.impl.JPAQueryFactory +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable +import org.springframework.data.support.PageableExecutionUtils +import org.springframework.stereotype.Repository +import java.util.function.LongSupplier + +@Repository +class OrderQuerydslRepository( + private val queryFactory: JPAQueryFactory +) { + fun findAllByProjectIdAndStateAndName(projectId: Long, state: OrderState, name: String?, pageable: Pageable): Page { + val orders = queryFactory.select(order) + .from(order) + .where(order.projectId.eq(projectId), order.orderState.eq(state), order.name.eq(name)) + .orderBy(order.orderNo.asc()) + .offset(pageable.offset) + .limit(pageable.pageSize.toLong()) + .fetch() + val countQuery = queryFactory.select(order.count()) + .from(order) + .where(order.projectId.eq(projectId), order.orderState.eq(state), order.name.eq(name)) + val countSupplier = LongSupplier { countQuery.fetchOne() ?: 0L } + return PageableExecutionUtils.getPage(orders, pageable, countSupplier) + } +} diff --git a/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/order/service/OrderDomainService.kt b/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/order/service/OrderDomainService.kt index df3b825..844d137 100644 --- a/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/order/service/OrderDomainService.kt +++ b/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/order/service/OrderDomainService.kt @@ -6,20 +6,17 @@ import com.higoods.domain.order.adapter.OrderAdapter import com.higoods.domain.order.domain.Order import com.higoods.domain.order.domain.OrderAnswer import com.higoods.domain.order.domain.OrderOptionItem -import org.springframework.transaction.annotation.Transactional @DomainService class OrderDomainService( private val orderAdapter: OrderAdapter ) { - @Transactional // TODO: 클래스 key 값 잘 들어가는지 확인 필요 @RedissonLock(key = "#order.projectId", lockName = "주문 생성") fun createOrder(order: Order): Order { return orderAdapter.saveOrder(order) } - @Transactional @RedissonLock(key = "#orderOptionItems.get(0).orderId", lockName = "주문 옵션 생성") fun createOrderOptions(orderOptionItems: List): List { return orderOptionItems.map { @@ -29,7 +26,6 @@ class OrderDomainService( .toList() } - @Transactional @RedissonLock(key = "#answers.get(0).orderId", lockName = "주문폼 답변 생성") fun createOrderAnswers(answers: List): List { return answers.map { @@ -38,4 +34,14 @@ class OrderDomainService( } .toList() } + + fun cancelOrder(order: Order): Long { + order.cancel() + return order.id + } + + fun approveOrder(order: Order): Long { + order.approve() + return order.id + } } diff --git a/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/project/service/ProjectDomainService.kt b/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/project/service/ProjectDomainService.kt index 73990b7..86a6b27 100644 --- a/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/project/service/ProjectDomainService.kt +++ b/HIGOODS-Domain/src/main/kotlin/com/higoods/domain/project/service/ProjectDomainService.kt @@ -27,17 +27,14 @@ class ProjectDomainService( return projectId } - @Transactional @RedissonLock(key = "#projectId", lockName = "구매 인원 증가") fun increasePurchaseNum(projectId: Long) { val project = projectAdapter.queryById(projectId) project.increaseCurrentPurchaseQuantity() } - @Transactional @RedissonLock(key = "#projectId", lockName = "구매 인원 감소") - fun decreasePurchaseNum(projectId: Long) { - val project = projectAdapter.queryById(projectId) + fun decreasePurchaseNum(project: Project) { project.decreaseCurrentPurchaseQuantity() } }