Skip to content

Commit 31699ac

Browse files
authored
[Woo POS][Historical Orders] Order List Search UI and Functionality (Wireframe UI) (#16089)
2 parents 7688160 + c07807f commit 31699ac

21 files changed

+541
-73
lines changed

Modules/Sources/NetworkingCore/Remote/OrdersRemote.swift

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ extension OrdersRemote: POSOrdersRemoteProtocol {
458458
ParameterKeys.statusKey: Defaults.statusAny,
459459
ParameterKeys.usesGMTDates: true,
460460
ParameterKeys.fields: ParameterValues.fieldValues,
461-
ParameterKeys.createdVia: "pos-rest-api"
461+
ParameterKeys.createdVia: ParameterValues.posFilter
462462
]
463463

464464
let path = Constants.ordersPath
@@ -469,10 +469,30 @@ extension OrdersRemote: POSOrdersRemoteProtocol {
469469
parameters: parameters,
470470
availableAsRESTRequest: true)
471471
let mapper = OrderListMapper(siteID: siteID)
472+
let (orders, responseHeaders) = try await enqueueWithResponseHeaders(request, mapper: mapper)
473+
return createPagedItems(items: orders, responseHeaders: responseHeaders, currentPageNumber: pageNumber)
474+
}
472475

473-
let orders: [Order] = try await enqueue(request, mapper: mapper)
474-
let hasMorePages = orders.count == pageSize
475-
return PagedItems(items: orders, hasMorePages: hasMorePages, totalItems: nil)
476+
public func searchPOSOrders(siteID: Int64, searchTerm: String, pageNumber: Int, pageSize: Int) async throws -> PagedItems<Order> {
477+
let parameters: [String: Any] = [
478+
ParameterKeys.keyword: searchTerm,
479+
ParameterKeys.page: String(pageNumber),
480+
ParameterKeys.perPage: String(pageSize),
481+
ParameterKeys.statusKey: Defaults.statusAny,
482+
ParameterKeys.usesGMTDates: true,
483+
ParameterKeys.fields: ParameterValues.fieldValues,
484+
ParameterKeys.createdVia: ParameterValues.posFilter
485+
]
486+
let path = Constants.ordersPath
487+
let request = JetpackRequest(wooApiVersion: .mark3,
488+
method: .get,
489+
siteID: siteID,
490+
path: path,
491+
parameters: parameters,
492+
availableAsRESTRequest: true)
493+
let mapper = OrderListMapper(siteID: siteID)
494+
let (orders, responseHeaders) = try await enqueueWithResponseHeaders(request, mapper: mapper)
495+
return createPagedItems(items: orders, responseHeaders: responseHeaders, currentPageNumber: pageNumber)
476496
}
477497
}
478498

@@ -522,6 +542,7 @@ public extension OrdersRemote {
522542
"is_editable", "needs_payment", "needs_processing", "gift_cards", "created_via"
523543
]
524544
static let dateModifiedField = "date_modified_gmt"
545+
static let posFilter = "pos-rest-api"
525546
}
526547

