Skip to content

Commit 39762f1

Browse files
authored
Merge pull request #6183 from woocommerce/issue/6128-select-support
Order Creation: Wire remove product support to LocalOrderSynchronizer
2 parents c77f0d5 + 7b1fa0e commit 39762f1

File tree

4 files changed

+44
-102
lines changed

4 files changed

+44
-102
lines changed

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

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,10 @@ private struct ProductsSection: View {
106106
ForEach(viewModel.productRows) { productRow in
107107
ProductRow(viewModel: productRow)
108108
.onTapGesture {
109-
// TODO: Support selecting an order item
110-
// viewModel.selectOrderItem(productRow.id)
109+
viewModel.selectOrderItem(productRow.id)
111110
}
112-
.sheet(item: $viewModel.selectedOrderItem) { item in
113-
createProductInOrderView(for: item)
111+
.sheet(item: $viewModel.selectedProductViewModel) { productViewModel in
112+
ProductInOrder(viewModel: productViewModel)
114113
}
115114

116115
Divider()
@@ -138,17 +137,6 @@ private struct ProductsSection: View {
138137
Divider()
139138
}
140139
}
141-
142-
@ViewBuilder private func createProductInOrderView(for item: NewOrderViewModel.NewOrderItem) -> some View {
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-
// }
150-
EmptyView()
151-
}
152140
}
153141

154142
// MARK: Constants

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

Lines changed: 25 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ final class NewOrderViewModel: ObservableObject {
1212

1313
private var cancellables: Set<AnyCancellable> = []
1414

15-
/// Order details used to create the order
16-
///
17-
@Published var orderDetails = OrderDetails()
18-
1915
/// Active navigation bar trailing item.
2016
/// Defaults to no visible button.
2117
///
@@ -114,10 +110,10 @@ final class NewOrderViewModel: ObservableObject {
114110
///
115111
@Published private(set) var productRows: [ProductRowViewModel] = []
116112

117-
/// Item selected from the list of products in the order.
113+
/// Selected product view model to render.
118114
/// Used to open the product details in `ProductInOrder`.
119115
///
120-
@Published var selectedOrderItem: NewOrderItem? = nil
116+
@Published var selectedProductViewModel: ProductInOrderViewModel? = nil
121117

122118
// MARK: Payment properties
123119

@@ -171,18 +167,19 @@ final class NewOrderViewModel: ObservableObject {
171167
configurePaymentDataViewModel()
172168
}
173169

174-
/// Selects an order item.
170+
/// Selects an order item by setting the `selectedProductViewModel`.
175171
///
176172
/// - Parameter id: ID of the order item to select
177-
func selectOrderItem(_ id: String) {
178-
selectedOrderItem = orderDetails.items.first(where: { $0.id == id })
173+
func selectOrderItem(_ id: Int64) {
174+
selectedProductViewModel = createSelectedProductViewModel(itemID: id)
179175
}
180176

181177
/// Removes an item from the order.
182178
///
183179
/// - Parameter item: Item to remove from the order
184-
func removeItemFromOrder(_ item: NewOrderItem) {
185-
orderDetails.items.removeAll(where: { $0 == item })
180+
func removeItemFromOrder(_ item: OrderItem) {
181+
guard let input = createUpdateProductInput(item: item, quantity: 0) else { return }
182+
orderSynchronizer.setProduct.send(input)
186183
configureProductRowViewModels()
187184
}
188185

@@ -271,19 +268,6 @@ extension NewOrderViewModel {
271268
case loading
272269
}
273270

274-
/// Type to hold all order detail values
275-
///
276-
struct OrderDetails {
277-
var items: [NewOrderItem] = []
278-
279-
func toOrder() -> Order {
280-
OrderFactory.emptyNewOrder.copy(status: .pending,
281-
items: items.map { $0.orderItem },
282-
billingAddress: nil,
283-
shippingAddress: nil)
284-
}
285-
}
286-
287271
/// Representation of order status display properties
288272
///
289273
struct StatusBadgeViewModel {
@@ -312,52 +296,6 @@ extension NewOrderViewModel {
312296
}
313297
}
314298

315-
/// Representation of new items in an order.
316-
///
317-
struct NewOrderItem: Equatable, Identifiable {
318-
let id: String
319-
let productID: Int64
320-
let variationID: Int64
321-
var quantity: Decimal
322-
let price: NSDecimalNumber
323-
var subtotal: String {
324-
String(describing: quantity * price.decimalValue)
325-
}
326-
327-
var orderItem: OrderItem {
328-
OrderItem(itemID: 0,
329-
name: "",
330-
productID: productID,
331-
variationID: variationID,
332-
quantity: quantity,
333-
price: price,
334-
sku: nil,
335-
subtotal: subtotal,
336-
subtotalTax: "",
337-
taxClass: "",
338-
taxes: [],
339-
total: "",
340-
totalTax: "",
341-
attributes: [])
342-
}
343-
344-
init(product: Product, quantity: Decimal) {
345-
self.id = UUID().uuidString
346-
self.productID = product.productID
347-
self.variationID = 0 // Products in an order are represented in Core with a variation ID of 0
348-
self.quantity = quantity
349-
self.price = NSDecimalNumber(string: product.price)
350-
}
351-
352-
init(variation: ProductVariation, quantity: Decimal) {
353-
self.id = UUID().uuidString
354-
self.productID = variation.productID
355-
self.variationID = variation.productVariationID
356-
self.quantity = quantity
357-
self.price = NSDecimalNumber(string: variation.price)
358-
}
359-
}
360-
361299
/// Representation of customer data display properties
362300
///
363301
struct CustomerDataViewModel {
@@ -581,6 +519,23 @@ private extension NewOrderViewModel {
581519
// Return a new input with the new quantity but with the same item id to properly reference the update.
582520
return OrderSyncProductInput(id: item.itemID, product: product, quantity: quantity)
583521
}
522+
523+
/// Creates a `ProductInOrderViewModel` based on the provided order item id.
524+
///
525+
func createSelectedProductViewModel(itemID: Int64) -> ProductInOrderViewModel? {
526+
// Find order item based on the provided id.
527+
// Creates the product row view model needed for `ProductInOrderViewModel`.
528+
guard
529+
let orderItem = orderSynchronizer.order.items.first(where: { $0.itemID == itemID }),
530+
let rowViewModel = createProductRowViewModel(for: orderItem, canChangeQuantity: false)
531+
else {
532+
return nil
533+
}
534+
535+
return ProductInOrderViewModel(productRowViewModel: rowViewModel) { [weak self] in
536+
self?.removeItemFromOrder(orderItem)
537+
}
538+
}
584539
}
585540

586541
private extension NewOrderViewModel {

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import Yosemite
22

33
/// View model for `ProductInOrder`.
44
///
5-
final class ProductInOrderViewModel {
6-
5+
final class ProductInOrderViewModel: Identifiable {
76
/// The product being edited.
87
///
98
let productRowViewModel: ProductRowViewModel

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

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ class NewOrderViewModelTests: XCTestCase {
164164
XCTAssertTrue(viewModel.productRows.contains(where: { $0.productOrVariationID == sampleProductID }), "Product rows do not contain expected product")
165165
}
166166

167-
func test_order_details_are_updated_when_product_quantity_changes() throws {
167+
func test_order_details_are_updated_when_product_quantity_changes() {
168168
// Given
169169
let product = Product.fake().copy(siteID: sampleSiteID, productID: sampleProductID, purchasable: true)
170170
let storageManager = MockStorageManager()
@@ -184,8 +184,6 @@ class NewOrderViewModelTests: XCTestCase {
184184
}
185185

186186
func test_selectOrderItem_selects_expected_order_item() throws {
187-
throw XCTSkip("Test disabled while we enable select order support on OrderSynchronizer")
188-
189187
// Given
190188
let product = Product.fake().copy(siteID: sampleSiteID, productID: sampleProductID, purchasable: true)
191189
let storageManager = MockStorageManager()
@@ -194,14 +192,15 @@ class NewOrderViewModelTests: XCTestCase {
194192
viewModel.addProductViewModel.selectProduct(product.productID)
195193

196194
// When
197-
let expectedOrderItem = viewModel.orderDetails.items[0]
198-
viewModel.selectOrderItem(expectedOrderItem.id)
195+
let expectedRow = viewModel.productRows[0]
196+
viewModel.selectOrderItem(expectedRow.id)
199197

200198
// Then
201-
XCTAssertEqual(viewModel.selectedOrderItem, expectedOrderItem)
199+
XCTAssertNotNil(viewModel.selectedProductViewModel)
200+
XCTAssertEqual(viewModel.selectedProductViewModel?.productRowViewModel.id, expectedRow.id)
202201
}
203202

204-
func test_view_model_is_updated_when_product_is_removed_from_order() throws {
203+
func test_view_model_is_updated_when_product_is_removed_from_order() {
205204
// Given
206205
let product0 = Product.fake().copy(siteID: sampleSiteID, productID: 0, purchasable: true)
207206
let product1 = Product.fake().copy(siteID: sampleSiteID, productID: 1, purchasable: true)
@@ -214,13 +213,13 @@ class NewOrderViewModelTests: XCTestCase {
214213
viewModel.addProductViewModel.selectProduct(product1.productID)
215214

216215
// When
217-
throw XCTSkip("Test disabled while we enable remove item support on OrderSynchronizer")
218-
let expectedRemainingItem = viewModel.orderDetails.items[1]
219-
viewModel.removeItemFromOrder(viewModel.orderDetails.items[0])
216+
let expectedRemainingRow = viewModel.productRows[1]
217+
let itemToRemove = OrderItem.fake().copy(itemID: viewModel.productRows[0].id)
218+
viewModel.removeItemFromOrder(itemToRemove)
220219

221220
// Then
222221
XCTAssertFalse(viewModel.productRows.contains(where: { $0.productOrVariationID == product0.productID }))
223-
XCTAssertEqual(viewModel.orderDetails.items, [expectedRemainingItem])
222+
XCTAssertEqual(viewModel.productRows.map { $0.id }, [expectedRemainingRow].map { $0.id })
224223
}
225224

226225
func test_createProductRowViewModel_creates_expected_row_for_product() {
@@ -321,7 +320,7 @@ class NewOrderViewModelTests: XCTestCase {
321320
XCTAssertEqual(paymentDataViewModel.orderTotal, "£30.00")
322321
}
323322

324-
func test_payment_section_only_displayed_when_order_has_products() throws {
323+
func test_payment_section_only_displayed_when_order_has_products() {
325324
// Given
326325
let product = Product.fake().copy(siteID: sampleSiteID, productID: sampleProductID, purchasable: true)
327326
let storageManager = MockStorageManager()
@@ -333,12 +332,13 @@ class NewOrderViewModelTests: XCTestCase {
333332
XCTAssertTrue(viewModel.shouldShowPaymentSection)
334333

335334
// When & Then
336-
throw XCTSkip("This unit test needs to be reenabled when the remove item method is migrated")
337-
viewModel.removeItemFromOrder(viewModel.orderDetails.items[0])
335+
let itemToRemove = OrderItem.fake().copy(itemID: viewModel.productRows[0].id, productID: product.productID)
336+
viewModel.removeItemFromOrder(itemToRemove)
337+
338338
XCTAssertFalse(viewModel.shouldShowPaymentSection)
339339
}
340340

341-
func test_payment_section_is_updated_when_products_update() throws {
341+
func test_payment_section_is_updated_when_products_update() {
342342
// Given
343343
let currencySettings = CurrencySettings(currencyCode: .GBP, currencyPosition: .left, thousandSeparator: "", decimalSeparator: ".", numberOfDecimals: 2)
344344
let product = Product.fake().copy(siteID: sampleSiteID, productID: sampleProductID, price: "8.50", purchasable: true)

0 commit comments

Comments
 (0)