From 73df5ac2fffe4c7bf831c8f60d1b8e46183ddcbd Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 27 Dec 2024 11:01:22 +0100 Subject: [PATCH 1/8] Do not show items that's been refunded already --- .../payments/refunds/IssueRefundViewModel.kt | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt index 9f0be15d165c..5de4b95ffa18 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt @@ -1,5 +1,6 @@ package com.woocommerce.android.ui.payments.refunds +import org.wordpress.android.fluxc.utils.sumBy as sumByBigDecimal import android.os.Parcelable import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -76,8 +77,6 @@ import java.math.BigDecimal import java.util.Locale import javax.inject.Inject import kotlin.collections.set -import kotlin.math.min -import org.wordpress.android.fluxc.utils.sumBy as sumByBigDecimal @HiltViewModel @Suppress("LargeClass") // TODO Refactor this class in a follow up PR @@ -284,18 +283,7 @@ class IssueRefundViewModel @Inject constructor( ) } - val items = order.items.map { - val maxQuantity = maxQuantities[it.itemId] ?: 0f - val selectedQuantity = min(selectedQuantities[it.itemId] ?: 0, maxQuantity.toInt()) - ProductRefundListItem( - orderItem = it, - maxQuantity = maxQuantity, - quantity = selectedQuantity, - subtotal = formatCurrency(BigDecimal.ZERO), - taxes = formatCurrency(BigDecimal.ZERO) - ) - } - updateRefundItems(items) + updateRefundItems(determineRefundableOrderItems(order, refunds)) /* Grab all shipping lines listed in the Order, but remove those that are already refunded previously) */ val shippingLines = order.shippingLines @@ -327,6 +315,29 @@ class IssueRefundViewModel @Inject constructor( } } + private fun determineRefundableOrderItems(order: Order, refunds: List): List { + val allRefundedItems = refunds.flatMap { it.items } + + return order.items.mapNotNull { orderItem -> + val timesRefunded = allRefundedItems + .filter { it.productId == orderItem.productId } + .sumOf { it.quantity } + + val quantityLeftToRefund = orderItem.quantity - timesRefunded + + if (quantityLeftToRefund <= 0) null + else { + ProductRefundListItem( + orderItem = orderItem, + quantity = 0, + maxQuantity = quantityLeftToRefund.toFloat(), + subtotal = formatCurrency(BigDecimal.ZERO), + taxes = formatCurrency(BigDecimal.ZERO) + ) + } + } + } + private fun orderContainsOnlyCustomAmounts(): Boolean { return order.items.isEmpty() && order.shippingLines.isEmpty() && order.feesLines.isNotEmpty() } From f2771923f4212e35f7edea359eca4eace775b620 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 27 Dec 2024 12:09:53 +0100 Subject: [PATCH 2/8] Fixed the quantaties. Added tests --- .../payments/refunds/IssueRefundViewModel.kt | 4 +- .../refunds/IssueRefundViewModelTest.kt | 178 +++++++++++++++++- 2 files changed, 180 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt index 5de4b95ffa18..6d9187ddd9b3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt @@ -327,10 +327,12 @@ class IssueRefundViewModel @Inject constructor( if (quantityLeftToRefund <= 0) null else { + val maxQuantity = maxQuantities[orderItem.itemId] ?: 0f + val selectedQuantity = kotlin.math.min(selectedQuantities[orderItem.itemId] ?: 0, maxQuantity.toInt()) ProductRefundListItem( orderItem = orderItem, - quantity = 0, maxQuantity = quantityLeftToRefund.toFloat(), + quantity = selectedQuantity, subtotal = formatCurrency(BigDecimal.ZERO), taxes = formatCurrency(BigDecimal.ZERO) ) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModelTest.kt index 7469797760f6..2deb0fe11f23 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModelTest.kt @@ -7,6 +7,7 @@ import com.woocommerce.android.analytics.AnalyticsTrackerWrapper import com.woocommerce.android.extensions.isEqualTo import com.woocommerce.android.model.AmbiguousLocation import com.woocommerce.android.model.Location +import com.woocommerce.android.model.Order import com.woocommerce.android.model.OrderMapper import com.woocommerce.android.tools.NetworkStatus import com.woocommerce.android.tools.SelectedSite @@ -26,9 +27,11 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import org.wordpress.android.fluxc.model.OrderEntity import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.model.metadata.WCMetaData import org.wordpress.android.fluxc.model.refunds.WCRefundModel +import org.wordpress.android.fluxc.model.refunds.WCRefundModel.WCRefundItem import org.wordpress.android.fluxc.network.BaseRequest import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooError import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooErrorType @@ -78,7 +81,9 @@ class IssueRefundViewModelTest : BaseUnitTest() { private lateinit var viewModel: IssueRefundViewModel - private fun initViewModel() { + private fun initViewModel( + orderMapper: OrderMapper = this.orderMapper + ) { whenever(selectedSite.get()).thenReturn(SiteModel()) whenever(currencyFormatter.buildBigDecimalFormatter(any())).thenReturn { "" } @@ -948,4 +953,175 @@ class IssueRefundViewModelTest : BaseUnitTest() { ) } } + + @Test + fun `given order with zero refunded items, when vm init, then all items are shown`() = testBlocking { + // GIVEN + val items = listOf( + mock { + on { quantity }.thenReturn(1.0F) + }, + mock { + on { quantity }.thenReturn(1.0F) + }, + mock { + on { quantity }.thenReturn(1.0F) + }, + ) + val order = mock { + on { this.items }.thenReturn(items) + on { this.total }.thenReturn(BigDecimal.TEN) + on { this.refundTotal }.thenReturn(BigDecimal.ZERO) + on { this.currency }.thenReturn("USD") + on { this.shippingTotal }.thenReturn(BigDecimal.ZERO) + on { this.feesTotal }.thenReturn(BigDecimal.ZERO) + on { this.paymentMethod }.thenReturn("cod") + } + val orderEntity = mock() + val orderMapper = mock { + on { toAppModel(orderEntity) }.thenReturn(order) + } + whenever(orderStore.getOrderByIdAndSite(any(), any())).thenReturn(orderEntity) + whenever(refundStore.getAllRefunds(any(), any())).thenReturn(emptyList()) + + // WHEN + initViewModel(orderMapper) + + // THEN + assertThat(viewModel.refundItems.value).hasSize(3) + } + + @Test + fun `given order with 1 refunded items, when vm init, then all items minus one are shown`() = testBlocking { + // GIVEN + val items = listOf( + mock { + on { productId }.thenReturn(1L) + on { quantity }.thenReturn(1.0F) + }, + mock { + on { quantity }.thenReturn(1.0F) + }, + mock { + on { quantity }.thenReturn(1.0F) + }, + ) + val order = mock { + on { this.items }.thenReturn(items) + on { this.total }.thenReturn(BigDecimal.TEN) + on { this.refundTotal }.thenReturn(BigDecimal.ZERO) + on { this.currency }.thenReturn("USD") + on { this.shippingTotal }.thenReturn(BigDecimal.ZERO) + on { this.feesTotal }.thenReturn(BigDecimal.ZERO) + on { this.paymentMethod }.thenReturn("cod") + } + val orderEntity = mock() + val orderMapper = mock { + on { toAppModel(orderEntity) }.thenReturn(order) + } + val refundedItems = listOf( + mock { + on { productId }.thenReturn(1L) + on { quantity }.thenReturn(-1) + on { subtotal }.thenReturn(BigDecimal.ZERO) + on { total }.thenReturn(BigDecimal.ZERO) + on { sku }.thenReturn("") + on { price }.thenReturn(BigDecimal.ZERO) + on { totalTax }.thenReturn(BigDecimal.ZERO) + on { metaData }.thenReturn(null) + } + ) + val refund = WCRefundModel( + id = 1L, + dateCreated = Date(), + amount = BigDecimal.ZERO, + reason = "", + automaticGatewayRefund = false, + items = refundedItems, + shippingLineItems = listOf(), + feeLineItems = listOf() + ) + whenever(orderStore.getOrderByIdAndSite(any(), any())).thenReturn(orderEntity) + whenever(refundStore.getAllRefunds(any(), any())).thenReturn(listOf(refund)) + + // WHEN + initViewModel(orderMapper) + + // THEN + assertThat(viewModel.refundItems.value).hasSize(2) + } + + @Test + fun `given order with 6 product each and 2 fully refunded, when vm init, then 1 product with 2 items shown`() = + testBlocking { + // GIVEN + val items = listOf( + mock { + on { productId }.thenReturn(1L) + on { quantity }.thenReturn(2.0F) + }, + mock { + on { productId }.thenReturn(2L) + on { quantity }.thenReturn(2.0F) + }, + mock { + on { productId }.thenReturn(3L) + on { quantity }.thenReturn(2.0F) + }, + ) + val order = mock { + on { this.items }.thenReturn(items) + on { this.total }.thenReturn(BigDecimal.TEN) + on { this.refundTotal }.thenReturn(BigDecimal.ZERO) + on { this.currency }.thenReturn("USD") + on { this.shippingTotal }.thenReturn(BigDecimal.ZERO) + on { this.feesTotal }.thenReturn(BigDecimal.ZERO) + on { this.paymentMethod }.thenReturn("cod") + } + val orderEntity = mock() + val orderMapper = mock { + on { toAppModel(orderEntity) }.thenReturn(order) + } + val refundedItems = listOf( + mock { + on { productId }.thenReturn(1L) + on { quantity }.thenReturn(-2) + on { subtotal }.thenReturn(BigDecimal.ZERO) + on { total }.thenReturn(BigDecimal.ZERO) + on { sku }.thenReturn("") + on { price }.thenReturn(BigDecimal.ZERO) + on { totalTax }.thenReturn(BigDecimal.ZERO) + on { metaData }.thenReturn(null) + }, + mock { + on { productId }.thenReturn(2L) + on { quantity }.thenReturn(-2) + on { subtotal }.thenReturn(BigDecimal.ZERO) + on { total }.thenReturn(BigDecimal.ZERO) + on { sku }.thenReturn("") + on { price }.thenReturn(BigDecimal.ZERO) + on { totalTax }.thenReturn(BigDecimal.ZERO) + on { metaData }.thenReturn(null) + }, + ) + val refund = WCRefundModel( + id = 1L, + dateCreated = Date(), + amount = BigDecimal.ZERO, + reason = "", + automaticGatewayRefund = false, + items = refundedItems, + shippingLineItems = listOf(), + feeLineItems = listOf() + ) + whenever(orderStore.getOrderByIdAndSite(any(), any())).thenReturn(orderEntity) + whenever(refundStore.getAllRefunds(any(), any())).thenReturn(listOf(refund)) + + // WHEN + initViewModel(orderMapper) + + // THEN + assertThat(viewModel.refundItems.value).hasSize(1) + assertThat(viewModel.refundItems.value!!.first().maxQuantity).isEqualTo(2.0F) + } } From f832bf6bbcb88ca1fe7126e5d6cb641e3fed2d87 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 27 Dec 2024 14:45:13 +0100 Subject: [PATCH 3/8] Fixed formatting --- .../android/ui/payments/refunds/IssueRefundViewModel.kt | 7 ++++--- .../ui/payments/refunds/IssueRefundViewModelTest.kt | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt index 6d9187ddd9b3..8fafdd7cf1d4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt @@ -1,6 +1,5 @@ package com.woocommerce.android.ui.payments.refunds -import org.wordpress.android.fluxc.utils.sumBy as sumByBigDecimal import android.os.Parcelable import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -77,6 +76,7 @@ import java.math.BigDecimal import java.util.Locale import javax.inject.Inject import kotlin.collections.set +import org.wordpress.android.fluxc.utils.sumBy as sumByBigDecimal @HiltViewModel @Suppress("LargeClass") // TODO Refactor this class in a follow up PR @@ -325,8 +325,9 @@ class IssueRefundViewModel @Inject constructor( val quantityLeftToRefund = orderItem.quantity - timesRefunded - if (quantityLeftToRefund <= 0) null - else { + if (quantityLeftToRefund <= 0) { + null + } else { val maxQuantity = maxQuantities[orderItem.itemId] ?: 0f val selectedQuantity = kotlin.math.min(selectedQuantities[orderItem.itemId] ?: 0, maxQuantity.toInt()) ProductRefundListItem( diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModelTest.kt index 2deb0fe11f23..1eccfcb06fa0 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModelTest.kt @@ -1052,6 +1052,7 @@ class IssueRefundViewModelTest : BaseUnitTest() { } @Test + @Suppress("LongMethod") fun `given order with 6 product each and 2 fully refunded, when vm init, then 1 product with 2 items shown`() = testBlocking { // GIVEN From 76220fb3bf26a183371480bf2a5070192f07deae Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 7 Jan 2025 16:20:50 +0100 Subject: [PATCH 4/8] Modify the method that calculates the max quantity for each item by subtracting the number of already-refunded items --- .../com/woocommerce/android/model/Refund.kt | 21 +++++++--- .../payments/refunds/IssueRefundViewModel.kt | 42 +++++++------------ 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/Refund.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/Refund.kt index 1ffa8fda5480..01f184e72121 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/Refund.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/Refund.kt @@ -147,10 +147,19 @@ fun List.getNonRefundedProducts( fun List.getMaxRefundQuantities( products: List ): Map { - val map = mutableMapOf() - val groupedRefunds = this.flatMap { it.items }.groupBy { it.orderItemId } - products.map { item -> - map[item.itemId] = item.quantity - (groupedRefunds[item.itemId]?.sumOf { it.quantity } ?: 0) - } - return map + val allRefundedItems = this.flatMap { it.items } + + return products.mapNotNull { product -> + val refundedQuantity = allRefundedItems + .filter { it.productId == product.productId } + .sumOf { it.quantity } + + val quantityLeftToRefund = product.quantity - refundedQuantity + + if (quantityLeftToRefund > 0) { + product.itemId to quantityLeftToRefund.toFloat() + } else { + null + } + }.toMap() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt index 8fafdd7cf1d4..e40a183bd3cb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt @@ -1,5 +1,6 @@ package com.woocommerce.android.ui.payments.refunds +import org.wordpress.android.fluxc.utils.sumBy as sumByBigDecimal import android.os.Parcelable import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -76,7 +77,7 @@ import java.math.BigDecimal import java.util.Locale import javax.inject.Inject import kotlin.collections.set -import org.wordpress.android.fluxc.utils.sumBy as sumByBigDecimal +import kotlin.math.min @HiltViewModel @Suppress("LargeClass") // TODO Refactor this class in a follow up PR @@ -283,7 +284,18 @@ class IssueRefundViewModel @Inject constructor( ) } - updateRefundItems(determineRefundableOrderItems(order, refunds)) + val items = order.items.map { + val maxQuantity = maxQuantities[it.itemId] ?: 0f + val selectedQuantity = min(selectedQuantities[it.itemId] ?: 0, maxQuantity.toInt()) + ProductRefundListItem( + orderItem = it, + maxQuantity = maxQuantity, + quantity = selectedQuantity, + subtotal = formatCurrency(BigDecimal.ZERO), + taxes = formatCurrency(BigDecimal.ZERO) + ) + } + updateRefundItems(items) /* Grab all shipping lines listed in the Order, but remove those that are already refunded previously) */ val shippingLines = order.shippingLines @@ -315,32 +327,6 @@ class IssueRefundViewModel @Inject constructor( } } - private fun determineRefundableOrderItems(order: Order, refunds: List): List { - val allRefundedItems = refunds.flatMap { it.items } - - return order.items.mapNotNull { orderItem -> - val timesRefunded = allRefundedItems - .filter { it.productId == orderItem.productId } - .sumOf { it.quantity } - - val quantityLeftToRefund = orderItem.quantity - timesRefunded - - if (quantityLeftToRefund <= 0) { - null - } else { - val maxQuantity = maxQuantities[orderItem.itemId] ?: 0f - val selectedQuantity = kotlin.math.min(selectedQuantities[orderItem.itemId] ?: 0, maxQuantity.toInt()) - ProductRefundListItem( - orderItem = orderItem, - maxQuantity = quantityLeftToRefund.toFloat(), - quantity = selectedQuantity, - subtotal = formatCurrency(BigDecimal.ZERO), - taxes = formatCurrency(BigDecimal.ZERO) - ) - } - } - } - private fun orderContainsOnlyCustomAmounts(): Boolean { return order.items.isEmpty() && order.shippingLines.isEmpty() && order.feesLines.isNotEmpty() } From 67934a52ecf8f7341fdc579f4cfe909b4eb96d1f Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 7 Jan 2025 16:43:44 +0100 Subject: [PATCH 5/8] Added a test. Fixed the usage of the adjusted method --- .../payments/refunds/IssueRefundViewModel.kt | 4 +- .../refunds/IssueRefundViewModelTest.kt | 110 ++++++++++++++++++ 2 files changed, 112 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt index e40a183bd3cb..ccb855a849db 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt @@ -284,8 +284,8 @@ class IssueRefundViewModel @Inject constructor( ) } - val items = order.items.map { - val maxQuantity = maxQuantities[it.itemId] ?: 0f + val items = order.items.mapNotNull { + val maxQuantity = maxQuantities[it.itemId] ?: return@mapNotNull null val selectedQuantity = min(selectedQuantities[it.itemId] ?: 0, maxQuantity.toInt()) ProductRefundListItem( orderItem = it, diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModelTest.kt index 1eccfcb06fa0..012d498ea9e9 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModelTest.kt @@ -997,12 +997,17 @@ class IssueRefundViewModelTest : BaseUnitTest() { val items = listOf( mock { on { productId }.thenReturn(1L) + on { itemId }.thenReturn(1L) on { quantity }.thenReturn(1.0F) }, mock { + on { productId }.thenReturn(2L) + on { itemId }.thenReturn(2L) on { quantity }.thenReturn(1.0F) }, mock { + on { productId }.thenReturn(3L) + on { itemId }.thenReturn(3L) on { quantity }.thenReturn(1.0F) }, ) @@ -1059,14 +1064,17 @@ class IssueRefundViewModelTest : BaseUnitTest() { val items = listOf( mock { on { productId }.thenReturn(1L) + on { itemId }.thenReturn(1L) on { quantity }.thenReturn(2.0F) }, mock { on { productId }.thenReturn(2L) + on { itemId }.thenReturn(2L) on { quantity }.thenReturn(2.0F) }, mock { on { productId }.thenReturn(3L) + on { itemId }.thenReturn(3L) on { quantity }.thenReturn(2.0F) }, ) @@ -1125,4 +1133,106 @@ class IssueRefundViewModelTest : BaseUnitTest() { assertThat(viewModel.refundItems.value).hasSize(1) assertThat(viewModel.refundItems.value!!.first().maxQuantity).isEqualTo(2.0F) } + + @Test + fun `given order with 5 product each and some fully refunded some partially, when vm init, then the rest are shown`() = + testBlocking { + // GIVEN + val items = listOf( + mock { + on { productId }.thenReturn(1L) + on { itemId }.thenReturn(1L) + on { quantity }.thenReturn(2.0F) + }, + mock { + on { productId }.thenReturn(2L) + on { itemId }.thenReturn(2L) + on { quantity }.thenReturn(2.0F) + }, + mock { + on { productId }.thenReturn(3L) + on { itemId }.thenReturn(3L) + on { quantity }.thenReturn(2.0F) + }, + mock { + on { productId }.thenReturn(4L) + on { itemId }.thenReturn(4L) + on { quantity }.thenReturn(2.0F) + }, + mock { + on { productId }.thenReturn(5L) + on { itemId }.thenReturn(5L) + on { quantity }.thenReturn(2.0F) + }, + ) + val order = mock { + on { this.items }.thenReturn(items) + on { this.total }.thenReturn(BigDecimal.TEN) + on { this.refundTotal }.thenReturn(BigDecimal.ZERO) + on { this.currency }.thenReturn("USD") + on { this.shippingTotal }.thenReturn(BigDecimal.ZERO) + on { this.feesTotal }.thenReturn(BigDecimal.ZERO) + on { this.paymentMethod }.thenReturn("cod") + } + val orderEntity = mock() + val orderMapper = mock { + on { toAppModel(orderEntity) }.thenReturn(order) + } + val refundedItems = listOf( + mock { + on { productId }.thenReturn(1L) + on { quantity }.thenReturn(-2) + on { subtotal }.thenReturn(BigDecimal.ZERO) + on { total }.thenReturn(BigDecimal.ZERO) + on { sku }.thenReturn("") + on { price }.thenReturn(BigDecimal.ZERO) + on { totalTax }.thenReturn(BigDecimal.ZERO) + on { metaData }.thenReturn(null) + }, + mock { + on { productId }.thenReturn(2L) + on { quantity }.thenReturn(-1) + on { subtotal }.thenReturn(BigDecimal.ZERO) + on { total }.thenReturn(BigDecimal.ZERO) + on { sku }.thenReturn("") + on { price }.thenReturn(BigDecimal.ZERO) + on { totalTax }.thenReturn(BigDecimal.ZERO) + on { metaData }.thenReturn(null) + }, + mock { + on { productId }.thenReturn(3L) + on { quantity }.thenReturn(-2) + on { subtotal }.thenReturn(BigDecimal.ZERO) + on { total }.thenReturn(BigDecimal.ZERO) + on { sku }.thenReturn("") + on { price }.thenReturn(BigDecimal.ZERO) + on { totalTax }.thenReturn(BigDecimal.ZERO) + on { metaData }.thenReturn(null) + }, + + ) + + val refund = WCRefundModel( + id = 1L, + dateCreated = Date(), + amount = BigDecimal.ZERO, + reason = "", + automaticGatewayRefund = false, + items = refundedItems, + shippingLineItems = listOf(), + feeLineItems = listOf() + ) + + whenever(orderStore.getOrderByIdAndSite(any(), any())).thenReturn(orderEntity) + whenever(refundStore.getAllRefunds(any(), any())).thenReturn(listOf(refund)) + + // WHEN + initViewModel(orderMapper) + + // THEN + assertThat(viewModel.refundItems.value).hasSize(3) + assertThat(viewModel.refundItems.value!![0].maxQuantity).isEqualTo(1.0F) + assertThat(viewModel.refundItems.value!![1].maxQuantity).isEqualTo(2.0F) + assertThat(viewModel.refundItems.value!![2].maxQuantity).isEqualTo(2.0F) + } } From 09f75e4633af343c53abea0c1330924ffa12c538 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 8 Jan 2025 10:11:29 +0100 Subject: [PATCH 6/8] Updated release notes --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index bc01b74b94cb..33c8c05b6a0d 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -9,6 +9,7 @@ - [**] Fixed a crash when a shop manager was trying to install or activate plugin in the POS onboarding [https://github.com/woocommerce/woocommerce-android/pull/13203] - [*] Fixed a crash on the order details [https://github.com/woocommerce/woocommerce-android/pull/13191] - [**] Introduced fallback logic for the barcode scanner to use the front-facing camera when a back-facing camera is unavailable [https://github.com/woocommerce/woocommerce-android/pull/13230] +- [**] Fixed a bug when refunded items were displayed on the refund screen [https://github.com/woocommerce/woocommerce-android/pull/13212] 21.3 ----- From 1790b0c908c12e8e52ec28a93c5405228742ec37 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 8 Jan 2025 10:23:01 +0100 Subject: [PATCH 7/8] Fixed formatting --- .../android/ui/payments/refunds/IssueRefundViewModel.kt | 2 +- .../android/ui/payments/refunds/IssueRefundViewModelTest.kt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt index ccb855a849db..03a3107d8440 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModel.kt @@ -1,6 +1,5 @@ package com.woocommerce.android.ui.payments.refunds -import org.wordpress.android.fluxc.utils.sumBy as sumByBigDecimal import android.os.Parcelable import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -78,6 +77,7 @@ import java.util.Locale import javax.inject.Inject import kotlin.collections.set import kotlin.math.min +import org.wordpress.android.fluxc.utils.sumBy as sumByBigDecimal @HiltViewModel @Suppress("LargeClass") // TODO Refactor this class in a follow up PR diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModelTest.kt index 012d498ea9e9..0371b91b6739 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/refunds/IssueRefundViewModelTest.kt @@ -1135,6 +1135,7 @@ class IssueRefundViewModelTest : BaseUnitTest() { } @Test + @Suppress("LongMethod") fun `given order with 5 product each and some fully refunded some partially, when vm init, then the rest are shown`() = testBlocking { // GIVEN From 92489701603024f99b8e8ce3506696c40b4cb314 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 8 Jan 2025 11:17:16 +0100 Subject: [PATCH 8/8] Removed unnecessary stubbings --- .../woocommerce/android/ui/orders/OrderDetailViewModelTest.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt index 88780dee50c8..bf0d06567d00 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt @@ -483,14 +483,11 @@ class OrderDetailViewModelTest : BaseUnitTest() { doReturn(true).whenever(orderDetailRepository).hasVirtualProductsOnly(listOf(3, 4)) doReturn(virtualOrder).whenever(orderDetailRepository).getOrderById(any()) - doReturn(virtualOrder).whenever(orderDetailRepository).fetchOrderById(any()) doReturn(testOrderRefunds).whenever(orderDetailRepository).getOrderRefunds(any()) - doReturn(true).whenever(orderDetailRepository).fetchOrderNotes(any()) doReturn(testOrderNotes).whenever(orderDetailRepository).getOrderNotes(any()) doReturn(emptyList()).whenever(orderDetailRepository).getOrderShippingLabels(any()) - doReturn(emptyList()).whenever(orderDetailRepository).fetchOrderShippingLabels(any()) viewModel.start()