diff --git a/src/main/kotlin/com/kioschool/kioschoolapi/domain/product/controller/AdminProductController.kt b/src/main/kotlin/com/kioschool/kioschoolapi/domain/product/controller/AdminProductController.kt
index 0caed7cc..33c435c8 100644
--- a/src/main/kotlin/com/kioschool/kioschoolapi/domain/product/controller/AdminProductController.kt
+++ b/src/main/kotlin/com/kioschool/kioschoolapi/domain/product/controller/AdminProductController.kt
@@ -82,7 +82,11 @@ class AdminProductController(
)
}
- @Operation(summary = "상품 판매 여부 수정", description = "상품의 판매 여부를 수정합니다.")
+ @Operation(
+ summary = "상품 판매 여부 수정",
+ description = "상품의 판매 여부를 수정합니다. deprecated: 상품 상태 수정 API로 대체됩니다.",
+ deprecated = true
+ )
@PutMapping("/product/sellable")
fun updateProductSellable(
@AdminUsername username: String,
@@ -96,6 +100,20 @@ class AdminProductController(
)
}
+ @Operation(summary = "상품 상태 수정", description = "상품의 상태를 수정합니다.")
+ @PutMapping("/product/status")
+ fun updateProductStatus(
+ @AdminUsername username: String,
+ @RequestBody body: UpdateProductStatusRequestBody,
+ ): ProductDto {
+ return productFacade.updateProductStatus(
+ username,
+ body.workspaceId,
+ body.productId,
+ body.status
+ )
+ }
+
@Operation(summary = "상품 삭제", description = "상품을 삭제합니다.")
@DeleteMapping("/product")
fun deleteProduct(
diff --git a/src/main/kotlin/com/kioschool/kioschoolapi/domain/product/dto/common/ProductDto.kt b/src/main/kotlin/com/kioschool/kioschoolapi/domain/product/dto/common/ProductDto.kt
index 6654c5bd..3a2b0f27 100644
--- a/src/main/kotlin/com/kioschool/kioschoolapi/domain/product/dto/common/ProductDto.kt
+++ b/src/main/kotlin/com/kioschool/kioschoolapi/domain/product/dto/common/ProductDto.kt
@@ -1,7 +1,7 @@
package com.kioschool.kioschoolapi.domain.product.dto.common
-import com.kioschool.kioschoolapi.domain.product.dto.common.ProductCategoryDto
import com.kioschool.kioschoolapi.domain.product.entity.Product
+import com.kioschool.kioschoolapi.global.common.enums.ProductStatus
import java.time.LocalDateTime
data class ProductDto(
@@ -11,6 +11,7 @@ data class ProductDto(
val price: Int,
val imageUrl: String?,
val isSellable: Boolean?,
+ val status: ProductStatus?,
val productCategory: ProductCategoryDto?,
val createdAt: LocalDateTime?,
val updatedAt: LocalDateTime?
@@ -24,6 +25,7 @@ data class ProductDto(
price = product.price,
imageUrl = product.imageUrl,
isSellable = product.isSellable,
+ status = product.status,
productCategory = product.productCategory?.let { ProductCategoryDto.of(it) },
createdAt = product.createdAt,
updatedAt = product.updatedAt
diff --git a/src/main/kotlin/com/kioschool/kioschoolapi/domain/product/dto/request/UpdateProductStatusRequestBody.kt b/src/main/kotlin/com/kioschool/kioschoolapi/domain/product/dto/request/UpdateProductStatusRequestBody.kt
new file mode 100644
index 00000000..2fbeb0a9
--- /dev/null
+++ b/src/main/kotlin/com/kioschool/kioschoolapi/domain/product/dto/request/UpdateProductStatusRequestBody.kt
@@ -0,0 +1,9 @@
+package com.kioschool.kioschoolapi.domain.product.dto.request
+
+import com.kioschool.kioschoolapi.global.common.enums.ProductStatus
+
+class UpdateProductStatusRequestBody(
+ val workspaceId: Long,
+ val productId: Long,
+ val status: ProductStatus
+)
\ No newline at end of file
diff --git a/src/main/kotlin/com/kioschool/kioschoolapi/domain/product/entity/Product.kt b/src/main/kotlin/com/kioschool/kioschoolapi/domain/product/entity/Product.kt
index aed45c76..a615cbce 100644
--- a/src/main/kotlin/com/kioschool/kioschoolapi/domain/product/entity/Product.kt
+++ b/src/main/kotlin/com/kioschool/kioschoolapi/domain/product/entity/Product.kt
@@ -3,6 +3,7 @@ package com.kioschool.kioschoolapi.domain.product.entity
import com.fasterxml.jackson.annotation.JsonIgnore
import com.kioschool.kioschoolapi.domain.workspace.entity.Workspace
import com.kioschool.kioschoolapi.global.common.entity.BaseEntity
+import com.kioschool.kioschoolapi.global.common.enums.ProductStatus
import jakarta.persistence.Entity
import jakarta.persistence.ManyToOne
import jakarta.persistence.Table
@@ -15,6 +16,7 @@ class Product(
var price: Int,
var imageUrl: String? = null,
var isSellable: Boolean? = true,
+ var status: ProductStatus = ProductStatus.SELLING,
@ManyToOne
@JsonIgnore
val workspace: Workspace,
diff --git a/src/main/kotlin/com/kioschool/kioschoolapi/domain/product/facade/ProductFacade.kt b/src/main/kotlin/com/kioschool/kioschoolapi/domain/product/facade/ProductFacade.kt
index 175147a1..1409e3cf 100644
--- a/src/main/kotlin/com/kioschool/kioschoolapi/domain/product/facade/ProductFacade.kt
+++ b/src/main/kotlin/com/kioschool/kioschoolapi/domain/product/facade/ProductFacade.kt
@@ -8,6 +8,7 @@ import com.kioschool.kioschoolapi.domain.workspace.exception.WorkspaceInaccessib
import com.kioschool.kioschoolapi.domain.workspace.service.WorkspaceService
import com.kioschool.kioschoolapi.global.aws.S3Service
import com.kioschool.kioschoolapi.global.cache.constant.CacheNames
+import com.kioschool.kioschoolapi.global.common.enums.ProductStatus
import org.springframework.cache.annotation.Cacheable
import org.springframework.stereotype.Component
import org.springframework.web.multipart.MultipartFile
@@ -167,4 +168,17 @@ class ProductFacade(
return productService.saveProductCategories(productCategories)
.map { ProductCategoryDto.of(it) }
}
+
+ fun updateProductStatus(
+ username: String,
+ workspaceId: Long,
+ productId: Long,
+ status: ProductStatus
+ ): ProductDto {
+ val product = productService.getProduct(productId)
+ workspaceService.checkAccessible(username, workspaceId)
+
+ product.status = status
+ return ProductDto.of(productService.saveProduct(product))
+ }
}
\ No newline at end of file
diff --git a/src/main/kotlin/com/kioschool/kioschoolapi/global/common/enums/ProductStatus.kt b/src/main/kotlin/com/kioschool/kioschoolapi/global/common/enums/ProductStatus.kt
new file mode 100644
index 00000000..a59dadb2
--- /dev/null
+++ b/src/main/kotlin/com/kioschool/kioschoolapi/global/common/enums/ProductStatus.kt
@@ -0,0 +1,7 @@
+package com.kioschool.kioschoolapi.global.common.enums
+
+enum class ProductStatus {
+ SELLING,
+ SOLD_OUT,
+ HIDDEN,
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/kioschool/kioschoolapi/global/schedule/script/InitProductStatus.kt b/src/main/kotlin/com/kioschool/kioschoolapi/global/schedule/script/InitProductStatus.kt
new file mode 100644
index 00000000..e54e5060
--- /dev/null
+++ b/src/main/kotlin/com/kioschool/kioschoolapi/global/schedule/script/InitProductStatus.kt
@@ -0,0 +1,25 @@
+package com.kioschool.kioschoolapi.global.schedule.script
+
+import com.kioschool.kioschoolapi.domain.product.repository.ProductRepository
+import com.kioschool.kioschoolapi.global.common.enums.ProductStatus
+import com.kioschool.kioschoolapi.global.schedule.Runnable
+import org.springframework.stereotype.Component
+
+@Component
+class InitProductStatus(
+ private val productRepository: ProductRepository
+) : Runnable {
+ override fun run() {
+ val products = productRepository.findAll()
+ products.filter {
+ it.status == null
+ }.forEach {
+ it.status = if (it.isSellable == true) {
+ ProductStatus.SELLING
+ } else {
+ ProductStatus.HIDDEN
+ }
+ }
+ productRepository.saveAll(products)
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/2025/11/22-01-changelog.xml b/src/main/resources/db/changelog/2025/11/22-01-changelog.xml
new file mode 100644
index 00000000..6f1f3920
--- /dev/null
+++ b/src/main/resources/db/changelog/2025/11/22-01-changelog.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/kotlin/com/kioschool/kioschoolapi/product/facade/ProductFacadeTest.kt b/src/test/kotlin/com/kioschool/kioschoolapi/product/facade/ProductFacadeTest.kt
index e7b4ba77..6035701f 100644
--- a/src/test/kotlin/com/kioschool/kioschoolapi/product/facade/ProductFacadeTest.kt
+++ b/src/test/kotlin/com/kioschool/kioschoolapi/product/facade/ProductFacadeTest.kt
@@ -8,6 +8,7 @@ import com.kioschool.kioschoolapi.domain.workspace.exception.WorkspaceInaccessib
import com.kioschool.kioschoolapi.domain.workspace.service.WorkspaceService
import com.kioschool.kioschoolapi.factory.SampleEntity
import com.kioschool.kioschoolapi.global.aws.S3Service
+import com.kioschool.kioschoolapi.global.common.enums.ProductStatus
import io.kotest.core.spec.style.DescribeSpec
import io.mockk.*
import org.junit.jupiter.api.assertThrows
@@ -680,4 +681,49 @@ class ProductFacadeTest : DescribeSpec({
verify(exactly = 0) { productService.saveProductCategories(any()) }
}
}
+
+ describe("updateProductStatus") {
+ it("should update product status") {
+ val username = "username"
+ val workspaceId = 1L
+ val productId = 1L
+ val status = ProductStatus.SOLD_OUT
+ val product = SampleEntity.product
+ every { productService.getProduct(productId) } returns product
+ every { workspaceService.checkAccessible(username, workspaceId) } just Runs
+ every { productService.saveProduct(product) } returns product
+
+ val result = sut.updateProductStatus(username, workspaceId, productId, status)
+
+ assert(result.id == product.id)
+ assert(result.status == status)
+
+ verify { productService.getProduct(productId) }
+ verify { workspaceService.checkAccessible(username, workspaceId) }
+ verify { productService.saveProduct(product) }
+ }
+
+ it("should throw WorkspaceInaccessibleException") {
+ val username = "username"
+ val workspaceId = 1L
+ val productId = 1L
+ val status = ProductStatus.SOLD_OUT
+ val product = SampleEntity.product
+ every { productService.getProduct(productId) } returns product
+ every {
+ workspaceService.checkAccessible(
+ username,
+ workspaceId
+ )
+ } throws WorkspaceInaccessibleException()
+
+ assertThrows {
+ sut.updateProductStatus(username, workspaceId, productId, status)
+ }
+
+ verify { productService.getProduct(productId) }
+ verify { workspaceService.checkAccessible(username, workspaceId) }
+ verify(exactly = 0) { productService.saveProduct(any()) }
+ }
+ }
})
\ No newline at end of file