Skip to content

Commit edd116f

Browse files
authored
Merge pull request #6170 from woocommerce/issue/6128-add-product-support
Order Creation: Wire select product support to LocalOrderSynchronizer
2 parents f72fb76 + e152cf2 commit edd116f

File tree

6 files changed

+52
-40
lines changed

6 files changed

+52
-40
lines changed

WooCommerce/Classes/ViewRelated/Orders/Order Creation/NewOrder.swift

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ private struct ProductsSection: View {
106106
ForEach(viewModel.productRows) { productRow in
107107
ProductRow(viewModel: productRow)
108108
.onTapGesture {
109-
viewModel.selectOrderItem(productRow.id)
109+
// TODO: Support selecting an order item
110+
// viewModel.selectOrderItem(productRow.id)
110111
}
111112
.sheet(item: $viewModel.selectedOrderItem) { item in
112113
createProductInOrderView(for: item)
@@ -139,12 +140,13 @@ private struct ProductsSection: View {
139140
}
140141

141142
@ViewBuilder private func createProductInOrderView(for item: NewOrderViewModel.NewOrderItem) -> some View {
142-
if let productRowViewModel = viewModel.createProductRowViewModel(for: item, canChangeQuantity: false) {
143-
let productInOrderViewModel = ProductInOrderViewModel(productRowViewModel: productRowViewModel) {
144-
viewModel.removeItemFromOrder(item)
145-
}
146-
ProductInOrder(viewModel: productInOrderViewModel)
147-
}
143+
// TODO: Support selecting an order item
144+
// if let productRowViewModel = viewModel.createProductRowViewModel(for: item, canChangeQuantity: false) {
145+
// let productInOrderViewModel = ProductInOrderViewModel(productRowViewModel: productRowViewModel) {
146+
// viewModel.removeItemFromOrder(item)
147+
// }
148+
// ProductInOrder(viewModel: productInOrderViewModel)
149+
// }
148150
EmptyView()
149151
}
150152
}

WooCommerce/Classes/ViewRelated/Orders/Order Creation/NewOrderViewModel.swift

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ final class NewOrderViewModel: ObservableObject {
124124
/// Indicates if the Payment section should be shown
125125
///
126126
var shouldShowPaymentSection: Bool {
127-
orderDetails.items.isNotEmpty
127+
orderSynchronizer.order.items.isNotEmpty
128128
}
129129

130130
/// Defines if the view should be disabled.
@@ -188,21 +188,21 @@ final class NewOrderViewModel: ObservableObject {
188188

189189
/// Creates a view model for the `ProductRow` corresponding to an order item.
190190
///
191-
func createProductRowViewModel(for item: NewOrderItem, canChangeQuantity: Bool) -> ProductRowViewModel? {
191+
func createProductRowViewModel(for item: OrderItem, canChangeQuantity: Bool) -> ProductRowViewModel? {
192192
guard let product = allProducts.first(where: { $0.productID == item.productID }) else {
193193
return nil
194194
}
195195

196196
if item.variationID != 0, let variation = allProductVariations.first(where: { $0.productVariationID == item.variationID }) {
197197
let attributes = ProductVariationFormatter().generateAttributes(for: variation, from: product.attributes)
198-
return ProductRowViewModel(id: item.id,
198+
return ProductRowViewModel(id: item.itemID,
199199
productVariation: variation,
200200
name: product.name,
201201
quantity: item.quantity,
202202
canChangeQuantity: canChangeQuantity,
203203
displayMode: .attributes(attributes))
204204
} else {
205-
return ProductRowViewModel(id: item.id, product: product, quantity: item.quantity, canChangeQuantity: canChangeQuantity)
205+
return ProductRowViewModel(id: item.itemID, product: product, quantity: item.quantity, canChangeQuantity: canChangeQuantity)
206206
}
207207
}
208208

@@ -436,8 +436,8 @@ private extension NewOrderViewModel {
436436
/// Adds a selected product (from the product list) to the order.
437437
///
438438
func addProductToOrder(_ product: Product) {
439-
let newOrderItem = NewOrderItem(product: product, quantity: 1)
440-
orderDetails.items.append(newOrderItem)
439+
let input = OrderSyncProductInput(product: .product(product), quantity: 1)
440+
orderSynchronizer.setProduct.send(input)
441441
configureProductRowViewModels()
442442

443443
analytics.track(event: WooAnalyticsEvent.Orders.orderProductAdd(flow: .creation))
@@ -446,8 +446,8 @@ private extension NewOrderViewModel {
446446
/// Adds a selected product variation (from the product list) to the order.
447447
///
448448
func addProductVariationToOrder(_ variation: ProductVariation) {
449-
let newOrderItem = NewOrderItem(variation: variation, quantity: 1)
450-
orderDetails.items.append(newOrderItem)
449+
let input = OrderSyncProductInput(product: .variation(variation), quantity: 1)
450+
orderSynchronizer.setProduct.send(input)
451451
configureProductRowViewModels()
452452

453453
analytics.track(event: WooAnalyticsEvent.Orders.orderProductAdd(flow: .creation))
@@ -458,15 +458,16 @@ private extension NewOrderViewModel {
458458
func configureProductRowViewModels() {
459459
updateProductsResultsController()
460460
updateProductVariationsResultsController()
461-
productRows = orderDetails.items.enumerated().compactMap { index, item in
461+
productRows = orderSynchronizer.order.items.compactMap { item in
462462
guard let productRowViewModel = createProductRowViewModel(for: item, canChangeQuantity: true) else {
463463
return nil
464464
}
465465

466466
// Observe changes to the product quantity
467467
productRowViewModel.$quantity
468468
.sink { [weak self] newQuantity in
469-
self?.orderDetails.items[index].quantity = newQuantity
469+
// TODO: Add update quantity support
470+
// self?.orderDetails.items[index].quantity = newQuantity
470471
}
471472
.store(in: &cancellables)
472473

@@ -487,14 +488,14 @@ private extension NewOrderViewModel {
487488
/// Updates payment section view model based on items in the order.
488489
///
489490
func configurePaymentDataViewModel() {
490-
$orderDetails
491-
.map { [weak self] orderDetails in
491+
orderSynchronizer.orderPublisher
492+
.map { [weak self] order in
492493
guard let self = self else {
493494
return PaymentDataViewModel()
494495
}
495496

496-
let itemsTotal = orderDetails.items
497-
.map { $0.orderItem.subtotal }
497+
let itemsTotal = order.items
498+
.map { $0.subtotal }
498499
.compactMap { self.currencyFormatter.convertToDecimal(from: $0) }
499500
.reduce(NSDecimalNumber(value: 0), { $0.adding($1) })
500501
.stringValue
@@ -519,7 +520,7 @@ private extension NewOrderViewModel {
519520

520521
/// Tracks when the create order button is tapped.
521522
///
522-
/// Warning: This methods assume that `orderDetails.items.count` is equal to the product count,
523+
/// Warning: This methods assume that `orderSynchronizer.order.items.count` is equal to the product count,
523524
/// As the module evolves to handle more types of items, we need to update the property to something like `itemsCount`
524525
/// or figure out a better way to get the product count.
525526
///

WooCommerce/Classes/ViewRelated/Orders/Order Creation/ProductsSection/ProductRowViewModel.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ final class ProductRowViewModel: ObservableObject, Identifiable {
1313

1414
/// Unique ID for the view model.
1515
///
16-
let id: String
16+
let id: Int64
1717

1818
// MARK: Product properties
1919

@@ -93,7 +93,7 @@ final class ProductRowViewModel: ObservableObject, Identifiable {
9393
///
9494
let numberOfVariations: Int
9595

96-
init(id: String? = nil,
96+
init(id: Int64? = nil,
9797
productOrVariationID: Int64,
9898
name: String,
9999
sku: String?,
@@ -107,7 +107,7 @@ final class ProductRowViewModel: ObservableObject, Identifiable {
107107
numberOfVariations: Int = 0,
108108
variationDisplayMode: VariationDisplayMode? = nil,
109109
currencyFormatter: CurrencyFormatter = CurrencyFormatter(currencySettings: ServiceLocator.currencySettings)) {
110-
self.id = id ?? productOrVariationID.description
110+
self.id = id ?? Int64(UUID().uuidString.hashValue)
111111
self.productOrVariationID = productOrVariationID
112112
self.name = name
113113
self.sku = sku
@@ -125,7 +125,7 @@ final class ProductRowViewModel: ObservableObject, Identifiable {
125125

126126
/// Initialize `ProductRowViewModel` with a `Product`
127127
///
128-
convenience init(id: String? = nil,
128+
convenience init(id: Int64? = nil,
129129
product: Product,
130130
quantity: Decimal = 1,
131131
canChangeQuantity: Bool,
@@ -155,7 +155,7 @@ final class ProductRowViewModel: ObservableObject, Identifiable {
155155

156156
/// Initialize `ProductRowViewModel` with a `ProductVariation`
157157
///
158-
convenience init(id: String? = nil,
158+
convenience init(id: Int64? = nil,
159159
productVariation: ProductVariation,
160160
name: String,
161161
quantity: Decimal = 1,

WooCommerce/Classes/ViewRelated/Orders/Order Creation/Synchronizer/OrderSynchronizer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ struct OrderSyncProductInput {
1919
case product(Product)
2020
case variation(ProductVariation)
2121
}
22-
var id: Int64 = Int64(UUID().uuidString.hashValue)
22+
var id = Int64(UUID().uuidString.hashValue)
2323
let product: ProductType
2424
let quantity: Decimal
2525
}

WooCommerce/WooCommerceTests/ViewRelated/Orders/Order Creation/NewOrderViewModelTests.swift

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -163,10 +163,9 @@ class NewOrderViewModelTests: XCTestCase {
163163
// Then
164164
let expectedOrderItem = NewOrderViewModel.NewOrderItem(product: product, quantity: 1).orderItem
165165
XCTAssertTrue(viewModel.productRows.contains(where: { $0.productOrVariationID == sampleProductID }), "Product rows do not contain expected product")
166-
XCTAssertTrue(viewModel.orderDetails.items.contains(where: { $0.orderItem == expectedOrderItem }), "Order details do not contain expected order item")
167166
}
168167

169-
func test_order_details_are_updated_when_product_quantity_changes() {
168+
func test_order_details_are_updated_when_product_quantity_changes() throws {
170169
// Given
171170
let product = Product.fake().copy(siteID: sampleSiteID, productID: sampleProductID, purchasable: true)
172171
let storageManager = MockStorageManager()
@@ -181,12 +180,15 @@ class NewOrderViewModelTests: XCTestCase {
181180
viewModel.addProductViewModel.selectProduct(product.productID)
182181

183182
// Then
183+
throw XCTSkip("Test disabled while we enable update quantity support on OrderSynchronizer")
184184
let expectedOrderItem = NewOrderViewModel.NewOrderItem(product: product, quantity: 2).orderItem
185185
XCTAssertTrue(viewModel.orderDetails.items.contains(where: { $0.orderItem == expectedOrderItem }),
186186
"Order details do not contain order item with updated quantity")
187187
}
188188

189-
func test_selectOrderItem_selects_expected_order_item() {
189+
func test_selectOrderItem_selects_expected_order_item() throws {
190+
throw XCTSkip("Test disabled while we enable select order support on OrderSynchronizer")
191+
190192
// Given
191193
let product = Product.fake().copy(siteID: sampleSiteID, productID: sampleProductID, purchasable: true)
192194
let storageManager = MockStorageManager()
@@ -202,7 +204,7 @@ class NewOrderViewModelTests: XCTestCase {
202204
XCTAssertEqual(viewModel.selectedOrderItem, expectedOrderItem)
203205
}
204206

205-
func test_view_model_is_updated_when_product_is_removed_from_order() {
207+
func test_view_model_is_updated_when_product_is_removed_from_order() throws {
206208
// Given
207209
let product0 = Product.fake().copy(siteID: sampleSiteID, productID: 0, purchasable: true)
208210
let product1 = Product.fake().copy(siteID: sampleSiteID, productID: 1, purchasable: true)
@@ -215,6 +217,7 @@ class NewOrderViewModelTests: XCTestCase {
215217
viewModel.addProductViewModel.selectProduct(product1.productID)
216218

217219
// When
220+
throw XCTSkip("Test disabled while we enable remove item support on OrderSynchronizer")
218221
let expectedRemainingItem = viewModel.orderDetails.items[1]
219222
viewModel.removeItemFromOrder(viewModel.orderDetails.items[0])
220223

@@ -231,8 +234,8 @@ class NewOrderViewModelTests: XCTestCase {
231234
let viewModel = NewOrderViewModel(siteID: sampleSiteID, storageManager: storageManager)
232235

233236
// When
234-
let newOrderItem = NewOrderViewModel.NewOrderItem(product: product, quantity: 1)
235-
let productRow = viewModel.createProductRowViewModel(for: newOrderItem, canChangeQuantity: true)
237+
let orderItem = OrderItem.fake().copy(name: product.name, productID: product.productID, quantity: 1)
238+
let productRow = viewModel.createProductRowViewModel(for: orderItem, canChangeQuantity: true)
236239

237240
// Then
238241
let expectedProductRow = ProductRowViewModel(product: product, canChangeQuantity: true)
@@ -254,8 +257,11 @@ class NewOrderViewModelTests: XCTestCase {
254257
let viewModel = NewOrderViewModel(siteID: sampleSiteID, storageManager: storageManager)
255258

256259
// When
257-
let newOrderItem = NewOrderViewModel.NewOrderItem(variation: productVariation, quantity: 2)
258-
let productRow = viewModel.createProductRowViewModel(for: newOrderItem, canChangeQuantity: false)
260+
let orderItem = OrderItem.fake().copy(name: product.name,
261+
productID: product.productID,
262+
variationID: productVariation.productVariationID,
263+
quantity: 2)
264+
let productRow = viewModel.createProductRowViewModel(for: orderItem, canChangeQuantity: false)
259265

260266
// Then
261267
let expectedProductRow = ProductRowViewModel(productVariation: productVariation,
@@ -318,7 +324,7 @@ class NewOrderViewModelTests: XCTestCase {
318324
XCTAssertEqual(paymentDataViewModel.orderTotal, "£30.00")
319325
}
320326

321-
func test_payment_section_only_displayed_when_order_has_products() {
327+
func test_payment_section_only_displayed_when_order_has_products() throws {
322328
// Given
323329
let product = Product.fake().copy(siteID: sampleSiteID, productID: sampleProductID, purchasable: true)
324330
let storageManager = MockStorageManager()
@@ -330,11 +336,12 @@ class NewOrderViewModelTests: XCTestCase {
330336
XCTAssertTrue(viewModel.shouldShowPaymentSection)
331337

332338
// When & Then
339+
throw XCTSkip("This unit test needs to be reenabled when the remove item method is migrated")
333340
viewModel.removeItemFromOrder(viewModel.orderDetails.items[0])
334341
XCTAssertFalse(viewModel.shouldShowPaymentSection)
335342
}
336343

337-
func test_payment_section_is_updated_when_products_update() {
344+
func test_payment_section_is_updated_when_products_update() throws {
338345
// Given
339346
let currencySettings = CurrencySettings(currencyCode: .GBP, currencyPosition: .left, thousandSeparator: "", decimalSeparator: ".", numberOfDecimals: 2)
340347
let product = Product.fake().copy(siteID: sampleSiteID, productID: sampleProductID, price: "8.50", purchasable: true)
@@ -348,6 +355,7 @@ class NewOrderViewModelTests: XCTestCase {
348355
XCTAssertEqual(viewModel.paymentDataViewModel.orderTotal, "£8.50")
349356

350357
// When & Then
358+
throw XCTSkip("Test disabled while we enable update quantity support on OrderSynchronizer")
351359
viewModel.productRows[0].incrementQuantity()
352360
XCTAssertEqual(viewModel.paymentDataViewModel.itemsTotal, "£17.00")
353361
XCTAssertEqual(viewModel.paymentDataViewModel.orderTotal, "£17.00")

WooCommerce/WooCommerceTests/ViewRelated/Orders/Order Creation/ProductRowViewModelTests.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import XCTest
22
import Yosemite
3+
import Fakes
34
@testable import WooCommerce
45

56
class ProductRowViewModelTests: XCTestCase {
67

78
func test_viewModel_is_created_with_correct_initial_values_from_product() {
89
// Given
9-
let rowID = "0"
10+
let rowID = Int64(0)
1011
let imageURLString = "https://woo.com/woo.jpg"
1112
let product = Product.fake().copy(productID: 12,
1213
name: "Test Product",
@@ -38,7 +39,7 @@ class ProductRowViewModelTests: XCTestCase {
3839

3940
func test_viewModel_is_created_with_correct_initial_values_from_product_variation() {
4041
// Given
41-
let rowID = "0"
42+
let rowID = Int64(0)
4243
let imageURLString = "https://woo.com/woo.jpg"
4344
let name = "Blue - Any Size"
4445
let productVariation = ProductVariation.fake().copy(productVariationID: 12,

0 commit comments

Comments
 (0)