Skip to content

Conversation

@staskus
Copy link
Contributor

@staskus staskus commented Sep 2, 2025

WOOMOB-1138

Description

Implement order details UI according to the wireframes. Include a header section with Order ID, Date & Time, Total, Status, Payment Method, and Customer Email. Display the products section and totals section. No actions are included in this task.

  • Expanded POSOrderItem with additional required fields
  • Developed PointOfSaleOrderDetailsView with expanded header, products, and totals sections
  • Created PointOfSaleOrderDetailsViewHelper to consolidate formatting and calculations logic
  • Improved state management of selected orders
  • Added simple loading and empty views, they will be finalized in another task

One thing that I didn't include are product images, since they require additional logic I haven't anticipated. I'l added it into a new PR.

Steps to reproduce

  1. Open POS -> Menu -> Orders
  2. Confirm the loading state of a list and order details
  3. Confirm that order details automatically appear after finishing loading
  4. Select orders of different states - completed, failed, refunded, or partially refunded
  5. Confirm correct status, products, product attributes, total, paid, and refund (if applicable) calculations are shown

Testing information

iPad Air 18.5 Simulator

Screenshots

Loading and navigating orders

Loading.mov

Making a new order & reopening

New.Order.mov

  • I have considered if this change warrants user-facing release notes and have added them to RELEASE-NOTES.txt if necessary.

@staskus staskus added this to the 23.2 milestone Sep 2, 2025
@staskus staskus added type: task An internally driven task. feature: POS labels Sep 2, 2025
@dangermattic
Copy link
Collaborator

dangermattic commented Sep 2, 2025

2 Warnings
⚠️ This PR is larger than 300 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.
⚠️ This PR is assigned to the milestone 23.2. This milestone is due in less than 2 days.
Please make sure to get it merged by then or assign it to a milestone with a later deadline.

Generated by 🚫 Danger

@staskus staskus force-pushed the woomob-1138-woo-poshistorical-orders-order-details-wireframe-ui branch from 82709ef to eb02d0c Compare September 2, 2025 12:22
@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Sep 2, 2025

App Icon📲 You can test the changes from this Pull Request in WooCommerce iOS Prototype by scanning the QR code below to install the corresponding build.

App NameWooCommerce iOS Prototype
Build Numberpr16072-dc6e7c7
Version23.1
Bundle IDcom.automattic.alpha.woocommerce
Commitdc6e7c7
Installation URL11kmuh8ju77s0
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@staskus staskus force-pushed the woomob-1138-woo-poshistorical-orders-order-details-wireframe-ui branch from fbbef00 to d4012be Compare September 2, 2025 13:06
@staskus staskus marked this pull request as ready for review September 2, 2025 13:33
@iamgabrielma iamgabrielma self-assigned this Sep 3, 2025
Copy link
Contributor

@iamgabrielma iamgabrielma left a comment

Choose a reason for hiding this comment

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

Works well! Looking good 🚢

Copy link
Contributor

Choose a reason for hiding this comment

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

We got new warnings here, it seems to detect that we had a trailing closure for trailingContent previously, and now is updated with a new bottomContent trailing closure, so it wants to be declared explicitly:

Backward matching of the unlabeled trailing closure is deprecated; label the argument with 'bottomContent' to suppress this warning


/// Calculates the products subtotal by summing line item subtotals
/// Follows the same pattern as OrderPaymentDetailsViewModel.subtotal
func productsSubtotal(for order: POSOrder) -> String {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not necessarily as part of this PR (or project), but to avoid repetition on those across features, I wonder if would be worth to move these calculations as functions over POSOrder, rather than keep them as free functions. So rather than helper.productsSubtotal(for: posOrder) we would do posOrder.productsSubtotal() 🤔 WDYT? I'm happy to open some experimentation branch with this if makes sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea, I'll see what could be done now 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I ended up following this advice. I found Order+CurrencyFormattedValues, expanded and improved it a bit (fixing some formatting inconsistencies in Woo App order details as well), and then created POSOrdersMapper to set formatted values straight on POSOrder, allowing views to use these values. It simplified the code, reused existing logic, and avoided using CurrencyFormatter (and ServiceLocator) in views.

struct PointOfSaleOrderDetailsViewHelper {
private let currencyFormatter: CurrencyFormatter

init(currencySettings: CurrencySettings = ServiceLocator.currencySettings) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure how much this is worth the efford, but should we provide a default value for currencySettings so deep in the view or move it higher? Thinking in the context of getting rid of direct references to ServiceLocator for demodularization

Since PointOfSaleOrderDetailsView() owns PointOfSaleOrderDetailsViewHelper() perhaps is worth to inject it a bit higher. The DI chain is a bit annoying here, so perhaps could sit in PointOfSaleOrderListModel or PointOfSaleOrderListController 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I agree. I dealt with it during modularization work. What I did was inject the currency settings protocol into the environment so it could be accessed within views. It may be worth bringing some of the modularization helper code to trunk, not to introduce more dependencies...

/// Formats discount total
/// Follows TotalsView pattern using discountTotal directly
func formattedDiscountTotal(for order: POSOrder) -> String? {
guard let discountTotal = Double(order.discountTotal), discountTotal != 0 else {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just double checking since the original implementation checks for >0. Is !=0 what we want here? That would allow for negative discounts depending on how is used, which would be summed to the amount (amount - (-discount))


/// Formats tax total
func formattedTaxTotal(for order: POSOrder) -> String? {
guard let taxTotal = Double(order.totalTax), taxTotal != 0 else {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment as above regarding >0 vs !=0

Copy link
Contributor

Choose a reason for hiding this comment

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

In general, if possible, I'd add when/then to all the test names in the file as well as separate the words that are not defined as variables within the test, ie: singleItem -> single_item

#expect(result == false)
}

@Test func shouldShowDiscount_positiveDiscount_returnsTrue() async throws {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's add a test for negative discounts as well, my understanding is that the negative discount should not be supported, as we detract it from the order amount so its always absolute, but if a negative value happens to go through we may want to handle it here as well.

ordersViewState = .loading(cachedOrders)
}

@MainActor
Copy link
Contributor

Choose a reason for hiding this comment

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

This one does not require MainActor, if we want to run it in the main actor then we need to make the function and conformance async

Suggested change
@MainActor

/// A header view for POS pages.
/// Design ref: 1qcjzXitBHU7xPnpCOWnNM-fi-450_24951
struct POSPageHeaderView<TrailingContent: View>: View {
struct POSPageHeaderView<TrailingContent: View, BottomContent: View>: View {
Copy link
Contributor

Choose a reason for hiding this comment

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

💯

@staskus staskus force-pushed the woomob-1138-woo-poshistorical-orders-order-details-wireframe-ui branch from 1c16229 to f39d665 Compare September 3, 2025 21:37
@staskus staskus merged commit db48e93 into trunk Sep 4, 2025
14 checks passed
@staskus staskus deleted the woomob-1138-woo-poshistorical-orders-order-details-wireframe-ui branch September 4, 2025 11:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: POS type: task An internally driven task.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants