Skip to content

Commit

Permalink
MLT-0053 Add use-case for moving items (#37)
Browse files Browse the repository at this point in the history
* Add port for moving items

This use-case is mainly in-place to handle batch-updates from SynQ

* Fix move item query, use coroutine

* Add tests for moving items

* Address reviews

- Handle case with negative product quantities
- Handle case where location is blank

* Rename item id list query

doAllItemsExist -> doesEveryItemExist

* Add validation tests

* Update src/test/kotlin/no/nb/mlt/wls/domain/WLSServiceTest.kt

---------

Co-authored-by: Daniel Aaron Salwerowicz <[email protected]>
  • Loading branch information
anotheroneofthese and MormonJesus69420 authored Oct 4, 2024
1 parent caa5521 commit 90db5b8
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses
import io.swagger.v3.oas.annotations.tags.Tag
import no.nb.mlt.wls.domain.ports.inbound.AddNewItem
import no.nb.mlt.wls.domain.ports.inbound.GetItem
import no.nb.mlt.wls.domain.ports.inbound.MoveItem
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PostMapping
Expand All @@ -21,7 +22,8 @@ import org.springframework.web.server.ServerWebInputException
@Tag(name = "Product Controller", description = "API for managing products in Hermes WLS")
class ProductController(
private val addNewItem: AddNewItem,
private val getItem: GetItem
private val getItem: GetItem,
private val moveItem: MoveItem
) {
@Operation(
summary = "Register a product in the storage system",
Expand Down
24 changes: 21 additions & 3 deletions src/main/kotlin/no/nb/mlt/wls/domain/WLSService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import no.nb.mlt.wls.domain.ports.inbound.DeleteOrder
import no.nb.mlt.wls.domain.ports.inbound.GetItem
import no.nb.mlt.wls.domain.ports.inbound.GetOrder
import no.nb.mlt.wls.domain.ports.inbound.ItemMetadata
import no.nb.mlt.wls.domain.ports.inbound.ItemNotFoundException
import no.nb.mlt.wls.domain.ports.inbound.MoveItem
import no.nb.mlt.wls.domain.ports.inbound.OrderNotFoundException
import no.nb.mlt.wls.domain.ports.inbound.ServerException
import no.nb.mlt.wls.domain.ports.inbound.UpdateOrder
Expand All @@ -32,7 +34,7 @@ class WLSService(
private val itemRepository: ItemRepository,
private val orderRepository: OrderRepository,
private val storageSystemFacade: StorageSystemFacade
) : AddNewItem, CreateOrder, DeleteOrder, UpdateOrder, GetOrder, GetItem {
) : AddNewItem, CreateOrder, DeleteOrder, UpdateOrder, GetOrder, GetItem, MoveItem {
override suspend fun addItem(itemMetadata: ItemMetadata): Item {
getItem(itemMetadata.hostName, itemMetadata.hostId)?.let {
logger.info { "Item already exists: $it" }
Expand All @@ -53,14 +55,30 @@ class WLSService(
.awaitSingle()
}

override suspend fun moveItem(
hostId: String,
hostName: HostName,
quantity: Double,
location: String
): Item {
if (quantity < 0.0) {
throw ValidationException("Quantity can not be negative")
}
if (location.isBlank()) {
throw ValidationException("Location can not be blank")
}
val item = getItem(hostName, hostId) ?: throw ItemNotFoundException("Item with id '$hostId' does not exist for '$hostName'")
return itemRepository.moveItem(item.hostId, item.hostName, quantity, location)
}

override suspend fun createOrder(orderDTO: CreateOrderDTO): Order {
orderRepository.getOrder(orderDTO.hostName, orderDTO.hostOrderId)?.let {
logger.info { "Order already exists: $it" }
return it
}

val itemIds = orderDTO.orderItems.map { ItemId(orderDTO.hostName, it.hostId) }
if (!itemRepository.doesAllItemsExist(itemIds)) {
if (!itemRepository.doesEveryItemExist(itemIds)) {
throw ValidationException("All order items in order must exist")
}

Expand Down Expand Up @@ -92,7 +110,7 @@ class WLSService(
callbackUrl: String
): Order {
val itemIds = itemHostIds.map { ItemId(hostName, it) }
if (!itemRepository.doesAllItemsExist(itemIds)) {
if (!itemRepository.doesEveryItemExist(itemIds)) {
throw ValidationException("All order items in order must exist")
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package no.nb.mlt.wls.domain.ports.inbound

class ItemNotFoundException(override val message: String) : RuntimeException(message)
21 changes: 21 additions & 0 deletions src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/MoveItem.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package no.nb.mlt.wls.domain.ports.inbound

import no.nb.mlt.wls.domain.model.HostName
import no.nb.mlt.wls.domain.model.Item

/**
* This port is used for handling messages regarding items moving
* inside the storage system.
* Some examples include handling status messages when crates arrive
* at picking stations, or when items return to the
* storage systems.
* In both cases we want to know where the item went, and if the count changed
*/
interface MoveItem {
suspend fun moveItem(
hostId: String,
hostName: HostName,
quantity: Double,
location: String
): Item
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,4 @@ interface UpdateOrder {
): Order
}

class ValidationException(override val message: String, cause: Throwable? = null) : RuntimeException(message, cause)

class IllegalOrderStateException(override val message: String, cause: Throwable? = null) : RuntimeException(message, cause)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package no.nb.mlt.wls.domain.ports.inbound

class ValidationException(override val message: String, cause: Throwable? = null) : RuntimeException(message, cause)
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,16 @@ interface ItemRepository {

fun createItem(item: Item): Mono<Item>

suspend fun doesAllItemsExist(ids: List<ItemId>): Boolean
suspend fun doesEveryItemExist(ids: List<ItemId>): Boolean

suspend fun moveItem(
hostId: String,
hostName: HostName,
quantity: Double,
location: String
): Item
}

data class ItemId(val hostName: HostName, val hostId: String)

class ItemMovingException(message: String, cause: Throwable? = null) : RuntimeException(message, cause)
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import kotlinx.coroutines.reactor.awaitSingle
import kotlinx.coroutines.reactor.awaitSingleOrNull
import no.nb.mlt.wls.domain.model.HostName
import no.nb.mlt.wls.domain.model.Item
import no.nb.mlt.wls.domain.ports.inbound.ItemNotFoundException
import no.nb.mlt.wls.domain.ports.outbound.ItemId
import no.nb.mlt.wls.domain.ports.outbound.ItemMovingException
import no.nb.mlt.wls.domain.ports.outbound.ItemRepository
import org.springframework.data.mongodb.repository.Query
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
import org.springframework.data.mongodb.repository.Update
import org.springframework.stereotype.Component
import org.springframework.stereotype.Repository
import reactor.core.publisher.Mono
Expand Down Expand Up @@ -41,7 +44,7 @@ class ItemRepositoryMongoAdapter(
return mongoRepo.save(item.toMongoItem()).map(MongoItem::toItem)
}

override suspend fun doesAllItemsExist(ids: List<ItemId>): Boolean {
override suspend fun doesEveryItemExist(ids: List<ItemId>): Boolean {
return mongoRepo.countItemsMatchingIds(ids)
.map {
logger.debug { "Counted items matching ids: $ids, count: $it" }
Expand All @@ -55,6 +58,30 @@ class ItemRepositoryMongoAdapter(
}
.awaitSingle()
}

override suspend fun moveItem(
hostId: String,
hostName: HostName,
quantity: Double,
location: String
): Item {
mongoRepo
.findAndUpdateItemByHostNameAndHostId(hostId, hostName, quantity, location)
.timeout(Duration.ofSeconds(8))
.doOnError {
logger.error(it) {
if (it is TimeoutException) {
"Timed out while updating Item. Order ID: $hostId, Host: $hostName"
} else {
"Error while updating order"
}
}
}
.onErrorMap { ItemMovingException(it.message ?: "Item could not be moved", it) }
.awaitSingleOrNull() ?: ItemNotFoundException("Item with host ID $hostId for $hostName does not exist in WLS database")

return getItem(hostName, hostId)!!
}
}

@Repository
Expand All @@ -66,4 +93,13 @@ interface ItemMongoRepository : ReactiveMongoRepository<MongoItem, String> {

@Query(count = true, value = "{ '\$or': ?0 }")
fun countItemsMatchingIds(ids: List<ItemId>): Mono<Long>

@Query("{hostName: ?0, hostOrderId: ?1}")
@Update("{'\$set':{quantity: ?2,location: ?3}}")
fun findAndUpdateItemByHostNameAndHostId(
hostOrderId: String,
hostName: HostName,
quantity: Double,
location: String
): Mono<Void>
}
Loading

0 comments on commit 90db5b8

Please sign in to comment.