diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/adapter/OrderDetailRefundsAdapter.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/adapter/OrderDetailRefundsAdapter.kt index 037e92ee3d1f..77776d565cdd 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/adapter/OrderDetailRefundsAdapter.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/adapter/OrderDetailRefundsAdapter.kt @@ -15,6 +15,7 @@ import java.math.BigDecimal class OrderDetailRefundsAdapter( private val isCashPayment: Boolean, private val paymentMethodTitle: String, + private val orderDetailRefundsLineBuilder: OrderDetailRefundsLineBuilder, private val formatCurrency: (BigDecimal) -> String ) : RecyclerView.Adapter() { var refundList: List = ArrayList() @@ -38,6 +39,7 @@ class OrderDetailRefundsAdapter( ), isCashPayment, paymentMethodTitle, + orderDetailRefundsLineBuilder, formatCurrency ) } @@ -52,12 +54,19 @@ class OrderDetailRefundsAdapter( private val viewBinding: OrderDetailRefundPaymentItemBinding, private val isCashPayment: Boolean, private val paymentMethodTitle: String, + private val orderDetailRefundsLineBuilder: OrderDetailRefundsLineBuilder, private val formatCurrency: (BigDecimal) -> String ) : RecyclerView.ViewHolder( viewBinding.root ) { fun bind(refund: Refund) { val context = viewBinding.root.context + + val refundLine = orderDetailRefundsLineBuilder.buildRefundLine(refund) + viewBinding.refundsListLblRefund.text = context.getString( + R.string.orderdetail_refunded_line_with_info, + refundLine + ) viewBinding.refundsListRefundAmount.text = context.getString( R.string.orderdetail_refund_amount, formatCurrency(refund.amount) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/adapter/OrderDetailRefundsLineBuilder.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/adapter/OrderDetailRefundsLineBuilder.kt new file mode 100644 index 000000000000..942149d994f2 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/adapter/OrderDetailRefundsLineBuilder.kt @@ -0,0 +1,30 @@ +package com.woocommerce.android.ui.orders.details.adapter + +import com.woocommerce.android.R +import com.woocommerce.android.model.Refund +import com.woocommerce.android.viewmodel.ResourceProvider +import javax.inject.Inject + +class OrderDetailRefundsLineBuilder @Inject constructor( + private val resourceProvider: ResourceProvider, +) { + fun buildRefundLine(refund: Refund): String { + val tokens = mutableListOf() + if (refund.items.isNotEmpty()) { + val quantity = refund.items.sumOf { it.quantity } + val productString = resourceProvider.getQuantityString( + quantity = quantity, + default = R.string.orderdetail_product_multiple, + one = R.string.orderdetail_product + ) + tokens.add("$quantity $productString") + } + if (refund.shippingLines.isNotEmpty()) { + tokens.add(resourceProvider.getString(R.string.product_shipping)) + } + if (refund.feeLines.isNotEmpty()) { + tokens.add(resourceProvider.getString(R.string.orderdetail_payment_fees)) + } + return tokens.joinToString(separator = ", ") { it.lowercase() } + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/views/OrderDetailPaymentInfoView.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/views/OrderDetailPaymentInfoView.kt index 602bc1c48fd6..3f97435f1cf2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/views/OrderDetailPaymentInfoView.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/views/OrderDetailPaymentInfoView.kt @@ -16,13 +16,19 @@ import com.woocommerce.android.extensions.show import com.woocommerce.android.model.Order import com.woocommerce.android.model.Refund import com.woocommerce.android.ui.orders.details.adapter.OrderDetailRefundsAdapter +import com.woocommerce.android.ui.orders.details.adapter.OrderDetailRefundsLineBuilder +import dagger.hilt.android.AndroidEntryPoint import java.math.BigDecimal +import javax.inject.Inject +@AndroidEntryPoint class OrderDetailPaymentInfoView @JvmOverloads constructor( ctx: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : MaterialCardView(ctx, attrs, defStyleAttr) { + @Inject lateinit var orderDetailRefundsLineBuilder: OrderDetailRefundsLineBuilder + private val binding = OrderDetailPaymentInfoBinding.inflate(LayoutInflater.from(ctx), this) @Suppress("LongParameterList") @@ -174,7 +180,12 @@ class OrderDetailPaymentInfoView @JvmOverloads constructor( formatCurrencyForDisplay: (BigDecimal) -> String ) { val adapter = binding.paymentInfoRefunds.adapter as? OrderDetailRefundsAdapter - ?: OrderDetailRefundsAdapter(order.isCashPayment, order.paymentMethodTitle, formatCurrencyForDisplay) + ?: OrderDetailRefundsAdapter( + order.isCashPayment, + order.paymentMethodTitle, + orderDetailRefundsLineBuilder, + formatCurrencyForDisplay, + ) binding.paymentInfoRefunds.adapter = adapter adapter.refundList = refunds diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/ResourceProvider.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/ResourceProvider.kt index 388044847cf2..6da7c0724c4c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/ResourceProvider.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/ResourceProvider.kt @@ -1,8 +1,13 @@ package com.woocommerce.android.viewmodel import android.content.Context -import androidx.annotation.* +import androidx.annotation.ArrayRes +import androidx.annotation.ColorRes +import androidx.annotation.DimenRes +import androidx.annotation.RawRes +import androidx.annotation.StringRes import androidx.core.content.ContextCompat +import com.woocommerce.android.util.StringUtils import java.io.InputStream import javax.inject.Inject @@ -32,4 +37,17 @@ class ResourceProvider @Inject constructor(private val context: Context) { val resources = context.resources return resources.openRawResource(rawId) } + + fun getQuantityString( + quantity: Int, + @StringRes default: Int, + @StringRes zero: Int? = null, + @StringRes one: Int? = null + ) = StringUtils.getQuantityString( + context, + quantity, + default, + zero, + one, + ) } diff --git a/WooCommerce/src/main/res/layout/order_detail_refund_payment_item.xml b/WooCommerce/src/main/res/layout/order_detail_refund_payment_item.xml index 748e88bcb750..b2d3fc507d50 100644 --- a/WooCommerce/src/main/res/layout/order_detail_refund_payment_item.xml +++ b/WooCommerce/src/main/res/layout/order_detail_refund_payment_item.xml @@ -26,7 +26,7 @@ android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginTop="@dimen/major_75" - android:text="@string/orderdetail_refunded" + android:text="@string/orderdetail_refunded_line_with_info" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/refundsList_refundDivider" /> diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index f3c07e397c67..fd9aebb971dd 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -439,6 +439,7 @@ Awaiting payment via %s Paid Refunded + Refunded: %1$s Refunded products %1$s via %2$s Net Payment diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/details/adapter/OrderDetailRefundsLineBuilderTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/details/adapter/OrderDetailRefundsLineBuilderTest.kt new file mode 100644 index 000000000000..04d22f3c5049 --- /dev/null +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/details/adapter/OrderDetailRefundsLineBuilderTest.kt @@ -0,0 +1,272 @@ +package com.woocommerce.android.ui.orders.details.adapter + +import com.woocommerce.android.R +import com.woocommerce.android.model.Refund +import com.woocommerce.android.model.Refund.Item +import com.woocommerce.android.viewmodel.BaseUnitTest +import com.woocommerce.android.viewmodel.ResourceProvider +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +class OrderDetailRefundsLineBuilderTest : BaseUnitTest() { + private val resourceProvider: ResourceProvider = mock { + on { + getQuantityString( + quantity = 1, + default = R.string.orderdetail_product_multiple, + one = R.string.orderdetail_product + ) + }.thenReturn("Product") + on { + getQuantityString( + quantity = 2, + default = R.string.orderdetail_product_multiple, + one = R.string.orderdetail_product + ) + }.thenReturn("Products") + on { getString(R.string.product_shipping) }.thenReturn("Shipping") + on { getString(R.string.orderdetail_payment_fees) }.thenReturn("Fees") + } + + private val refund = mock { + on { items }.thenReturn(emptyList()) + on { shippingLines }.thenReturn(emptyList()) + on { feeLines }.thenReturn(emptyList()) + } + + private val builder = OrderDetailRefundsLineBuilder(resourceProvider) + + @Test + fun `given refund with product, when building line, then product returned`() { + // GIVEN + val item = mock { + on { quantity }.thenReturn(1) + } + whenever(refund.items).thenReturn( + listOf(item) + ) + + // WHEN + val result = builder.buildRefundLine(refund) + + // THEN + assertThat(result).isEqualTo("1 product") + } + + @Test + fun `given refund with products, when building line, then products returned`() { + // GIVEN + val itemOne = mock { + on { quantity }.thenReturn(3) + } + val itemTwo = mock { + on { quantity }.thenReturn(4) + } + val itemThree = mock { + on { quantity }.thenReturn(5) + } + whenever(refund.items).thenReturn( + listOf(itemOne, itemTwo, itemThree) + ) + whenever( + resourceProvider.getQuantityString( + quantity = 12, + default = R.string.orderdetail_product_multiple, + one = R.string.orderdetail_product + ) + ).thenReturn("Products") + + // WHEN + val result = builder.buildRefundLine(refund) + + // THEN + assertThat(result).isEqualTo("12 products") + } + + @Test + fun `given refund with fee, when building line, then fees returned`() { + // GIVEN + whenever(refund.feeLines).thenReturn( + listOf(mock()) + ) + + // WHEN + val result = builder.buildRefundLine(refund) + + // THEN + assertThat(result).isEqualTo("fees") + } + + @Test + fun `given refund with shipping, when building line, then shipping returned`() { + // GIVEN + whenever(refund.shippingLines).thenReturn( + listOf(mock()) + ) + + // WHEN + val result = builder.buildRefundLine(refund) + + // THEN + assertThat(result).isEqualTo("shipping") + } + + @Test + fun `given refund with product and shipping, when building line, then product and shipping returned`() { + // GIVEN + val item = mock { + on { quantity }.thenReturn(1) + } + whenever(refund.items).thenReturn( + listOf(item) + ) + whenever(refund.shippingLines).thenReturn( + listOf(mock()) + ) + + // WHEN + val result = builder.buildRefundLine(refund) + + // THEN + assertThat(result).isEqualTo("1 product, shipping") + } + + @Test + fun `given refund with shipping and fee, when building line, then shipping and fee returned`() { + // GIVEN + whenever(refund.shippingLines).thenReturn( + listOf(mock()) + ) + whenever(refund.feeLines).thenReturn( + listOf(mock()) + ) + + // WHEN + val result = builder.buildRefundLine(refund) + + // THEN + assertThat(result).isEqualTo("shipping, fees") + } + + @Test + fun `given refund with product, shipping and fee, when building line, then product shipping and fee returned`() { + // GIVEN + val item = mock { + on { quantity }.thenReturn(1) + } + whenever(refund.items).thenReturn( + listOf(item) + ) + whenever(refund.shippingLines).thenReturn( + listOf(mock()) + ) + whenever(refund.feeLines).thenReturn( + listOf(mock()) + ) + + // WHEN + val result = builder.buildRefundLine(refund) + + // THEN + assertThat(result).isEqualTo("1 product, shipping, fees") + } + + @Test + fun `given refund with product and fees, when building line, then product and fees returned`() { + // GIVEN + val item = mock { + on { quantity }.thenReturn(1) + } + whenever(refund.items).thenReturn( + listOf(item) + ) + whenever(refund.feeLines).thenReturn( + listOf(mock()) + ) + + // WHEN + val result = builder.buildRefundLine(refund) + + // THEN + assertThat(result).isEqualTo("1 product, fees") + } + + @Test + fun `given refund with 2 products and fees, when building line, then 2 products and fees returned`() { + // GIVEN + val item = mock { + on { quantity }.thenReturn(2) + } + whenever(refund.items).thenReturn( + listOf(item) + ) + whenever(refund.feeLines).thenReturn( + listOf(mock()) + ) + + // WHEN + val result = builder.buildRefundLine(refund) + + // THEN + assertThat(result).isEqualTo("2 products, fees") + } + + @Test + fun `given refund with 5 products and shipping, when building line, then 5 products and shipping returned`() { + // GIVEN + val item = mock { + on { quantity }.thenReturn(5) + } + whenever(refund.items).thenReturn( + listOf(item) + ) + whenever(refund.shippingLines).thenReturn( + listOf(mock()) + ) + whenever( + resourceProvider.getQuantityString( + quantity = 5, + default = R.string.orderdetail_product_multiple, + one = R.string.orderdetail_product + ) + ).thenReturn("Products") + + // WHEN + val result = builder.buildRefundLine(refund) + + // THEN + assertThat(result).isEqualTo("5 products, shipping") + } + + @Test + fun `given refund with 10 products ship fees, when building line, then 10 products, ship and fees returned`() { + // GIVEN + val item = mock { + on { quantity }.thenReturn(10) + } + whenever(refund.items).thenReturn( + listOf(item) + ) + whenever(refund.shippingLines).thenReturn( + listOf(mock()) + ) + whenever(refund.feeLines).thenReturn( + listOf(mock()) + ) + whenever( + resourceProvider.getQuantityString( + quantity = 10, + default = R.string.orderdetail_product_multiple, + one = R.string.orderdetail_product + ) + ).thenReturn("Products") + + // WHEN + val result = builder.buildRefundLine(refund) + + // THEN + assertThat(result).isEqualTo("10 products, shipping, fees") + } +}