527548
enum NestedFieldKeys {

Modules/Sources/NetworkingCore/Remote/POSOrdersRemoteProtocol.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,9 @@ public protocol POSOrdersRemoteProtocol {
1818
func loadPOSOrders(siteID: Int64,
1919
pageNumber: Int,
2020
pageSize: Int) async throws -> PagedItems<Order>
21+
22+
func searchPOSOrders(siteID: Int64,
23+
searchTerm: String,
24+
pageNumber: Int,
25+
pageSize: Int) async throws -> PagedItems<Order>
2126
}

Modules/Sources/Yosemite/PointOfSale/OrderList/PointOfSaleOrderListFetchStrategy.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,21 @@ import struct NetworkingCore.PagedItems
33

44
public protocol PointOfSaleOrderListFetchStrategy {
55
func fetchOrders(pageNumber: Int) async throws -> PagedItems<POSOrder>
6+
var supportsCaching: Bool { get }
7+
var showsLoadingWithItems: Bool { get }
8+
var id: String { get }
9+
}
10+
11+
extension PointOfSaleOrderListFetchStrategy {
12+
var id: String {
13+
String(describing: type(of: self))
14+
}
615
}
716

817
struct PointOfSaleDefaultOrderListFetchStrategy: PointOfSaleOrderListFetchStrategy {
918
private let orderListService: PointOfSaleOrderListServiceProtocol
19+
let supportsCaching: Bool = true
20+
var showsLoadingWithItems: Bool = true
1021

1122
init(orderListService: PointOfSaleOrderListServiceProtocol) {
1223
self.orderListService = orderListService
@@ -16,3 +27,20 @@ struct PointOfSaleDefaultOrderListFetchStrategy: PointOfSaleOrderListFetchStrate
1627
try await orderListService.providePointOfSaleOrders(pageNumber: pageNumber)
1728
}
1829
}
30+
31+
struct PointOfSaleSearchOrderListFetchStrategy: PointOfSaleOrderListFetchStrategy {
32+
private let orderListService: PointOfSaleOrderListServiceProtocol
33+
private let searchTerm: String
34+
35+
var supportsCaching: Bool = false
36+
var showsLoadingWithItems = false
37+
38+
init(orderListService: PointOfSaleOrderListServiceProtocol, searchTerm: String) {
39+
self.orderListService = orderListService
40+
self.searchTerm = searchTerm
41+
}
42+
43+
func fetchOrders(pageNumber: Int) async throws -> PagedItems<POSOrder> {
44+
try await orderListService.searchPointOfSaleOrders(searchTerm: searchTerm, pageNumber: pageNumber)
45+
}
46+
}

Modules/Sources/Yosemite/PointOfSale/OrderList/PointOfSaleOrderListFetchStrategyFactory.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import class WooFoundationCore.CurrencyFormatter
55

66
public protocol PointOfSaleOrderListFetchStrategyFactoryProtocol {
77
func defaultStrategy() -> PointOfSaleOrderListFetchStrategy
8+
func searchStrategy(searchTerm: String) -> PointOfSaleOrderListFetchStrategy
89
}
910

1011
public final class PointOfSaleOrderListFetchStrategyFactory: PointOfSaleOrderListFetchStrategyFactoryProtocol {
@@ -30,4 +31,15 @@ public final class PointOfSaleOrderListFetchStrategyFactory: PointOfSaleOrderLis
3031
)
3132
)
3233
}
34+
35+
public func searchStrategy(searchTerm: String) -> PointOfSaleOrderListFetchStrategy {
36+
PointOfSaleSearchOrderListFetchStrategy(
37+
orderListService: PointOfSaleOrderListService(
38+
siteID: siteID,
39+
ordersRemote: ordersRemote,
40+
currencyFormatter: currencyFormatter
41+
),
42+
searchTerm: searchTerm
43+
)
44+
}
3345
}

Modules/Sources/Yosemite/PointOfSale/OrderList/PointOfSaleOrderListService.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,30 @@ public final class PointOfSaleOrderListService: PointOfSaleOrderListServiceProto
4444
throw PointOfSaleOrderListServiceError.requestFailed
4545
}
4646
}
47+
48+
public func searchPointOfSaleOrders(searchTerm: String, pageNumber: Int = 1) async throws -> PagedItems<POSOrder> {
49+
do {
50+
let pagedOrders = try await ordersRemote.searchPOSOrders(
51+
siteID: siteID,
52+
searchTerm: searchTerm,
53+
pageNumber: pageNumber,
54+
pageSize: 25
55+
)
56+
57+
if pageNumber != 1 && pagedOrders.items.count == 0 {
58+
return .init(items: [], hasMorePages: false, totalItems: 0)
59+
}
60+
61+
// Convert Order objects to POSOrder objects
62+
let posOrders = pagedOrders.items.map { mapper.map(order: $0) }
63+
64+
return .init(items: posOrders,
65+
hasMorePages: pagedOrders.hasMorePages,
66+
totalItems: pagedOrders.totalItems)
67+
} catch AFError.explicitlyCancelled {
68+
throw PointOfSaleOrderListServiceError.requestCancelled
69+
} catch {
70+
throw PointOfSaleOrderListServiceError.requestFailed
71+
}
72+
}
4773
}

