Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,7 @@ private fun sampleOrderDetails(
)
)
),
refundedLineItems = WooPosOrdersState.OrderDetailsViewState.Computed.Details.LineItemsState.Loaded(emptyList()),
breakdown = WooPosOrdersState.OrderDetailsViewState.Computed.Details.TotalsBreakdown(
products = "$23.00",
discount = "-$5.00",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ class WooPosGroupRefundedItems @Inject constructor() {
.map { (orderItemId, items) ->
val quantity = items.sumOf { it.quantity }
val total = items.fold(BigDecimal.ZERO) { acc, item -> acc + item.total }
val totalTax = items.fold(BigDecimal.ZERO) { acc, item -> acc + item.totalTax }
items.first().copy(
orderItemId = orderItemId,
quantity = quantity,
total = total,
totalTax = totalTax,
price = if (quantity != 0) {
total.divide(BigDecimal.valueOf(quantity.toLong()), total.scale(), RoundingMode.HALF_UP)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,9 @@ fun WooPosOrderDetails(

Spacer(Modifier.height(WooPosSpacing.Large.value))

val lineItemsState = details.lineItems
if (lineItemsState is WooPosOrdersState.OrderDetailsViewState.Computed.Details.LineItemsState.Loaded &&
lineItemsState.items.isNotEmpty()
) {
OrdersProducts(lineItems = lineItemsState.items)
}
ProductsSection(lineItems = details.lineItems)

RefundedProductsSection(refundedLineItems = details.refundedLineItems)

Spacer(Modifier.height(WooPosSpacing.Medium.value))

Expand Down Expand Up @@ -166,11 +163,56 @@ private fun OrdersHeader(details: WooPosOrdersState.OrderDetailsViewState.Comput
}

@Composable
private fun OrdersProducts(lineItems: List<WooPosOrdersState.OrderDetailsViewState.Computed.Details.LineItemRow>) {
private fun ProductsSection(
lineItems: WooPosOrdersState.OrderDetailsViewState.Computed.Details.LineItemsState
) {
when (lineItems) {
is WooPosOrdersState.OrderDetailsViewState.Computed.Details.LineItemsState.Loading ->
ProductsShimmer(
title = stringResource(R.string.woopos_orders_details_products_title)
)
is WooPosOrdersState.OrderDetailsViewState.Computed.Details.LineItemsState.Loaded ->
if (lineItems.items.isNotEmpty()) {
OrdersProducts(
title = stringResource(R.string.woopos_orders_details_products_title),
lineItems = lineItems.items
)
}
}
}

@Composable
private fun RefundedProductsSection(
refundedLineItems: WooPosOrdersState.OrderDetailsViewState.Computed.Details.LineItemsState
) {
when (refundedLineItems) {
is WooPosOrdersState.OrderDetailsViewState.Computed.Details.LineItemsState.Loading -> {
Spacer(Modifier.height(WooPosSpacing.Medium.value))
ProductsShimmer(
title = stringResource(R.string.woopos_orders_details_refunded_products_title)
)
}
is WooPosOrdersState.OrderDetailsViewState.Computed.Details.LineItemsState.Loaded -> {
if (refundedLineItems.items.isNotEmpty()) {
Spacer(Modifier.height(WooPosSpacing.Medium.value))
OrdersProducts(
title = stringResource(R.string.woopos_orders_details_refunded_products_title),
lineItems = refundedLineItems.items
)
}
}
}
}

@Composable
private fun OrdersProducts(
title: String,
lineItems: List<WooPosOrdersState.OrderDetailsViewState.Computed.Details.LineItemRow>
) {
WooPosCard(shadowType = ShadowType.Soft) {
Column(Modifier.padding(WooPosSpacing.Medium.value)) {
WooPosText(
text = stringResource(R.string.woopos_orders_details_products_title),
text = title,
style = WooPosTypography.BodyXLarge,
fontWeight = FontWeight.SemiBold,
)
Expand All @@ -188,6 +230,55 @@ private fun OrdersProducts(lineItems: List<WooPosOrdersState.OrderDetailsViewSta
}
}

@Composable
private fun ProductsShimmer(title: String) {
WooPosCard(shadowType = ShadowType.Soft) {
Column(Modifier.padding(WooPosSpacing.Medium.value)) {
WooPosText(
text = title,
style = WooPosTypography.BodyXLarge,
fontWeight = FontWeight.SemiBold,
)

Spacer(Modifier.height(WooPosSpacing.Medium.value))

repeat(2) { index ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = WooPosSpacing.Small.value),
verticalAlignment = Alignment.CenterVertically
) {
WooPosShimmerBox(
modifier = Modifier
.size(56.dp)
.clip(RoundedCornerShape(WooPosCornerRadius.Small.value))
)
Spacer(Modifier.width(WooPosSpacing.Medium.value))
Column(modifier = Modifier.weight(1f)) {
WooPosShimmerBox(
modifier = Modifier
.fillMaxWidth(0.6f)
.height(16.dp)
.clip(RoundedCornerShape(WooPosCornerRadius.Small.value))
)
Spacer(Modifier.height(WooPosSpacing.XSmall.value))
WooPosShimmerBox(
modifier = Modifier
.fillMaxWidth(0.3f)
.height(14.dp)
.clip(RoundedCornerShape(WooPosCornerRadius.Small.value))
)
}
}
if (index < 1) {
DividerWithSpacing()
}
}
}
}
}

@Composable
@Suppress("DestructuringDeclarationWithTooManyEntries")
private fun OrderProductItem(row: WooPosOrdersState.OrderDetailsViewState.Computed.Details.LineItemRow) {
Expand Down Expand Up @@ -495,7 +586,7 @@ fun WooPosOrderDetailsPreview() {
),
WooPosOrdersState.OrderDetailsViewState.Computed.Details.LineItemRow(
id = 103,
name = "A vey tasty coffee that incidentally has a very long name " +
name = "A very tasty coffee that incidentally has a very long name " +
"and should go over a few lines without overlapping anything",
attributesDescription = "Medium roast, Decaf",
qtyAndUnitPrice = "1 x $5.00",
Expand All @@ -515,6 +606,18 @@ fun WooPosOrderDetailsPreview() {
)
)
),
refundedLineItems = WooPosOrdersState.OrderDetailsViewState.Computed.Details.LineItemsState.Loaded(
listOf(
WooPosOrdersState.OrderDetailsViewState.Computed.Details.LineItemRow(
id = 101,
name = "Cup",
attributesDescription = null,
qtyAndUnitPrice = "1 x $4.00",
lineTotal = "-$4.00",
imageUrl = null
)
)
),
breakdown = WooPosOrdersState.OrderDetailsViewState.Computed.Details.TotalsBreakdown(
products = "$23.00",
discount = "-$5.00",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,17 @@ class WooPosOrderDetailsMapper @Inject constructor(
order: Order
): WooPosOrdersState.OrderDetailsViewState.Computed.Details = coroutineScope {
val status = orderStatusMapper.mapOrderStatus(order.status)
val mayHaveRefunds = order.refundTotal > BigDecimal.ZERO ||
order.status == Order.Status.Refunded
val lineItems = if (mayHaveRefunds) LineItemsState.Loading else LineItemsState.Loaded(buildLineItems(order))
val refundedLineItems =
if (mayHaveRefunds) LineItemsState.Loading else LineItemsState.Loaded(emptyList())
val isFullyRefunded = order.status == Order.Status.Refunded
val hasPartialRefund = order.refundTotal > BigDecimal.ZERO && !isFullyRefunded
val lineItems = when {
isFullyRefunded -> LineItemsState.Loaded(emptyList())
hasPartialRefund -> LineItemsState.Loading
else -> LineItemsState.Loaded(buildLineItems(order))
}
val refundedLineItems = when {
isFullyRefunded || hasPartialRefund -> LineItemsState.Loading
else -> LineItemsState.Loaded(emptyList())
}
val refundInfo = RefundInfo(emptyList(), BigDecimal.ZERO)
val breakdown = refundInfoBuilder.buildTotalsBreakdown(order, refundInfo)

Expand Down Expand Up @@ -119,22 +125,24 @@ class WooPosOrderDetailsMapper @Inject constructor(
val orderItem = order.items.find { it.itemId == refundItem.orderItemId }
val name = orderItem?.name ?: refundItem.name
val attributesDescription = orderItem?.attributesDescription?.takeIf { it.isNotEmpty() }
val unitPrice = if (refundItem.quantity != 0) {
refundItem.total.divide(
BigDecimal.valueOf(refundItem.quantity.toLong()),
refundItem.total.scale(),
val absQuantity = kotlin.math.abs(refundItem.quantity)
val total = refundItem.total
val unitPrice = if (absQuantity != 0) {
total.divide(
BigDecimal.valueOf(absQuantity.toLong()),
total.scale(),
RoundingMode.HALF_UP
)
} else {
refundItem.total
total
}
val product = getProductById(refundItem.productId)
LineItemRow(
id = refundItem.orderItemId,
name = name,
attributesDescription = attributesDescription,
qtyAndUnitPrice = "${refundItem.quantity} x ${formatPrice(unitPrice, order.currency)}",
lineTotal = formatPrice(refundItem.total.negate(), order.currency),
qtyAndUnitPrice = "$absQuantity x ${formatPrice(unitPrice.abs(), order.currency)}",
lineTotal = formatPrice(total.abs(), order.currency),
imageUrl = product?.firstImageUrl,
)
}
Expand Down
1 change: 1 addition & 0 deletions WooCommerce/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3887,6 +3887,7 @@
<string name="woopos_orders_details_placeholder_title">Select an order</string>
<string name="woopos_orders_details_placeholder_message">Choose an order from the list to view details.</string>
<string name="woopos_orders_details_products_title">Products</string>
<string name="woopos_orders_details_refunded_products_title">Refunded Products</string>
<string name="woopos_orders_details_totals_title">Totals</string>
<string name="woopos_orders_details_qty_unit_price_format">%1$d x %2$s</string>
<string name="woopos_orders_details_breakdown_products_label">Products</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ class WooPosGroupRefundedItemsTest {
productId: Long = 10L,
quantity: Int = 1,
total: BigDecimal = BigDecimal("10.00"),
totalTax: BigDecimal = BigDecimal.ZERO,
) = Refund.Item(
productId = productId,
quantity = quantity,
orderItemId = orderItemId,
name = "Refund Product",
total = total,
totalTax = totalTax,
price = if (quantity > 0) total / quantity.toBigDecimal() else total,
)

Expand Down Expand Up @@ -158,4 +160,43 @@ class WooPosGroupRefundedItemsTest {
// THEN
assertThat(result).isEmpty()
}

@Test
fun `given multiple refunds with tax, when grouped, then totalTax is aggregated`() {
// GIVEN
val refunds = listOf(
createRefund(
id = 1L,
items = listOf(
createRefundItem(
orderItemId = 1L,
productId = 10L,
quantity = 1,
total = BigDecimal("4.00"),
totalTax = BigDecimal("0.40"),
)
)
),
createRefund(
id = 2L,
items = listOf(
createRefundItem(
orderItemId = 1L,
productId = 10L,
quantity = 2,
total = BigDecimal("8.00"),
totalTax = BigDecimal("0.80"),
)
)
),
)

// WHEN
val result = sut(refunds)

// THEN
assertThat(result).hasSize(1)
assertThat(result.first().totalTax).isEqualByComparingTo(BigDecimal("1.20"))
assertThat(result.first().total).isEqualByComparingTo(BigDecimal("12.00"))
}
}
Loading