-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Combi153 <[email protected]> Co-authored-by: hgo641 <[email protected]>
- Loading branch information
1 parent
33d30f1
commit 47985cd
Showing
38 changed files
with
1,173 additions
and
195 deletions.
There are no files selected for viewing
136 changes: 136 additions & 0 deletions
136
src/main/kotlin/com/petqua/application/order/OrderService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
package com.petqua.application.order | ||
|
||
import com.petqua.application.order.dto.SaveOrderCommand | ||
import com.petqua.application.order.dto.SaveOrderResponse | ||
import com.petqua.common.domain.findByIdOrThrow | ||
import com.petqua.common.util.throwExceptionWhen | ||
import com.petqua.domain.order.Order | ||
import com.petqua.domain.order.OrderName | ||
import com.petqua.domain.order.OrderNumber | ||
import com.petqua.domain.order.OrderRepository | ||
import com.petqua.domain.order.OrderShippingAddress | ||
import com.petqua.domain.order.OrderStatus.ORDER_CREATED | ||
import com.petqua.domain.order.ShippingAddressRepository | ||
import com.petqua.domain.order.ShippingNumber | ||
import com.petqua.domain.product.ProductRepository | ||
import com.petqua.domain.product.option.ProductOptionRepository | ||
import com.petqua.domain.store.StoreRepository | ||
import com.petqua.exception.order.OrderException | ||
import com.petqua.exception.order.OrderExceptionType.ORDER_PRICE_NOT_MATCH | ||
import com.petqua.exception.order.OrderExceptionType.PRODUCT_NOT_FOUND | ||
import com.petqua.exception.order.ShippingAddressException | ||
import com.petqua.exception.order.ShippingAddressExceptionType | ||
import com.petqua.exception.product.ProductException | ||
import com.petqua.exception.product.ProductExceptionType.INVALID_PRODUCT_OPTION | ||
import org.springframework.stereotype.Service | ||
import org.springframework.transaction.annotation.Transactional | ||
|
||
@Transactional | ||
@Service | ||
class OrderService( | ||
private val orderRepository: OrderRepository, | ||
private val productRepository: ProductRepository, | ||
private val productOptionRepository: ProductOptionRepository, | ||
private val shippingAddressRepository: ShippingAddressRepository, | ||
private val storeRepository: StoreRepository, | ||
) { | ||
|
||
fun save(command: SaveOrderCommand): SaveOrderResponse { | ||
// TODO 상품 존재 검증 | ||
val productIds = command.orderProductCommands.map { it.productId } | ||
val productById = productRepository.findAllByIsDeletedFalseAndIdIn(productIds).associateBy { it.id } | ||
val products = productById.map { it.value } | ||
|
||
throwExceptionWhen(products.size != productIds.size) { OrderException(PRODUCT_NOT_FOUND) } | ||
|
||
// TODO 상품 유효성 검증 - 올바른 옵션 매칭인가? | ||
val productOptions = productOptionRepository.findByProductIdIn(productIds) | ||
|
||
command.orderProductCommands.forEach { productOptionCommand -> | ||
productOptions.find { it.productId == productOptionCommand.productId }?.let { | ||
throwExceptionWhen(!it.isSame(productOptionCommand.toProductOption())) { | ||
ProductException(INVALID_PRODUCT_OPTION) | ||
} | ||
} ?: throw ProductException(INVALID_PRODUCT_OPTION) | ||
} | ||
|
||
// TODO 배송지 존재 검증 | ||
val shippingAddress = shippingAddressRepository.findByIdOrThrow( | ||
command.shippingAddressId, ShippingAddressException( | ||
ShippingAddressExceptionType.NOT_FOUND_SHIPPING_ADDRESS | ||
) | ||
) | ||
|
||
// TODO 총 가격 검증 | ||
// 1. 상품 가격 | ||
command.orderProductCommands.forEach { productCommand -> | ||
val product = productById[productCommand.productId] | ||
?: throw OrderException(PRODUCT_NOT_FOUND) | ||
val productOption = productOptions.find { it.productId == product.id } | ||
?: throw ProductException(INVALID_PRODUCT_OPTION) | ||
|
||
throwExceptionWhen( | ||
productCommand.orderPrice != (product.discountPrice + productOption.additionalPrice) * productCommand.quantity.toBigDecimal() | ||
|| productCommand.deliveryFee != product.getDeliveryFee(productCommand.deliveryMethod) | ||
) { | ||
OrderException( | ||
ORDER_PRICE_NOT_MATCH | ||
) | ||
} | ||
} | ||
|
||
// 3. 총 배송비 검증 (스토어로 묶인 뒤 배송비 검증) | ||
val groupBy = products.groupBy { product -> | ||
Pair( | ||
product.storeId, | ||
command.orderProductCommands.find { it.productId == product.id }?.deliveryMethod | ||
?: throw OrderException(PRODUCT_NOT_FOUND) | ||
) | ||
} | ||
val orderDeliveryFee = groupBy.map { (storeDeliveryMethod, products) -> | ||
val deliveryMethod = storeDeliveryMethod.second | ||
products.first().getDeliveryFee(deliveryMethod).toInt() | ||
}.sum() | ||
|
||
// 4. 총 결제 금액 검증 | ||
throwExceptionWhen(command.totalAmount != orderDeliveryFee.toBigDecimal() + command.orderProductCommands.sumOf { it.orderPrice }) { | ||
OrderException( | ||
ORDER_PRICE_NOT_MATCH | ||
) | ||
} | ||
|
||
// TODO: TODO 재고 검증 | ||
|
||
val storesById = storeRepository.findByIdIn(products.map { it.storeId }).associateBy { it.id } | ||
val orderNumber = OrderNumber.generate() | ||
val orderName = OrderName.from(products) | ||
// TODO 주문 저장 로직 | ||
val orders = command.orderProductCommands.map { productCommand -> | ||
val product = productById[productCommand.productId] | ||
?: throw OrderException(PRODUCT_NOT_FOUND) | ||
|
||
Order( | ||
memberId = command.memberId, | ||
orderNumber = orderNumber, | ||
orderName = orderName, | ||
orderShippingAddress = OrderShippingAddress.from(shippingAddress, command.shippingRequest), | ||
orderProduct = productCommand.toOrderProduct( | ||
shippingNumber = ShippingNumber.of(product.storeId, productCommand.deliveryMethod, orderNumber), | ||
product = product, | ||
storeName = storesById[product.storeId]?.name ?: throw OrderException(PRODUCT_NOT_FOUND), | ||
), | ||
isAbleToCancel = true, | ||
status = ORDER_CREATED, | ||
totalAmount = command.totalAmount, | ||
) | ||
} | ||
orderRepository.saveAll(orders) | ||
|
||
return SaveOrderResponse( | ||
orderId = orders.first().orderNumber.value, | ||
orderName = orders.first().orderName.value, | ||
successUrl = "successUrl", | ||
failUrl = "failUrl", | ||
) | ||
} | ||
} |
69 changes: 69 additions & 0 deletions
69
src/main/kotlin/com/petqua/application/order/dto/OrderDtos.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package com.petqua.application.order.dto | ||
|
||
import com.petqua.domain.delivery.DeliveryMethod | ||
import com.petqua.domain.order.OrderProduct | ||
import com.petqua.domain.order.ShippingNumber | ||
import com.petqua.domain.product.Product | ||
import com.petqua.domain.product.option.ProductOption | ||
import com.petqua.domain.product.option.Sex | ||
import java.math.BigDecimal | ||
|
||
data class SaveOrderCommand( | ||
val memberId: Long, | ||
val shippingAddressId: Long, | ||
val shippingRequest: String?, | ||
val orderProductCommands: List<OrderProductCommand>, | ||
val totalAmount: BigDecimal, | ||
) | ||
|
||
data class OrderProductCommand( | ||
val productId: Long, | ||
val quantity: Int, | ||
val originalPrice: BigDecimal, | ||
val discountRate: Int, | ||
val discountPrice: BigDecimal, | ||
val orderPrice: BigDecimal, | ||
val sex: Sex, | ||
val additionalPrice: BigDecimal, | ||
val deliveryFee: BigDecimal, | ||
val deliveryMethod: DeliveryMethod, | ||
) { | ||
|
||
fun toProductOption(): ProductOption { | ||
return ProductOption( | ||
sex = sex, | ||
productId = productId, | ||
additionalPrice = additionalPrice.setScale(2), | ||
) | ||
} | ||
|
||
fun toOrderProduct( | ||
shippingNumber: ShippingNumber, | ||
product: Product, | ||
storeName: String, | ||
): OrderProduct { | ||
return OrderProduct( | ||
quantity = quantity, | ||
originalPrice = originalPrice, | ||
discountRate = discountRate, | ||
discountPrice = discountPrice, | ||
deliveryFee = deliveryFee, | ||
shippingNumber = shippingNumber, | ||
orderPrice = orderPrice, | ||
productId = productId, | ||
productName = product.name, | ||
thumbnailUrl = product.thumbnailUrl, | ||
storeId = product.storeId, | ||
storeName = storeName, | ||
deliveryMethod = deliveryMethod, | ||
sex = sex, | ||
) | ||
} | ||
} | ||
|
||
data class SaveOrderResponse( | ||
val orderId: String, | ||
val orderName: String, | ||
val successUrl: String, | ||
val failUrl: String, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,18 @@ | ||
package com.petqua.domain.order | ||
|
||
import com.petqua.domain.product.Product | ||
import jakarta.persistence.Column | ||
import jakarta.persistence.Embeddable | ||
|
||
@Embeddable | ||
class OrderName( | ||
|
||
data class OrderName( | ||
@Column(nullable = false, unique = true) | ||
val value: String, | ||
) { | ||
|
||
//TODO : 주문 이름 생성 로직 | ||
companion object { | ||
fun from(products: List<Product>): OrderName { | ||
return OrderName("${products.first().name} 외 ${products.size - 1}건") | ||
} | ||
} | ||
} |
Oops, something went wrong.