Modules/Sources/Yosemite/PointOfSale/OrderList/PointOfSaleOrderListServiceProtocol.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ public enum PointOfSaleOrderListServiceError: Error, Equatable {
88

99
public protocol PointOfSaleOrderListServiceProtocol {
1010
func providePointOfSaleOrders(pageNumber: Int) async throws -> PagedItems<POSOrder>
11+
func searchPointOfSaleOrders(searchTerm: String, pageNumber: Int) async throws -> PagedItems<POSOrder>
1112
}

Modules/Tests/NetworkingTests/Remote/OrdersRemoteTests.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -916,6 +916,43 @@ final class OrdersRemoteTests: XCTestCase {
916916
"value": cashPaymentChangeDueAmount]]
917917
assertEqual(received, expected)
918918
}
919+
920+
func test_searchPOSOrders_sends_correct_parameters() async throws {
921+
// Given
922+
let remote = OrdersRemote(network: network)
923+
let searchTerm = "test search"
924+
let pageNumber = 2
925+
let pageSize = 10
926+
927+
// When
928+
_ = try? await remote.searchPOSOrders(siteID: sampleSiteID, searchTerm: searchTerm, pageNumber: pageNumber, pageSize: pageSize)
929+
930+
// Then
931+
let request = try XCTUnwrap(network.requestsForResponseData.last as? JetpackRequest)
932+
let parameters = request.parameters
933+
934+
XCTAssertEqual(parameters["search"] as? String, searchTerm)
935+
XCTAssertEqual(parameters["page"] as? String, String(pageNumber))
936+
XCTAssertEqual(parameters["per_page"] as? String, String(pageSize))
937+
XCTAssertEqual(parameters["status"] as? String, "any")
938+
XCTAssertEqual(parameters["created_via"] as? String, "pos-rest-api")
939+
XCTAssertEqual(parameters["dates_are_gmt"] as? Bool, true)
940+
XCTAssertNotNil(parameters["_fields"] as? String)
941+
}
942+
943+
func test_searchPOSOrders_properly_relays_networking_error() async throws {
944+
// Given
945+
let remote = OrdersRemote(network: network)
946+
947+
do {
948+
// When
949+
_ = try await remote.searchPOSOrders(siteID: sampleSiteID, searchTerm: "test", pageNumber: 1, pageSize: 25)
950+
XCTFail("Expected error to be thrown")
951+
} catch {
952+
// Then
953+
XCTAssertEqual(error as? NetworkError, .notFound(response: nil))
954+
}
955+
}
919956
}
920957

921958
private extension OrdersRemoteTests {

Modules/Tests/YosemiteTests/Mocks/MockPOSOrdersRemote.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,23 @@ final class MockPOSOrdersRemote: POSOrdersRemoteProtocol {
5353
throw error
5454
}
5555
}
56+
57+
var mockSearchPagedOrdersResult: Result<PagedItems<Order>, Error> = .success(PagedItems(items: [], hasMorePages: false, totalItems: 0))
58+
var searchPOSOrdersCalled = false
59+
var spySearchTerm: String?
60+
61+
func searchPOSOrders(siteID: Int64, searchTerm: String, pageNumber: Int, pageSize: Int) async throws -> PagedItems<Order> {
62+
searchPOSOrdersCalled = true
63+
spySiteID = siteID
64+
spySearchTerm = searchTerm
65+
spyPageNumber = pageNumber
66+
spyPageSize = pageSize
67+
68+
switch mockSearchPagedOrdersResult {
69+
case .success(let pagedOrders):
70+
return pagedOrders
71+
case .failure(let error):
72+
throw error
73+
}
74+
}
5675
}

WooCommerce/Classes/POS/Controllers/PointOfSaleOrderListController.swift

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,31 @@ protocol PointOfSaleOrderListControllerProtocol {
1818
func selectOrder(_ order: POSOrder?)
1919
}
2020

