Skip to content

Commit daffe2d

Browse files
authored
Merge pull request #7885 from woocommerce/issue/7741-fill-order-data-from-customer-search
Customer search: Fill customer fields on Order screen
2 parents d0a7d2f + 2b772b7 commit daffe2d

File tree

14 files changed

+239
-50
lines changed

14 files changed

+239
-50
lines changed

Networking/NetworkingTests/Mapper/WCAnalyticsCustomerMapperTests.swift

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class WCAnalyticsCustomerMapperTests: XCTestCase {
3535
customers = try! mapper.map(response: data)
3636

3737
// Then
38-
XCTAssertEqual(customers.count, 3)
38+
XCTAssertEqual(customers.count, 4)
3939
}
4040

4141
func test_WCAnalyticsCustomer_array_response_values_are_correctly_parsed() {
@@ -50,11 +50,13 @@ class WCAnalyticsCustomerMapperTests: XCTestCase {
5050
let customers = try! mapper.map(response: data)
5151

5252
// Then
53-
XCTAssertEqual(customers[0].userID, 1)
54-
XCTAssertEqual(customers[0].name, "John")
55-
XCTAssertEqual(customers[1].userID, 2)
56-
XCTAssertEqual(customers[1].name, "Paul")
57-
XCTAssertEqual(customers[2].userID, 3)
58-
XCTAssertEqual(customers[2].name, "John Doe")
53+
XCTAssertEqual(customers[0].userID, 0)
54+
XCTAssertEqual(customers[0].name, "Matt The Unregistered")
55+
XCTAssertEqual(customers[1].userID, 1)
56+
XCTAssertEqual(customers[1].name, "John")
57+
XCTAssertEqual(customers[2].userID, 2)
58+
XCTAssertEqual(customers[2].name, "Paul")
59+
XCTAssertEqual(customers[3].userID, 3)
60+
XCTAssertEqual(customers[3].name, "John Doe")
5961
}
6062
}

Networking/NetworkingTests/Remote/WCAnalyticsCustomerRemoteTests.swift

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,15 @@ class WCAnalyticsCustomerRemoteTests: XCTestCase {
4545
let customers = try XCTUnwrap(result.get())
4646
let hasSearchParameter = network.queryParameters?.contains(where: { $0 == "search=John" }) ?? false
4747
XCTAssertTrue(hasSearchParameter)
48-
assertEqual(3, customers.count)
49-
assertEqual(1, customers[0].userID)
50-
assertEqual(2, customers[1].userID)
51-
assertEqual(3, customers[2].userID)
52-
assertEqual("John", customers[0].name)
53-
assertEqual("Paul", customers[1].name)
54-
assertEqual("John Doe", customers[2].name)
48+
assertEqual(4, customers.count)
49+
assertEqual(0, customers[0].userID)
50+
assertEqual(1, customers[1].userID)
51+
assertEqual(2, customers[2].userID)
52+
assertEqual(3, customers[3].userID)
53+
assertEqual("Matt The Unregistered", customers[0].name)
54+
assertEqual("John", customers[1].name)
55+
assertEqual("Paul", customers[2].name)
56+
assertEqual("John Doe", customers[3].name)
5557
}
5658

