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
2 changes: 1 addition & 1 deletion Modules/Sources/Networking/Model/Customer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Codegen
/// Represents a Customer entity:
/// https://woocommerce.github.io/woocommerce-rest-api-docs/#customer-properties
///
public struct Customer: Codable, GeneratedCopiable, GeneratedFakeable, Equatable {
public struct Customer: Codable, GeneratedCopiable, GeneratedFakeable, Equatable, Hashable {
/// The siteID for the customer
public let siteID: Int64

Expand Down
6 changes: 3 additions & 3 deletions Modules/Sources/Yosemite/Actions/CustomerAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ public enum CustomerAction: Action {
///- `filter`: Filter to perform the search.
///- `retrieveFullCustomersData`: If `true`, retrieves all customers data one by one after the search request. It will be removed once
/// `betterCustomerSelectionInOrder` is finished for performance reasons.
///- `onCompletion`: Invoked when the operation finishes.
/// - `result.success()`: On success.
///- `onCompletion`: Invoked when the operation finishes. Returns true if there are more customers to be synced in the search results.
/// - `result.success(Bool)`: On success, returns whether there are more pages available.
/// - `result.failure(Error)`: Error fetching data
case searchCustomers(
siteID: Int64,
Expand All @@ -63,7 +63,7 @@ public enum CustomerAction: Action {
retrieveFullCustomersData: Bool,
filter: CustomerSearchFilter,
filterEmpty: WCAnalyticsCustomerRemote.FilterEmpty? = nil,
onCompletion: (Result<(), Error>) -> Void)
onCompletion: (Result<Bool, Error>) -> Void)

/// Searches for WCAnalyticsCustomers by keyword and stores the results.
///
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Foundation

/// Used to filter bookings by customers
///
public struct BookingCustomerFilter: Codable, Hashable {
/// ID of the customer
/// periphery:ignore - to be used later when applying filter
///
public let customerID: Int64

/// Name of the customer
///
public let name: String

public init(customerID: Int64, name: String) {
self.customerID = customerID
self.name = name
}
}
19 changes: 14 additions & 5 deletions Modules/Sources/Yosemite/Stores/CustomerStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public final class CustomerStore: Store {
/// - Parameters:
/// - siteID: The site for which the array of Customers should be fetched.
/// - keyword: Keyword that we pass to the `?query={keyword}` endpoint to perform the search
/// - onCompletion: Invoked when the operation finishes.
/// - onCompletion: Invoked when the operation finishes. Returns true if there are more pages available.
///
func searchCustomers(
for siteID: Int64,
Expand All @@ -102,7 +102,7 @@ public final class CustomerStore: Store {
retrieveFullCustomersData: Bool,
filter: CustomerSearchFilter,
filterEmpty: WCAnalyticsCustomerRemote.FilterEmpty?,
onCompletion: @escaping (Result<(), Error>) -> Void) {
onCompletion: @escaping (Result<Bool, Error>) -> Void) {
wcAnalyticsCustomerRemote.searchCustomers(for: siteID,
pageNumber: pageNumber,
pageSize: pageSize,
Expand All @@ -114,15 +114,24 @@ public final class CustomerStore: Store {
guard let self else { return }
switch result {
case .success(let customers):
let hasNextPage = customers.count == pageSize
if retrieveFullCustomersData {
self.mapSearchResultsToCustomerObjects(for: siteID, with: keyword, with: customers, onCompletion: onCompletion)
self.mapSearchResultsToCustomerObjects(for: siteID,
with: keyword,
with: customers,
onCompletion: { result in
switch result {
case .success: onCompletion(.success(hasNextPage))
case .failure(let error): onCompletion(.failure(error))
}
})
} else {
self.upsertCustomersAndSave(siteID: siteID,
readOnlyCustomers: customers,
shouldDeleteExistingCustomers: pageNumber == 1,
keyword: keyword,
onCompletion: {
onCompletion(.success(()))
onCompletion(.success(hasNextPage))
})
}
case .failure(let error):
Expand Down Expand Up @@ -262,7 +271,7 @@ public final class CustomerStore: Store {
private func mapSearchResultsToCustomerObjects(for siteID: Int64,
with keyword: String,
with searchResults: [WCAnalyticsCustomer],
onCompletion: @escaping (Result<(), Error>) -> Void) {
onCompletion: @escaping (Result<Void, Error>) -> Void) {
var customers = [Customer]()
let group = DispatchGroup()
for result in searchResults {
Expand Down
6 changes: 4 additions & 2 deletions Modules/Tests/YosemiteTests/Stores/CustomerStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ final class CustomerStoreTests: XCTestCase {
network.simulateError(requestUrlSuffix: "", error: expectedError)

// When
let result = waitFor { promise in
let result: Result<Bool, Error> = waitFor { promise in
let action = CustomerAction.searchCustomers(
siteID: self.dummySiteID,
pageNumber: 1,
Expand Down Expand Up @@ -172,7 +172,7 @@ final class CustomerStoreTests: XCTestCase {
XCTAssertEqual(viewStorage.countObjects(ofType: Storage.CustomerSearchResult.self), 0)

// When
let response = waitFor { promise in
let response: Result<Bool, Error> = waitFor { promise in
let action = CustomerAction.searchCustomers(siteID: self.dummySiteID,
pageNumber: 1,
pageSize: 25,
Expand All @@ -188,6 +188,8 @@ final class CustomerStoreTests: XCTestCase {

// Then
XCTAssertTrue(response.isSuccess)
// Verify hasNextPage is false since we received 2 customers with pageSize 25
XCTAssertEqual(try? response.get(), false)
XCTAssertEqual(viewStorage.countObjects(ofType: Storage.Customer.self), 2)
XCTAssertEqual(viewStorage.countObjects(ofType: Storage.CustomerSearchResult.self), 1)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ struct BookableProductListSyncable: ListSyncable {

let siteID: Int64

var title: String { Localization.title }
let title = Localization.title

var emptyStateMessage: String { Localization.noMembersFound }
let emptyStateMessage = Localization.noMembersFound
let emptyItemTitlePlaceholder: String? = nil

let searchConfiguration: ListSearchConfiguration? = nil

let selectionDisabledMessage: String? = nil

// MARK: - ResultsController Configuration

Expand Down Expand Up @@ -46,12 +51,22 @@ struct BookableProductListSyncable: ListSyncable {
)
}

/// Creates the action to search items with keyword
func createSearchAction(keyword: String, pageNumber: Int, pageSize: Int, completion: @escaping (Result<Bool, Error>) -> Void) -> Action {
fatalError("Searching is not supported")
}

// MARK: - Display Configuration

func displayName(for item: Product) -> String {
item.name
}

/// Returns the description for an item
func description(for item: Product) -> String? { nil }

func selectionEnabled(for item: Product) -> Bool { true }

func filterItem(for item: Product) -> BookingProductFilter {
BookingProductFilter(productID: item.productID, name: item.name)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ final class BookingFiltersViewModel: FilterListViewModel {
var criteria: Filters {
let teamMembers = (teamMemberFilterViewModel.selectedValue as? MultipleFilterSelection)?.items as? [BookingResource] ?? []
let products = (productFilterViewModel.selectedValue as? MultipleFilterSelection)?.items as? [BookingProductFilter] ?? []
let customer = customerFilterViewModel.selectedValue as? CustomerFilter
let customers = (customerFilterViewModel.selectedValue as? MultipleFilterSelection)?.items as? [BookingCustomerFilter] ?? []
let attendanceStatuses = (attendanceStatusFilterViewModel.selectedValue as? MultipleFilterSelection)?.items as? [BookingAttendanceStatus] ?? []
let paymentStatuses = (paymentStatusFilterViewModel.selectedValue as? MultipleFilterSelection)?.items as? [BookingStatus] ?? []
let dateRange = dateTimeFilterViewModel.selectedValue as? BookingDateRangeFilter
Expand All @@ -47,7 +47,7 @@ final class BookingFiltersViewModel: FilterListViewModel {
products: products,
attendanceStatuses: attendanceStatuses,
paymentStatuses: paymentStatuses,
customer: customer,
customers: customers,
dateRange: dateRange,
numberOfActiveFilters: numberOfActiveFilters)
}
Expand Down Expand Up @@ -90,7 +90,7 @@ final class BookingFiltersViewModel: FilterListViewModel {
let products: [BookingProductFilter]
let attendanceStatuses: [BookingAttendanceStatus]
let paymentStatuses: [BookingStatus]
let customer: CustomerFilter?
let customers: [BookingCustomerFilter]
let dateRange: BookingDateRangeFilter?

let numberOfActiveFilters: Int
Expand All @@ -100,7 +100,7 @@ final class BookingFiltersViewModel: FilterListViewModel {
products = []
attendanceStatuses = []
paymentStatuses = []
customer = nil
customers = []
dateRange = nil
numberOfActiveFilters = 0
}
Expand All @@ -109,14 +109,14 @@ final class BookingFiltersViewModel: FilterListViewModel {
products: [BookingProductFilter],
attendanceStatuses: [BookingAttendanceStatus],
paymentStatuses: [BookingStatus],
customer: CustomerFilter?,
customers: [BookingCustomerFilter],
dateRange: BookingDateRangeFilter?,
numberOfActiveFilters: Int) {
self.teamMembers = teamMembers
self.products = products
self.attendanceStatuses = attendanceStatuses
self.paymentStatuses = paymentStatuses
self.customer = customer
self.customers = customers
self.dateRange = dateRange
self.numberOfActiveFilters = numberOfActiveFilters
}
Expand All @@ -127,9 +127,8 @@ final class BookingFiltersViewModel: FilterListViewModel {
attendanceStatuses.map { $0.localizedTitle } +
paymentStatuses.map { $0.localizedTitle }

if let customer {
readable.append(customer.description)
}
readable += customers.map { $0.name }

if let dateRange {
readable.append(dateRange.description)
}
Expand Down Expand Up @@ -184,8 +183,8 @@ extension BookingFiltersViewModel.BookingListFilter {
selectedValue: MultipleFilterSelection(items: filters.products))
case .customer(let siteID):
return FilterTypeViewModel(title: title,
listSelectorConfig: .customer(siteID: siteID, source: .booking),
selectedValue: filters.customer)
listSelectorConfig: .bookingCustomers(siteID: siteID),
selectedValue: MultipleFilterSelection(items: filters.customers))
case .attendanceStatus:
let options: [BookingAttendanceStatus?] = [.booked, .checkedIn, .cancelled, .noShow]
return FilterTypeViewModel(title: title,
Expand Down Expand Up @@ -244,6 +243,14 @@ extension BookingProductFilter: FilterType {
var isActive: Bool { true }
}

extension BookingCustomerFilter: FilterType {
/// The user-facing description of the filter value.
var description: String { name }

/// Whether the filter is set to a non-empty value.
var isActive: Bool { true }
}

extension BookingDateRangeFilter: FilterType {
var description: String {
if let startDate, let endDate {
Expand Down Expand Up @@ -314,8 +321,8 @@ private extension BookingFiltersViewModel.BookingListFilter {
comment: "Row title for filtering bookings by product.")

static let rowTitleCustomer = NSLocalizedString(
"bookingFilters.rowTitleCustomer",
value: "Customer name",
"bookingFilters.rowCustomer",
value: "Customer",
comment: "Row title for filtering bookings by customer.")

static let rowTitleAttendanceStatus = NSLocalizedString(
Expand Down
Loading