21-
@Observable final class PointOfSaleOrderListController: PointOfSaleOrderListControllerProtocol {
21+
protocol PointOfSaleSearchingOrderListControllerProtocol: PointOfSaleOrderListControllerProtocol {
22+
func searchOrders(searchTerm: String) async
23+
func clearSearchOrders()
24+
}
25+
26+
@Observable final class PointOfSaleOrderListController: PointOfSaleSearchingOrderListControllerProtocol {
2227
var ordersViewState: POSOrderListState
23-
private let paginationTracker: AsyncPaginationTracker
28+
private var strategyPaginationTracker: [String: AsyncPaginationTracker] = [:]
2429
private var fetchStrategy: PointOfSaleOrderListFetchStrategy
2530
private var cachedOrders: [POSOrder] = []
2631
private(set) var selectedOrder: POSOrder?
32+
private let orderListFetchStrategyFactory: PointOfSaleOrderListFetchStrategyFactoryProtocol
33+
private var paginationTracker: AsyncPaginationTracker {
34+
if let existing = strategyPaginationTracker[fetchStrategy.id] {
35+
return existing
36+
}
37+
let tracker = AsyncPaginationTracker()
38+
strategyPaginationTracker[fetchStrategy.id] = tracker
39+
return tracker
40+
}
2741

2842
init(orderListFetchStrategyFactory: PointOfSaleOrderListFetchStrategyFactoryProtocol,
2943
initialState: POSOrderListState = .loading([])) {
3044
self.ordersViewState = initialState
31-
self.paginationTracker = .init()
45+
self.orderListFetchStrategyFactory = orderListFetchStrategyFactory
3246
self.fetchStrategy = orderListFetchStrategyFactory.defaultStrategy()
3347
}
3448

@@ -50,7 +64,7 @@ protocol PointOfSaleOrderListControllerProtocol {
5064
return
5165
}
5266
let currentOrders = ordersViewState.orders
53-
ordersViewState = .loading(currentOrders)
67+
ordersViewState = fetchStrategy.showsLoadingWithItems ? .loading(currentOrders) : .loading([])
5468
do {
5569
_ = try await paginationTracker.ensureNextPageIsSynced { [weak self] pageNumber in
5670
guard let self else { return true }
@@ -83,6 +97,11 @@ protocol PointOfSaleOrderListControllerProtocol {
8397
}
8498

8599
private func setLoadingState() {
100+
if !fetchStrategy.showsLoadingWithItems {
101+
ordersViewState = .loading([])
102+
return
103+
}
104+
86105
let orders = ordersViewState.orders
87106
let isInitialState = ordersViewState.isLoading && orders.isEmpty
88107
if !isInitialState {
@@ -103,7 +122,7 @@ protocol PointOfSaleOrderListControllerProtocol {
103122

104123
ordersViewState = allOrders.isEmpty ? .empty : .loaded(allOrders, hasMoreItems: pagedOrders.hasMorePages)
105124

106-
if pageNumber == 1 && !appendToExistingOrders {
125+
if fetchStrategy.supportsCaching {
107126
cachedOrders = allOrders
108127
}
109128

@@ -115,6 +134,10 @@ protocol PointOfSaleOrderListControllerProtocol {
115134

116135
@MainActor
117136
private func setCachedData() {
137+
guard fetchStrategy.supportsCaching else {
138+
return
139+
}
140+
118141
guard !ordersViewState.orders.isEmpty || !cachedOrders.isEmpty else {
119142
return
120143
}
@@ -126,4 +149,24 @@ protocol PointOfSaleOrderListControllerProtocol {
126149
func selectOrder(_ order: POSOrder?) {
127150
selectedOrder = order
128151
}
152+
153+
@MainActor
154+
func searchOrders(searchTerm: String) async {
155+
fetchStrategy = orderListFetchStrategyFactory.searchStrategy(searchTerm: searchTerm)
156+
ordersViewState = .loading([])
157+
await loadFirstPage()
158+
}
159+
160+
@MainActor
161+
func clearSearchOrders() {
162+
fetchStrategy = orderListFetchStrategyFactory.defaultStrategy()
163+
if cachedOrders.isNotEmpty {
164+
ordersViewState = .loaded(cachedOrders, hasMoreItems: true)
165+
} else {
166+
ordersViewState = .loading([])
167+
Task {
168+
await loadFirstPage()
169+
}
170+
}
171+
}
129172
}

WooCommerce/Classes/POS/Models/PointOfSaleOrderListModel.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import Foundation
22
import Observation
33

44
@Observable final class PointOfSaleOrderListModel {
5-
let ordersController: PointOfSaleOrderListControllerProtocol
5+
let ordersController: PointOfSaleSearchingOrderListControllerProtocol
66

7-
init(ordersController: PointOfSaleOrderListControllerProtocol) {
7+
init(ordersController: PointOfSaleSearchingOrderListControllerProtocol) {
88
self.ordersController = ordersController
99
}
1010
}

0 commit comments

Comments
 (0)