Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Partial refund for products #12164

Draft
wants to merge 8 commits into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -117,6 +120,10 @@ fun WCRefundFeeLine.toAppModel(): Refund.FeeLine {
)
}

fun List<Refund>.getRefundedProductsAmount() = filter { it.items.isNotEmpty() }.sumByBigDecimal {
it.items.sumByBigDecimal { item -> item.total }
}

fun List<Refund>.getNonRefundedProducts(
products: List<Order.Item>
): List<Order.Item> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -28,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
Expand All @@ -38,6 +40,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
Expand Down Expand Up @@ -179,6 +182,7 @@ class IssueRefundViewModel @Inject constructor(
private val refundableFeeLineIds: List<Long> /* Fees lines that haven't been refunded */

private val maxRefund: BigDecimal
private val availableRefundForProducts: BigDecimal
private val maxQuantities: Map<Long, Float>
private val formatCurrency: (BigDecimal) -> String
private val gateway: PaymentGateway
Expand All @@ -202,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()
Expand Down Expand Up @@ -494,6 +499,7 @@ class IssueRefundViewModel @Inject constructor(
refundStore.createItemsRefund(
selectedSite.get(),
order.id,
commonState.refundTotal,
refundSummaryState.refundReason ?: "",
true,
gateway.supportsRefunds,
Expand Down Expand Up @@ -614,6 +620,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,
Expand All @@ -628,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)
Expand Down Expand Up @@ -867,6 +891,8 @@ class IssueRefundViewModel @Inject constructor(
return availableFeeLines
}

private fun calculateProductTotal(order: Order) = order.items.sumByBigDecimal { it.subtotal + it.totalTax }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please review this logic to ensure nothing is missed here.


private fun calculatePartialShippingSubtotal(selectedShippingLinesId: List<Long>): BigDecimal {
return order.shippingLines
.filter { it.itemId in selectedShippingLinesId }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ class RefundByItemsFragment :
viewModel.onFeesRefundMainSwitchChanged(isChecked)
binding.issueRefundFeesSection.root.isVisible = isChecked
}
productsBinding.issueRefundProductsTotal.setOnClickListener {
viewModel.onProductRefundAmountTapped()
}
}

private fun setupObservers() {
Expand Down
10 changes: 6 additions & 4 deletions WooCommerce/src/main/res/layout/refund_by_items_products.xml
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,13 @@

<com.google.android.material.textview.MaterialTextView
android:id="@+id/issueRefund_productsTotal"
style="@style/Woo.Card.Body.Bold"
style="@style/Woo.ListItem.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/major_75"
android:layout_marginBottom="@dimen/major_75"
android:layout_height="32dp"
android:layout_marginVertical="@dimen/major_75"
android:background="@drawable/refund_product_item_qty_bg"
android:minWidth="42dp"
android:paddingHorizontal="@dimen/major_75"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/issueRefund_dividerBelowTaxes"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ class IssueRefundViewModelTest : BaseUnitTest() {
refundStore.createItemsRefund(
site = any(),
orderId = any(),
amount = any(),
reason = any(),
restockItems = any(),
autoRefund = any(),
Expand Down Expand Up @@ -432,6 +433,7 @@ class IssueRefundViewModelTest : BaseUnitTest() {
refundStore.createItemsRefund(
site = any(),
orderId = any(),
amount = any(),
reason = any(),
restockItems = any(),
autoRefund = any(),
Expand Down Expand Up @@ -483,6 +485,7 @@ class IssueRefundViewModelTest : BaseUnitTest() {
refundStore.createItemsRefund(
site = any(),
orderId = any(),
amount = any(),
reason = any(),
restockItems = any(),
autoRefund = any(),
Expand Down Expand Up @@ -800,6 +803,7 @@ class IssueRefundViewModelTest : BaseUnitTest() {
refundStore.createItemsRefund(
site = any(),
orderId = any(),
amount = any(),
reason = any(),
restockItems = any(),
autoRefund = any(),
Expand Down Expand Up @@ -855,6 +859,7 @@ class IssueRefundViewModelTest : BaseUnitTest() {
refundStore.createItemsRefund(
site = any(),
orderId = any(),
amount = any(),
reason = any(),
restockItems = any(),
autoRefund = any(),
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Loading