Skip to content

Commit

Permalink
Merge pull request #12263 from woocommerce/12259-disallow-coupons-and…
Browse files Browse the repository at this point in the history
…-discounts-together

[Bug] Disallow applying coupons and discounts together
  • Loading branch information
AnirudhBhat authored Aug 12, 2024
2 parents d55bbb1 + bd8b5c9 commit 56bb040
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 12 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- [*****] [Internal] Update Androidx-fragment from 1.6.2 to 1.8.2 [https://github.com/woocommerce/woocommerce-android/pull/12231]
- [*****] [Internal] Update Material to 1.12.0 and Transition to 1.5.1 [https://github.com/woocommerce/woocommerce-android/pull/12237]
- [*****] [Internal] Update Stripe Terminal SDK from 3.1.1 to 3.7.1 [https://github.com/woocommerce/woocommerce-android/pull/12239]
- [***] Fix logic behind handling enabled/disabled states of "+ Add coupon" and "+ Add discount" buttons in the Order creation/edition flow [https://github.com/woocommerce/woocommerce-android/pull/12263].

19.8
-----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ class OrderCreateEditViewModel @Inject constructor(
isEditable = order.isEditable
)
monitorOrderChanges()
updateCouponButtonVisibility(order)
updateCouponAndDiscountButtonsState(order)
updateAddShippingButtonVisibility(order)
updateAddGiftCardButtonVisibility(order)
handleCouponEditResult()
Expand Down Expand Up @@ -844,10 +844,17 @@ class OrderCreateEditViewModel @Inject constructor(
}
}

private fun updateCouponButtonVisibility(order: Order) {
viewState = viewState.copy(isCouponButtonEnabled = order.hasProducts() && order.isEditable)
private fun updateCouponAndDiscountButtonsState(order: Order) {
viewState = viewState.copy(
isCouponButtonEnabled = order.hasProducts() && order.isEditable && order.doesNotContainManualDiscounts(),
areDiscountButtonsEnabled = order.hasProducts() && order.isEditable && !order.containsCoupons()
)
}

private fun Order.containsDiscounts(): Boolean = items.any { it.discount > BigDecimal.ZERO }
private fun Order.containsCoupons(): Boolean = couponLines.isNotEmpty()
private fun Order.doesNotContainManualDiscounts() = (!containsDiscounts() || containsCoupons())

private fun updateAddShippingButtonVisibility(order: Order) {
viewState = viewState.copy(isAddShippingButtonEnabled = order.hasProducts() && order.isEditable)
}
Expand Down Expand Up @@ -1508,7 +1515,7 @@ class OrderCreateEditViewModel @Inject constructor(
updateStatus.order
}
}.also {
updateCouponButtonVisibility(it)
updateCouponAndDiscountButtonsState(it)
updateAddShippingButtonVisibility(it)
updateAddGiftCardButtonVisibility(it)
}
Expand Down Expand Up @@ -2040,6 +2047,7 @@ class OrderCreateEditViewModel @Inject constructor(
val isUpdatingOrderDraft: Boolean = false,
val showOrderUpdateSnackbar: Boolean = false,
val isCouponButtonEnabled: Boolean = false,
val areDiscountButtonsEnabled: Boolean = false,
val isAddShippingButtonEnabled: Boolean = false,
val isAddGiftCardButtonEnabled: Boolean = false,
val shouldDisplayAddGiftCardButton: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ fun ExpandableProductCard(
}
}

@Suppress("DestructuringDeclarationWithTooManyEntries")
@Composable
fun ExtendedProductCardContent(
state: State<OrderCreateEditViewModel.ViewState?>,
Expand All @@ -307,7 +308,7 @@ fun ExtendedProductCardContent(
bottomDivider,
orderCount,
price,
discountButton,
addDiscountButton,
discountAmount,
priceAfterDiscountLabel,
priceAfterDiscountValue,
Expand All @@ -322,6 +323,7 @@ fun ExtendedProductCardContent(
// The logic to update bundled products quantity is complex so we need to prevent any change while we are
// updating the bundle and inner products quantity
val isBundledProduct = product.productInfo.productType == ProductType.BUNDLE
val discountButtonsEnabled = state.value?.areDiscountButtonsEnabled == true

Divider(
modifier = Modifier
Expand Down Expand Up @@ -399,11 +401,11 @@ fun ExtendedProductCardContent(
}
if (product.productInfo.hasDiscount) {
WCTextButton(
modifier = Modifier.constrainAs(discountButton) {
modifier = Modifier.constrainAs(addDiscountButton) {
top.linkTo(price.bottom)
},
onClick = onDiscountButtonClicked,
enabled = editableControlsEnabled
enabled = editableControlsEnabled && discountButtonsEnabled
) {
Text(
text = stringResource(id = R.string.discount),
Expand All @@ -421,16 +423,16 @@ fun ExtendedProductCardContent(
.padding(horizontal = dimensionResource(id = R.dimen.minor_100))
.constrainAs(discountAmount) {
end.linkTo(parent.end)
top.linkTo(discountButton.top)
bottom.linkTo(discountButton.bottom)
top.linkTo(addDiscountButton.top)
bottom.linkTo(addDiscountButton.bottom)
},
text = "-${product.productInfo.discountAmount}",
color = colorResource(id = R.color.woo_green_50)
)
Text(
modifier = Modifier
.constrainAs(priceAfterDiscountLabel) {
top.linkTo(discountButton.bottom)
top.linkTo(addDiscountButton.bottom)
start.linkTo(parent.start)
bottom.linkTo(bottomDivider.top)
}
Expand All @@ -451,12 +453,12 @@ fun ExtendedProductCardContent(
)
} else {
WCTextButton(
modifier = Modifier.constrainAs(discountButton) {
modifier = Modifier.constrainAs(addDiscountButton) {
top.linkTo(price.bottom)
bottom.linkTo(bottomDivider.top)
},
onClick = onDiscountButtonClicked,
enabled = editableControlsEnabled
enabled = editableControlsEnabled && discountButtonsEnabled,
) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_add),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,208 @@ class EditFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTest()
assertTrue(lastReceivedState!!.isCouponButtonEnabled)
}

@Test
fun `given coupon applied to order, then should disable adding discount to a product`() {
// given
initMocksForAnalyticsWithOrder(defaultOrderValue)
val order = defaultOrderValue.copy(
isEditable = true,
items = listOf(
Order.Item(
1L,
1L,
"name",
BigDecimal(1),
"",
1f,
BigDecimal(1),
BigDecimal(1),
BigDecimal(1),
1L,
listOf()
)
),
couponLines = listOf(Order.CouponLine("code", 1L, ""))
)
orderDetailRepository.stub {
onBlocking { getOrderById(defaultOrderValue.id) }.doReturn(order)
}
createUpdateOrderUseCase = mock {
onBlocking { invoke(any(), any()) } doReturn flowOf(Succeeded(order))
}
createSut()
var orderDraft: Order? = null
sut.orderDraft.observeForever {
orderDraft = it
}
assertTrue(orderDraft!!.couponLines.isNotEmpty())
var lastReceivedState: OrderCreateEditViewModel.ViewState? = null
sut.viewStateData.liveData.observeForever {
lastReceivedState = it
}
// then
assertFalse(lastReceivedState!!.areDiscountButtonsEnabled)
}

@Test
fun `given a coupon applied to order, then should allow adding another one`() {
// given
initMocksForAnalyticsWithOrder(defaultOrderValue)
val order = defaultOrderValue.copy(
isEditable = true,
items = listOf(
Order.Item(
1L,
1L,
"name",
BigDecimal(1),
"",
1f,
BigDecimal(1),
BigDecimal(1),
BigDecimal(1),
1L,
listOf()
)
),
couponLines = listOf(Order.CouponLine("code", 1L, ""))
)
orderDetailRepository.stub {
onBlocking { getOrderById(defaultOrderValue.id) }.doReturn(order)
}
createUpdateOrderUseCase = mock {
onBlocking { invoke(any(), any()) } doReturn flowOf(Succeeded(order))
}
createSut()
var orderDraft: Order? = null
sut.orderDraft.observeForever {
orderDraft = it
}
assertTrue(orderDraft!!.couponLines.isNotEmpty())
var lastReceivedState: OrderCreateEditViewModel.ViewState? = null
sut.viewStateData.liveData.observeForever {
lastReceivedState = it
}
// then
assertTrue(lastReceivedState!!.isCouponButtonEnabled)
}

@Test
fun `given no coupons applied to order, then should enable adding discount to a product`() {
// given
initMocksForAnalyticsWithOrder(defaultOrderValue)
val order = defaultOrderValue.copy(
isEditable = true,
items = listOf(
Order.Item(
1L,
1L,
"name",
BigDecimal(1),
"",
1f,
BigDecimal(1),
BigDecimal(1),
BigDecimal(1),
1L,
listOf()
)
),
)
orderDetailRepository.stub {
onBlocking { getOrderById(defaultOrderValue.id) }.doReturn(order)
}
createUpdateOrderUseCase = mock {
onBlocking { invoke(any(), any()) } doReturn flowOf(Succeeded(order))
}
createSut()
var orderDraft: Order? = null
sut.orderDraft.observeForever {
orderDraft = it
}
assertTrue(orderDraft!!.couponLines.isEmpty())
var lastReceivedState: OrderCreateEditViewModel.ViewState? = null
sut.viewStateData.liveData.observeForever {
lastReceivedState = it
}
// then
assertTrue(lastReceivedState!!.areDiscountButtonsEnabled)
}

@Test
fun `given discount applied to at least one item, then should disable adding coupon to the order`() {
// given
initMocksForAnalyticsWithOrder(defaultOrderValue)
val item = Order.Item(
itemId = 1L,
productId = 1L,
name = "name",
price = BigDecimal(1),
sku = "",
quantity = 1f,
subtotal = BigDecimal(10),
totalTax = BigDecimal(5),
total = BigDecimal(1),
variationId = 1L,
attributesList = listOf()
)
val order = defaultOrderValue.copy(
isEditable = true,
items = listOf(item),
)
orderDetailRepository.stub {
onBlocking { getOrderById(defaultOrderValue.id) }.doReturn(order)
}
createUpdateOrderUseCase = mock {
onBlocking { invoke(any(), any()) } doReturn flowOf(Succeeded(order))
}
createSut()
var lastReceivedState: OrderCreateEditViewModel.ViewState? = null
sut.viewStateData.liveData.observeForever {
lastReceivedState = it
}
// then
assertTrue(item.discount > BigDecimal.ZERO)
assertFalse(lastReceivedState!!.isCouponButtonEnabled)
}

@Test
fun `given no discounts applied to order items, then should disable adding coupon to the order`() {
// given
initMocksForAnalyticsWithOrder(defaultOrderValue)
val item = Order.Item(
itemId = 1L,
productId = 1L,
name = "name",
price = BigDecimal(1),
sku = "",
quantity = 1f,
subtotal = BigDecimal(10),
totalTax = BigDecimal(1),
total = BigDecimal(10),
variationId = 1L,
attributesList = listOf()
)
val order = defaultOrderValue.copy(
isEditable = true,
items = listOf(item),
)
orderDetailRepository.stub {
onBlocking { getOrderById(defaultOrderValue.id) }.doReturn(order)
}
createUpdateOrderUseCase = mock {
onBlocking { invoke(any(), any()) } doReturn flowOf(Succeeded(order))
}
createSut()
var lastReceivedState: OrderCreateEditViewModel.ViewState? = null
sut.viewStateData.liveData.observeForever {
lastReceivedState = it
}
// then
assertTrue(item.discount == BigDecimal.ZERO)
assertTrue(lastReceivedState!!.isCouponButtonEnabled)
}

@Test
fun `given editable order and order paid, then set tax rate button should be disabled`() {
testBlocking {
Expand Down

0 comments on commit 56bb040

Please sign in to comment.