From 8b8975634a3a610512415f2ddc692523672d1fa7 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Mon, 29 Jul 2024 18:45:29 +0300 Subject: [PATCH 1/8] Make refund products total text a button --- .../src/main/res/layout/refund_by_items_products.xml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/res/layout/refund_by_items_products.xml b/WooCommerce/src/main/res/layout/refund_by_items_products.xml index 13cc361410c..1b3706c6efe 100644 --- a/WooCommerce/src/main/res/layout/refund_by_items_products.xml +++ b/WooCommerce/src/main/res/layout/refund_by_items_products.xml @@ -108,13 +108,16 @@ From 63342bb5fb640ffd6a9ffe900526a43de66f41f9 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Mon, 29 Jul 2024 18:45:50 +0300 Subject: [PATCH 2/8] Open the custom amount dialog when the product refund amount is tapped --- .../payments/refunds/IssueRefundViewModel.kt | 20 +++++++++++++++++++ .../payments/refunds/RefundByItemsFragment.kt | 3 +++ 2 files changed, 23 insertions(+) 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 352a93535bd..b4634bda53d 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 @@ -8,6 +8,7 @@ import com.woocommerce.android.R import com.woocommerce.android.WooException import com.woocommerce.android.analytics.AnalyticsEvent.CREATE_ORDER_REFUND_ITEM_QUANTITY_DIALOG_OPENED import com.woocommerce.android.analytics.AnalyticsEvent.CREATE_ORDER_REFUND_NEXT_BUTTON_TAPPED +import com.woocommerce.android.analytics.AnalyticsEvent.CREATE_ORDER_REFUND_PRODUCT_AMOUNT_DIALOG_OPENED import com.woocommerce.android.analytics.AnalyticsEvent.CREATE_ORDER_REFUND_SELECT_ALL_ITEMS_BUTTON_TAPPED import com.woocommerce.android.analytics.AnalyticsEvent.CREATE_ORDER_REFUND_SUMMARY_REFUND_BUTTON_TAPPED import com.woocommerce.android.analytics.AnalyticsEvent.ORDER_NOTE_ADD_FAILED @@ -38,6 +39,7 @@ import com.woocommerce.android.ui.payments.refunds.IssueRefundViewModel.InputVal import com.woocommerce.android.ui.payments.refunds.IssueRefundViewModel.IssueRefundEvent.HideValidationError import com.woocommerce.android.ui.payments.refunds.IssueRefundViewModel.IssueRefundEvent.OpenUrl import com.woocommerce.android.ui.payments.refunds.IssueRefundViewModel.IssueRefundEvent.ShowNumberPicker +import com.woocommerce.android.ui.payments.refunds.IssueRefundViewModel.IssueRefundEvent.ShowRefundAmountDialog import com.woocommerce.android.ui.payments.refunds.IssueRefundViewModel.IssueRefundEvent.ShowRefundConfirmation import com.woocommerce.android.ui.payments.refunds.IssueRefundViewModel.IssueRefundEvent.ShowRefundSummary import com.woocommerce.android.ui.payments.refunds.IssueRefundViewModel.IssueRefundEvent.ShowValidationError @@ -614,6 +616,24 @@ class IssueRefundViewModel @Inject constructor( refundSummaryState = refundSummaryState.copy(isSummaryTextTooLong = currLength > maxLength) } + fun onProductRefundAmountTapped() { + triggerEvent( + ShowRefundAmountDialog( + refundByItemsState.productsRefund, + availableRefundForProducts, + resourceProvider.getString( + R.string.order_refunds_available_for_refund, + formatCurrency(availableRefundForProducts) + ) + ) + ) + + analyticsTrackerWrapper.track( + CREATE_ORDER_REFUND_PRODUCT_AMOUNT_DIALOG_OPENED, + mapOf(AnalyticsTracker.KEY_ORDER_ID to order.id) + ) + } + fun onProductsRefundAmountChanged(newAmount: BigDecimal) { refundByItemsState = refundByItemsState.copy( productsRefund = newAmount, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/RefundByItemsFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/RefundByItemsFragment.kt index 72d62a64a63..87a3a1df2b4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/RefundByItemsFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/refunds/RefundByItemsFragment.kt @@ -113,6 +113,9 @@ class RefundByItemsFragment : viewModel.onFeesRefundMainSwitchChanged(isChecked) binding.issueRefundFeesSection.root.isVisible = isChecked } + productsBinding.issueRefundProductsTotal.setOnClickListener { + viewModel.onProductRefundAmountTapped() + } } private fun setupObservers() { From fb9c68ad1ef25513ef1fccde19e7a8feec9582a7 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Mon, 29 Jul 2024 18:46:03 +0300 Subject: [PATCH 3/8] Send amount data to refund endpoint --- .../android/ui/payments/refunds/IssueRefundViewModel.kt | 1 + 1 file changed, 1 insertion(+) 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 b4634bda53d..2f781e547bb 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 @@ -496,6 +496,7 @@ class IssueRefundViewModel @Inject constructor( refundStore.createItemsRefund( selectedSite.get(), order.id, + commonState.refundTotal, refundSummaryState.refundReason ?: "", true, gateway.supportsRefunds, From 42b1d7f2f6066fec9c239320712847b070976805 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Mon, 29 Jul 2024 18:47:34 +0300 Subject: [PATCH 4/8] Calculate the refund amount for products only and use it --- .../main/kotlin/com/woocommerce/android/model/Refund.kt | 9 ++++++++- .../android/ui/payments/refunds/IssueRefundViewModel.kt | 7 ++++++- 2 files changed, 14 insertions(+), 2 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 1ffa8fda548..1d74193212a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/Refund.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/Refund.kt @@ -1,11 +1,14 @@ package com.woocommerce.android.model import android.os.Parcelable +import com.woocommerce.android.extensions.sumByBigDecimal import com.woocommerce.android.ui.products.ProductHelper import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize import org.wordpress.android.fluxc.model.refunds.WCRefundModel -import org.wordpress.android.fluxc.model.refunds.WCRefundModel.* +import org.wordpress.android.fluxc.model.refunds.WCRefundModel.WCRefundFeeLine +import org.wordpress.android.fluxc.model.refunds.WCRefundModel.WCRefundItem +import org.wordpress.android.fluxc.model.refunds.WCRefundModel.WCRefundShippingLine import java.math.BigDecimal import java.math.RoundingMode.HALF_UP import java.util.Date @@ -117,6 +120,10 @@ fun WCRefundFeeLine.toAppModel(): Refund.FeeLine { ) } +fun List.getRefundedProductsAmount() = filter { it.items.isNotEmpty() }.sumByBigDecimal { + it.items.sumByBigDecimal { item -> item.total } +} + fun List.getNonRefundedProducts( products: List ): List { 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 2f781e547bb..55cf6071ada 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 @@ -29,6 +29,7 @@ import com.woocommerce.android.model.OrderNote import com.woocommerce.android.model.PaymentGateway import com.woocommerce.android.model.Refund import com.woocommerce.android.model.getMaxRefundQuantities +import com.woocommerce.android.model.getRefundedProductsAmount import com.woocommerce.android.model.toAppModel import com.woocommerce.android.tools.NetworkStatus import com.woocommerce.android.tools.SelectedSite @@ -181,6 +182,7 @@ class IssueRefundViewModel @Inject constructor( private val refundableFeeLineIds: List /* Fees lines that haven't been refunded */ private val maxRefund: BigDecimal + private val availableRefundForProducts: BigDecimal private val maxQuantities: Map private val formatCurrency: (BigDecimal) -> String private val gateway: PaymentGateway @@ -204,6 +206,7 @@ class IssueRefundViewModel @Inject constructor( refunds = refundStore.getAllRefunds(selectedSite.get(), arguments.orderId).map { it.toAppModel() } formatCurrency = currencyFormatter.buildBigDecimalFormatter(order.currency) maxRefund = order.total - order.refundTotal + availableRefundForProducts = calculateProductTotal(order) - refunds.getRefundedProductsAmount() maxQuantities = refunds.getMaxRefundQuantities(order.items) .map { (id, quantity) -> id to quantity } .toMap() @@ -649,7 +652,7 @@ class IssueRefundViewModel @Inject constructor( selectedQuantities[uniqueId] = newQuantity val (subtotal, taxes) = newItems.calculateTotals() - val productsRefund = min(max(subtotal + taxes, BigDecimal.ZERO), maxRefund) + val productsRefund = min(max(subtotal + taxes, BigDecimal.ZERO), availableRefundForProducts) val selectButtonTitle = if (areAllItemsSelected) { resourceProvider.getString(R.string.order_refunds_items_select_none) @@ -888,6 +891,8 @@ class IssueRefundViewModel @Inject constructor( return availableFeeLines } + private fun calculateProductTotal(order: Order) = order.items.sumByBigDecimal { it.subtotal + it.totalTax } + private fun calculatePartialShippingSubtotal(selectedShippingLinesId: List): BigDecimal { return order.shippingLines .filter { it.itemId in selectedShippingLinesId } From f1436523c0ed644aaffc2562dd28e4894b45b467 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Mon, 29 Jul 2024 18:48:08 +0300 Subject: [PATCH 5/8] Update IssueRefundViewModelTest --- .../refunds/IssueRefundViewModelTest.kt | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) 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 007517b8280..2fc808ec786 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 @@ -381,6 +381,7 @@ class IssueRefundViewModelTest : BaseUnitTest() { refundStore.createItemsRefund( site = any(), orderId = any(), + amount = any(), reason = any(), restockItems = any(), autoRefund = any(), @@ -432,6 +433,7 @@ class IssueRefundViewModelTest : BaseUnitTest() { refundStore.createItemsRefund( site = any(), orderId = any(), + amount = any(), reason = any(), restockItems = any(), autoRefund = any(), @@ -483,6 +485,7 @@ class IssueRefundViewModelTest : BaseUnitTest() { refundStore.createItemsRefund( site = any(), orderId = any(), + amount = any(), reason = any(), restockItems = any(), autoRefund = any(), @@ -800,6 +803,7 @@ class IssueRefundViewModelTest : BaseUnitTest() { refundStore.createItemsRefund( site = any(), orderId = any(), + amount = any(), reason = any(), restockItems = any(), autoRefund = any(), @@ -855,6 +859,7 @@ class IssueRefundViewModelTest : BaseUnitTest() { refundStore.createItemsRefund( site = any(), orderId = any(), + amount = any(), reason = any(), restockItems = any(), autoRefund = any(), @@ -929,6 +934,25 @@ class IssueRefundViewModelTest : BaseUnitTest() { } } + @Test + fun `when product refund amount is tapped, then proper track event is triggered`() { + testBlocking { + val orderWithMultipleShipping = OrderTestUtils.generateOrderWithMultipleShippingLines().copy( + paymentMethod = "cod", + metaData = "[]" + ) + whenever(orderStore.getOrderByIdAndSite(any(), any())).thenReturn(orderWithMultipleShipping) + + initViewModel() + viewModel.onProductRefundAmountTapped() + + verify(analyticsTrackerWrapper).track( + AnalyticsEvent.CREATE_ORDER_REFUND_PRODUCT_AMOUNT_DIALOG_OPENED, + mapOf(AnalyticsTracker.KEY_ORDER_ID to ORDER_ID) + ) + } + } + @Test fun `when select button is tapped, then proper track event is triggered`() { testBlocking { From 802c31e6fa15d445a33215db509c137e4f8dab5b Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Mon, 29 Jul 2024 20:00:01 +0300 Subject: [PATCH 6/8] Update RELEASE-NOTES.txt --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 67980f4eb0a..6869e8b8388 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -3,6 +3,7 @@ 19.8 ----- - [*] Stats: The Google Campaign analytics card now has a call to action to create a paid campaign if there are no campaign analytics for the selected time period. [https://github.com/woocommerce/woocommerce-android/pull/12161] +- [**] Added support for partial refunds. [https://github.com/woocommerce/woocommerce-android/pull/12164] 19.7 ----- From c43c6a6030722be681943ee6e36ea3d0c1c67204 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Mon, 29 Jul 2024 20:40:15 +0300 Subject: [PATCH 7/8] Update fluxCVersion to PR --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 74989839778..7c064c78423 100644 --- a/build.gradle +++ b/build.gradle @@ -99,7 +99,7 @@ tasks.register("installGitHooks", Copy) { } ext { - fluxCVersion = 'trunk-cc6b2f85546a844b13d99bc0ad6ceabd1af131d3' + fluxCVersion = '3069-7afd7e6be72dd202b98897527c5878d7c1a4aeff' glideVersion = '4.16.0' coilVersion = '2.1.0' constraintLayoutVersion = '1.2.0' From 541fa01723b3d8916845977f01b9c79891170b31 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Wed, 31 Jul 2024 18:01:17 +0300 Subject: [PATCH 8/8] Optimize refund_by_items_products.xml Removed the `textAlignment` property as it was unnecessary. --- WooCommerce/src/main/res/layout/refund_by_items_products.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/res/layout/refund_by_items_products.xml b/WooCommerce/src/main/res/layout/refund_by_items_products.xml index 1b3706c6efe..3de20b516ea 100644 --- a/WooCommerce/src/main/res/layout/refund_by_items_products.xml +++ b/WooCommerce/src/main/res/layout/refund_by_items_products.xml @@ -113,11 +113,10 @@ android:layout_height="32dp" android:layout_marginVertical="@dimen/major_75" android:background="@drawable/refund_product_item_qty_bg" - app:layout_constraintBottom_toBottomOf="parent" android:minWidth="42dp" - app:layout_constraintEnd_toEndOf="parent" android:paddingHorizontal="@dimen/major_75" - android:textAlignment="center" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/issueRefund_dividerBelowTaxes" tools:text="$1.00" />