Skip to content

Commit 3e29b53

Browse files
committed
Merge branch 'trunk' into issue/6025-add-shipping-line-section
# Conflicts: # WooCommerce/Classes/ViewRelated/Orders/Order Creation/NewOrderViewModel.swift
2 parents 9de01a6 + d05affd commit 3e29b53

File tree

11 files changed

+355
-177
lines changed

11 files changed

+355
-177
lines changed

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

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ private struct ProductsSection: View {
108108
.onTapGesture {
109109
viewModel.selectOrderItem(productRow.id)
110110
}
111-
.sheet(item: $viewModel.selectedOrderItem) { item in
112-
createProductInOrderView(for: item)
111+
.sheet(item: $viewModel.selectedProductViewModel) { productViewModel in
112+
ProductInOrder(viewModel: productViewModel)
113113
}
114114

115115
Divider()
@@ -137,16 +137,6 @@ private struct ProductsSection: View {
137137
Divider()
138138
}
139139
}
140-
141-
@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-
}
148-
EmptyView()
149-
}
150140
}
151141

152142
// MARK: Constants

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

Lines changed: 125 additions & 127 deletions
Large diffs are not rendered by default.

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/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/StatusSection/OrderStatusSection.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ struct OrderStatusSection: View {
3434
.padding(.trailing, -Layout.linkButtonTrailingPadding) // remove trailing padding to align button title to the side
3535
.accessibilityLabel(Text(Localization.editButtonAccessibilityLabel))
3636
.sheet(isPresented: $viewModel.shouldShowOrderStatusList) {
37-
OrderStatusList(siteID: viewModel.siteID, status: viewModel.orderDetails.status) { newStatus in
37+
OrderStatusList(siteID: viewModel.siteID, status: viewModel.currentOrderStatus) { newStatus in
3838
viewModel.updateOrderStatus(newStatus: newStatus)
3939
}
4040
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import Foundation
2+
import Yosemite
3+
import Combine
4+
5+
/// Local implementation that does not syncs the order with the remote server.
6+
///
7+
final class LocalOrderSynchronizer: OrderSynchronizer {
8+
9+
// MARK: Outputs
10+
11+
@Published private(set) var state: OrderSyncState = .synced
12+
13+
var statePublisher: Published<OrderSyncState>.Publisher {
14+
$state
15+
}
16+
17+
@Published private(set) var order: Order = OrderFactory.emptyNewOrder
18+
19+
var orderPublisher: Published<Order>.Publisher {
20+
$order
21+
}
22+
23+
// MARK: Inputs
24+
25+
var setStatus = PassthroughSubject<OrderStatusEnum, Never>()
26+
27+
var setProduct = PassthroughSubject<OrderSyncProductInput, Never>()
28+
29+
var setAddresses = PassthroughSubject<OrderSyncAddressesInput?, Never>()
30+
31+
var setShipping = PassthroughSubject<ShippingLine?, Never>()
32+
33+
var setFee = PassthroughSubject<OrderFeeLine?, Never>()
34+
35+
// MARK: Private properties
36+
37+
private let siteID: Int64
38+
39+
private let stores: StoresManager
40+
41+
// MARK: Initializers
42+
43+
init(siteID: Int64, stores: StoresManager = ServiceLocator.stores) {
44+
self.siteID = siteID
45+
self.stores = stores
46+
bindInputs()
47+
}
48+
49+
// MARK: Methods
50+
func retrySync() {
51+
// No op
52+
}
53+
54+
/// Creates the order remotely.
55+
///
56+
func commitAllChanges(onCompletion: @escaping (Result<Order, Error>) -> Void) {
57+
let action = OrderAction.createOrder(siteID: siteID, order: order, onCompletion: onCompletion)
58+
stores.dispatch(action)
59+
}
60+
}
61+
62+
private extension LocalOrderSynchronizer {
63+
/// Updates order as inputs are received.
64+
///
65+
func bindInputs() {
66+
setStatus.withLatestFrom(orderPublisher)
67+
.map { newStatus, order in
68+
order.copy(status: newStatus)
69+
}
70+
.assign(to: &$order)
71+
72+
setProduct.withLatestFrom(orderPublisher)
73+
.map { productInput, order in
74+
ProductInputTransformer.update(input: productInput, on: order)
75+
}
76+
.assign(to: &$order)
77+
78+
setAddresses.withLatestFrom(orderPublisher)
79+
.map { addressesInput, order in
80+
order.copy(billingAddress: addressesInput?.billing, shippingAddress: addressesInput?.shipping)
81+
}
82+
.assign(to: &$order)
83+
84+
setShipping.withLatestFrom(orderPublisher)
85+
.map { shippingLineInput, order in
86+
order.copy(shippingLines: shippingLineInput.flatMap { [$0] } ?? [])
87+
}
88+
.assign(to: &$order)
89+
90+
// TODO: Bind fees input
91+
}
92+
}
93+
94+
/// Helper to updates an `order` given an `OrderSyncInput` type.
95+
///
96+
private struct ProductInputTransformer {
97+
/// Type to help bundling order Items parameters.
98+
///
99+
struct OrderItemParameters {
100+
let quantity: Decimal
101+
let price: Decimal
102+
let productID: Int64
103+
let variationID: Int64?
104+
var subtotal: String {
105+
"\(price * quantity)"
106+
}
107+
}
108+
109+
/// Adds, deletes, or updates order items based on the given product input.
110+
///
111+
static func update(input: OrderSyncProductInput, on order: Order) -> Order {
112+
// If the input's quantity is 0 or less, delete the item if possible.
113+
guard input.quantity > 0 else {
114+
return remove(input: input, from: order)
115+
}
116+
117+
// Add or update the order items with the new input.
118+
let newItem = createOrderItem(using: input)
119+
var items = order.items
120+
if let itemIndex = order.items.firstIndex(where: { $0.itemID == newItem.itemID }) {
121+
items[itemIndex] = newItem
122+
} else {
123+
items.append(newItem)
124+
}
125+
126+
return order.copy(items: items)
127+
}
128+
129+
/// Removes an order item from an order when the `item.itemID` matches the `input.id`.
130+
///
131+
private static func remove(input: OrderSyncProductInput, from order: Order) -> Order {
132+
var items = order.items
133+
items.removeAll { $0.itemID == input.id }
134+
return order.copy(items: items)
135+
}
136+
137+
/// Creates and order item by using the `input.id` as the `item.itemID`.
138+
///
139+
private static func createOrderItem(using input: OrderSyncProductInput) -> OrderItem {
140+
let parameters: OrderItemParameters = {
141+
switch input.product {
142+
case .product(let product):
143+
let price = Decimal(string: product.price) ?? .zero
144+
return OrderItemParameters(quantity: input.quantity, price: price, productID: product.productID, variationID: nil)
145+
case .variation(let variation):
146+
let price = Decimal(string: variation.price) ?? .zero
147+
return OrderItemParameters(quantity: input.quantity, price: price, productID: variation.productID, variationID: variation.productVariationID)
148+
}
149+
}()
150+
151+
return OrderItem(itemID: input.id,
152+
name: "",
153+
productID: parameters.productID,
154+
variationID: parameters.variationID ?? 0,
155+
quantity: parameters.quantity,
156+
price: parameters.price as NSDecimalNumber,
157+
sku: nil,
158+
subtotal: parameters.subtotal,
159+
subtotalTax: "",
160+
taxClass: "",
161+
taxes: [],
162+
total: "",
163+
totalTax: "",
164+
attributes: [])
165+
}
166+
}

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ struct OrderSyncProductInput {
1919
case product(Product)
2020
case variation(ProductVariation)
2121
}
22+
var id = Int64(UUID().uuidString.hashValue)
2223
let product: ProductType
23-
let quantity: Int
24+
let quantity: Decimal
2425
}
2526

2627
/// Addresses input for an `OrderSynchronizer` type.
@@ -36,13 +37,21 @@ protocol OrderSynchronizer {
3637

3738
// MARK: Outputs
3839

39-
/// Defines the current sync state of the synchronizer.
40+
/// Latest order sync state.
4041
///
41-
var state: Published<OrderSyncState> { get }
42+
var state: OrderSyncState { get }
4243

43-
/// Defines the latest order to be synced or that is synced.
44+
/// Order Sync State Publisher.
4445
///
45-
var order: Published<Order> { get }
46+
var statePublisher: Published<OrderSyncState>.Publisher { get }
47+
48+
/// Latest order to be synced or that is synced.
49+
///
50+
var order: Order { get }
51+
52+
/// Publisher for the order toe be synced or that is synced.
53+
///
54+
var orderPublisher: Published<Order>.Publisher { get }
4655

4756
// MARK: Inputs
4857

@@ -74,5 +83,5 @@ protocol OrderSynchronizer {
7483

7584
/// Commits all order changes to the remote source. State needs to be in `.synced` to initiate work.
7685
///
77-
func commitAllChanges(onCompletion: (Result<Order, Error>) -> Void)
86+
func commitAllChanges(onCompletion: @escaping (Result<Order, Error>) -> Void)
7887
}

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,7 @@
503503
26B9875D273C6A830090E8CA /* SimplePaymentsNoteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B9875C273C6A830090E8CA /* SimplePaymentsNoteViewModel.swift */; };
504504
26B9875F273CB6AA0090E8CA /* SimplePaymentsNoteViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B9875E273CB6AA0090E8CA /* SimplePaymentsNoteViewModelTests.swift */; };
505505
26C6439327B5DBE900DD00D1 /* OrderSynchronizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26C6439227B5DBE900DD00D1 /* OrderSynchronizer.swift */; };
506+
26C6439527B9A1B300DD00D1 /* LocalOrderSynchronizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26C6439427B9A1B300DD00D1 /* LocalOrderSynchronizer.swift */; };
506507
26C6E8E026E2B7BD00C7BB0F /* CountrySelectorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26C6E8DF26E2B7BD00C7BB0F /* CountrySelectorViewModel.swift */; };
507508
26C6E8E426E2D87C00C7BB0F /* CountrySelectorViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26C6E8E326E2D87C00C7BB0F /* CountrySelectorViewModelTests.swift */; };
508509
26C6E8E626E6B5F500C7BB0F /* StateSelectorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26C6E8E526E6B5F500C7BB0F /* StateSelectorViewModel.swift */; };
@@ -2135,6 +2136,7 @@
21352136
26B9875C273C6A830090E8CA /* SimplePaymentsNoteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplePaymentsNoteViewModel.swift; sourceTree = "<group>"; };
21362137
26B9875E273CB6AA0090E8CA /* SimplePaymentsNoteViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplePaymentsNoteViewModelTests.swift; sourceTree = "<group>"; };
21372138
26C6439227B5DBE900DD00D1 /* OrderSynchronizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderSynchronizer.swift; sourceTree = "<group>"; };
2139+
26C6439427B9A1B300DD00D1 /* LocalOrderSynchronizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalOrderSynchronizer.swift; sourceTree = "<group>"; };
21382140
26C6E8DF26E2B7BD00C7BB0F /* CountrySelectorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountrySelectorViewModel.swift; sourceTree = "<group>"; };
21392141
26C6E8E326E2D87C00C7BB0F /* CountrySelectorViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountrySelectorViewModelTests.swift; sourceTree = "<group>"; };
21402142
26C6E8E526E6B5F500C7BB0F /* StateSelectorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateSelectorViewModel.swift; sourceTree = "<group>"; };
@@ -4520,6 +4522,7 @@
45204522
isa = PBXGroup;
45214523
children = (
45224524
26C6439227B5DBE900DD00D1 /* OrderSynchronizer.swift */,
4525+
26C6439427B9A1B300DD00D1 /* LocalOrderSynchronizer.swift */,
45234526
);
45244527
path = Synchronizer;
45254528
sourceTree = "<group>";
@@ -8857,6 +8860,7 @@
88578860
45C8B2662316AB460002FA77 /* BillingAddressTableViewCell.swift in Sources */,
88588861
E11228BC2707161E004E9F2D /* CardPresentModalUpdateFailed.swift in Sources */,
88598862
454453CC27566FAE00464AC5 /* HubMenuElement.swift in Sources */,
8863+
26C6439527B9A1B300DD00D1 /* LocalOrderSynchronizer.swift in Sources */,
88608864
B541B2152189EEA1008FE7C1 /* Scanner+Helpers.swift in Sources */,
88618865
4512054F2464741B005D68DE /* ProductVisibility.swift in Sources */,
88628866
45A4221A24ACC79C003B1E4C /* SwitchStoreNoticePresenter.swift in Sources */,

0 commit comments

Comments
 (0)