5759
func test_WCAnalyticsCustomerRemote_when_calls_retrieveCustomersByName_fails_then_returns_result_isFailure() {

Networking/NetworkingTests/Responses/wc-analytics-customers.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
{ "data":
22
[
3+
{
4+
"id":0,
5+
"user_id":0,
6+
"username":"Matt.the.unregistered",
7+
"name":"Matt The Unregistered",
8+
"email":"[email protected]",
9+
"country":"US",
10+
"city":"San Francisco",
11+
"state":"CA",
12+
"postcode":"94103",
13+
"date_registered":null,
14+
"date_last_active":"2022-07-12T08:36:54",
15+
"date_last_order":"2022-07-12 08:36:54",
16+
"orders_count":1,
17+
"total_spend":10,
18+
"avg_order_value":10,
19+
"date_registered_gmt":null,
20+
"date_last_active_gmt":"2022-07-12T08:36:54"
21+
},
322
{
423
"id":1,
524
"user_id":1,

WooCommerce/Classes/ViewRelated/Orders/Order Creation/CustomerSection/CreateOrderAddressFormViewModel.swift

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -97,24 +97,6 @@ final class CreateOrderAddressFormViewModel: AddressFormViewModel, AddressFormVi
9797
override func trackOnLoad() { }
9898

9999
func userDidCancelFlow() { }
100-
101-
/// Dispatches the searchCustomers action when the Search button is tapped.
102-
/// The hardcoded `keyword` is temporary until we implement the rest of the feature:
103-
/// https://github.com/woocommerce/woocommerce-ios/issues/7741
104-
///
105-
func customerSearchTapped() {
106-
let action = CustomerAction.searchCustomers(
107-
siteID: siteID,
108-
keyword: "hello") { result in
109-
switch result {
110-
case .success(_):
111-
print("Success")
112-
case .failure(let error):
113-
print(error)
114-
}
115-
}
116-
stores.dispatch(action)
117-
}
118100
}
119101

120102
private extension CreateOrderAddressFormViewModel {

WooCommerce/Classes/ViewRelated/Orders/Order Creation/CustomerSection/CustomerSearchUICommand.swift

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,17 @@ final class CustomerSearchUICommand: SearchUICommand {
3434
}
3535

3636
func createStarterViewController() -> UIViewController? {
37-
nil
37+
createEmptyStateViewController()
38+
}
39+
40+
func configureEmptyStateViewControllerBeforeDisplay(viewController: EmptyStateViewController, searchKeyword: String) {
41+
let boldSearchKeyword = NSAttributedString(string: searchKeyword,
42+
attributes: [.font: EmptyStateViewController.Config.messageFont.bold])
43+
let format = Localization.emptySearchResults
44+
let message = NSMutableAttributedString(string: format)
45+
46+
message.replaceFirstOccurrence(of: "%@", with: boldSearchKeyword)
47+
viewController.configure(.simple(message: message, image: .emptySearchResultsImage))
3848
}
3949

4050
func createCellViewModel(model: Customer) -> TitleAndSubtitleAndStatusTableViewCell.ViewModel {
@@ -61,10 +71,6 @@ final class CustomerSearchUICommand: SearchUICommand {
6171
}
6272

6373
func didSelectSearchResult(model: Customer, from viewController: UIViewController, reloadData: () -> Void, updateActionButton: () -> Void) {
64-
// Not implemented yet
65-
print("1 - Customer tapped")
66-
print("2 - Customer ID: \(model.customerID) - Name: \(model.firstName ?? ""))")
67-
// Customer data will go up to EditOrderAddressForm, via OrderCustomerListView completion handler
6874
onDidSelectSearchResult(model)
6975
}
7076

@@ -75,7 +81,11 @@ final class CustomerSearchUICommand: SearchUICommand {
7581

7682
private extension CustomerSearchUICommand {
7783
enum Localization {
78-
static let searchBarPlaceHolder = NSLocalizedString("Search all customers",
79-
comment: "Customer Search Placeholder")
84+
static let searchBarPlaceHolder = NSLocalizedString(
85+
"Search all customers",
86+
comment: "Customer Search Placeholder")
87+
static let emptySearchResults = NSLocalizedString(
88+
"We're sorry, we couldn't find results for “%@”",
89+
comment: "Message for empty Customers search results. %@ is a placeholder for the text entered by the user.")
8090
}
8191
}

WooCommerce/Classes/ViewRelated/Orders/Order Creation/CustomerSection/OrderCustomerListView.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Yosemite
33
import SwiftUI
44

55
/// `SwiftUI` wrapper for `SearchViewController` using `CustomerSearchUICommand`
6-
/// TODO: Make it generic
6+
///
77
struct OrderCustomerListView: UIViewControllerRepresentable {
88

99
let siteID: Int64
@@ -16,15 +16,13 @@ struct OrderCustomerListView: UIViewControllerRepresentable {
1616
storeID: siteID,
1717
command: CustomerSearchUICommand(siteID: siteID, onDidSelectSearchResult: onCustomerTapped),
1818
cellType: TitleAndSubtitleAndStatusTableViewCell.self,
19-
// Must conform to SearchResultCell.
20-
// TODO: Proper cell for this cellType.
2119
cellSeparator: .none
2220
)
2321
let navigationController = WooNavigationController(rootViewController: viewController)
2422
return navigationController
2523
}
2624

2725
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
28-
// nope
26+
// not implemented
2927
}
3028
}

WooCommerce/Classes/ViewRelated/Orders/Order Details/Address Edit/AddressFormViewModelProtocol.swift

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,9 @@ protocol AddressFormViewModelProtocol: ObservableObject {
104104
///
105105
func createSecondaryStateViewModel() -> StateSelectorViewModel
106106

107-
/// Callback method when the Customer Search button is tapped
107+
/// Triggers the logic to fill Customer Order details when a Customer is selected
108108
///
109-
func customerSearchTapped()
109+
func customerSelectedFromSearch(customer: Customer)
110110
}
111111

112112
/// Type to hold values from all the form fields
@@ -401,6 +401,40 @@ open class AddressFormViewModel: ObservableObject {
401401
let secondaryEmailIsValid = secondaryFields.email.isEmpty || EmailFormatValidator.validate(string: fields.email)
402402
return primaryEmailIsValid && secondaryEmailIsValid
403403
}
404+
405+
/// Fills Order AddressFormFields with Customer details
406+
///
407+
func customerSelectedFromSearch(customer: Customer) {
408+
fillCustomerFields(customer: customer)
409+
let addressesDiffer = customer.billing != customer.shipping
410+
showDifferentAddressForm = addressesDiffer
411+
}
412+
413+
private func fillCustomerFields(customer: Customer) {
414+
fields = populate(fields: fields, with: customer.billing)
415+
secondaryFields = populate(fields: secondaryFields, with: customer.shipping)
416+
}
417+
418+
private func populate(fields: AddressFormFields, with address: Address?) -> AddressFormFields {
419+
var fields = fields
420+
421+
fields.firstName = address?.firstName ?? ""
422+
fields.lastName = address?.lastName ?? ""
423+
// Email is declared optional because we're using the same property from the Address model
424+
// for both Shipping and Billing details:
425+
// https://github.com/woocommerce/woocommerce-ios/issues/7993
426+
fields.email = address?.email ?? ""
427+
fields.phone = address?.phone ?? ""
428+
fields.company = address?.company ?? ""
429+
fields.address1 = address?.address1 ?? ""
430+
fields.address2 = address?.address2 ?? ""
431+
fields.city = address?.city ?? ""
432+
fields.postcode = address?.postcode ?? ""
433+
fields.country = address?.country ?? ""
434+
fields.state = address?.state ?? ""
435+
436+
return fields
437+
}
404438
}
405439

406440
extension AddressFormViewModel {

WooCommerce/Classes/ViewRelated/Orders/Order Details/Address Edit/EditOrderAddressForm.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,8 @@ struct EditOrderAddressForm<ViewModel: AddressFormViewModelProtocol>: View {
172172
.notice($viewModel.notice)
173173
.sheet(isPresented: $showingCustomerSearch, content: {
174174
OrderCustomerListView(siteID: viewModel.siteID, onCustomerTapped: { customer in
175-
// Not implemented yet.
176-
print("3 - Customer Callback. Fill Order data with Customer details")
177-
print("4 - Customer ID: \(customer.customerID) - Name: \(customer.firstName ?? ""))")
175+
viewModel.customerSelectedFromSearch(customer: customer)
176+
showingCustomerSearch = false
178177
})
179178
})
180179
}

WooCommerce/Classes/ViewRelated/Orders/Order Details/Address Edit/EditOrderAddressFormViewModel.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,6 @@ final class EditOrderAddressFormViewModel: AddressFormViewModel, AddressFormView
164164
func userDidCancelFlow() {
165165
analytics.track(event: WooAnalyticsEvent.OrderDetailsEdit.orderDetailEditFlowCanceled(subject: self.analyticsFlowType()))
166166
}
167-
168-
func customerSearchTapped() {}
169167
}
170168

171169
extension EditOrderAddressFormViewModel {

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,7 @@
997997
57F2C6CD246DECC10074063B /* SummaryTableViewCellViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57F2C6CC246DECC10074063B /* SummaryTableViewCellViewModelTests.swift */; };
998998
57F42E40253768D600EA87F7 /* TitleAndEditableValueTableViewCellViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57F42E3F253768D600EA87F7 /* TitleAndEditableValueTableViewCellViewModelTests.swift */; };
999999
581D5052274AA2480089B6AD /* View+AutofocusTextModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581D5051274AA2480089B6AD /* View+AutofocusTextModifier.swift */; };
1000+
682210ED2909666600814E14 /* CustomerSearchUICommandTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 682210EC2909666600814E14 /* CustomerSearchUICommandTests.swift */; };
10001001
6827140F28A3988300E6E3F6 /* DismissableNoticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6827140E28A3988300E6E3F6 /* DismissableNoticeView.swift */; };
10011002
6827141128A5410D00E6E3F6 /* NewSimplePaymentsLocationNoticeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6827141028A5410D00E6E3F6 /* NewSimplePaymentsLocationNoticeViewModel.swift */; };
10021003
6827141528A671B900E6E3F6 /* NewSimplePaymentsLocationNoticeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6827141428A671B900E6E3F6 /* NewSimplePaymentsLocationNoticeViewController.swift */; };
@@ -2913,6 +2914,7 @@
29132914
57F2C6CC246DECC10074063B /* SummaryTableViewCellViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SummaryTableViewCellViewModelTests.swift; sourceTree = "<group>"; };
29142915
57F42E3F253768D600EA87F7 /* TitleAndEditableValueTableViewCellViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleAndEditableValueTableViewCellViewModelTests.swift; sourceTree = "<group>"; };
29152916
581D5051274AA2480089B6AD /* View+AutofocusTextModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+AutofocusTextModifier.swift"; sourceTree = "<group>"; };
2917+
682210EC2909666600814E14 /* CustomerSearchUICommandTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerSearchUICommandTests.swift; sourceTree = "<group>"; };
29162918
6827140E28A3988300E6E3F6 /* DismissableNoticeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissableNoticeView.swift; sourceTree = "<group>"; };
29172919
6827141028A5410D00E6E3F6 /* NewSimplePaymentsLocationNoticeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewSimplePaymentsLocationNoticeViewModel.swift; sourceTree = "<group>"; };
29182920
6827141428A671B900E6E3F6 /* NewSimplePaymentsLocationNoticeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewSimplePaymentsLocationNoticeViewController.swift; sourceTree = "<group>"; };
@@ -6031,6 +6033,7 @@
60316033
573D0ACC2458665C004DE614 /* Search */ = {
60326034
isa = PBXGroup;
60336035
children = (
6036+
682210EB2909664800814E14 /* Customer */,
60346037
020C908224C84638001E2BEB /* Product */,
60356038
);
60366039
path = Search;
@@ -6206,6 +6209,14 @@
62066209
path = TitleAndEditableValueTableViewCell;
62076210
sourceTree = "<group>";
62086211
};
6212+
682210EB2909664800814E14 /* Customer */ = {
6213+
isa = PBXGroup;
6214+
children = (
6215+
682210EC2909666600814E14 /* CustomerSearchUICommandTests.swift */,
6216+
);
6217+
path = Customer;
6218+
sourceTree = "<group>";
6219+
};
62096220
6856D6E9B1C3C89938DCAD5C /* Testing */ = {
62106221
isa = PBXGroup;
62116222
children = (
@@ -10665,6 +10676,7 @@
1066510676
03FBDAFD263EE4E800ACE257 /* CouponListViewModelTests.swift in Sources */,
1066610677
036F6EA6281847D5006D84F8 /* PaymentCaptureOrchestratorTests.swift in Sources */,
1066710678
B555531321B57E8800449E71 /* MockUserNotificationsCenterAdapter.swift in Sources */,
10679+
682210ED2909666600814E14 /* CustomerSearchUICommandTests.swift in Sources */,
1066810680
4590B652261C8D1E00A6FCE0 /* WeightFormatterTests.swift in Sources */,
1066910681
D8C11A6022E2479800D4A88D /* OrderPaymentDetailsViewModelTests.swift in Sources */,
1067010682
B622BC74289CF19400B10CEC /* WaitingTimeTrackerTests.swift in Sources */,

0 commit comments

Comments
 (0)