Skip to content

Commit 9ffe90b

Browse files
committed
Merge branch 'trunk' into fix/shipping-lines-crash-async-rows
# Conflicts: # WooCommerce/WooCommerceTests/ViewRelated/Orders/Order Details/OrderDetailsDataSourceTests.swift
2 parents 31ac914 + a124980 commit 9ffe90b

File tree

26 files changed

+681
-329
lines changed

26 files changed

+681
-329
lines changed

Modules/Sources/Yosemite/Actions/WooShippingAction.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,14 @@ public enum WooShippingAction: Action {
9999
orderID: Int64,
100100
completion: (Result<WooShippingConfig, Error>) -> Void)
101101

102+
/// Sync shipping labels for a given order.
103+
/// This uses the same endpoint as `loadConfig` but also stores shipping labels to the storage
104+
/// and returns them in the completion closure.
105+
///
106+
case syncShippingLabels(siteID: Int64,
107+
orderID: Int64,
108+
completion: (Result<[ShippingLabel], Error>) -> Void)
109+
102110
/// Updates shipments for given order
103111
///
104112
case updateShipment(siteID: Int64,

Modules/Sources/Yosemite/Model/Storage/Order+ReadOnlyConvertible.swift

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -123,51 +123,6 @@ extension Storage.Order: ReadOnlyConvertible {
123123

124124
}
125125

126-
/// Returns a lightweight ReadOnly version of the receiver without any relationships populated.
127-
///
128-
public func toLightweightReadOnly() -> Yosemite.Order {
129-
Order(siteID: siteID,
130-
orderID: orderID,
131-
parentID: parentID,
132-
customerID: customerID,
133-
orderKey: orderKey,
134-
isEditable: isEditable,
135-
needsPayment: needsPayment,
136-
needsProcessing: needsPayment,
137-
number: number ?? "",
138-
status: OrderStatusEnum(rawValue: statusKey),
139-
currency: currency ?? "",
140-
currencySymbol: "", // Not stored in the Storage Layer, only used in the notifications extension.
141-
customerNote: customerNote ?? "",
142-
dateCreated: dateCreated ?? Date(),
143-
dateModified: dateModified ?? Date(),
144-
datePaid: datePaid,
145-
discountTotal: discountTotal ?? "",
146-
discountTax: discountTax ?? "",
147-
shippingTotal: shippingTotal ?? "",
148-
shippingTax: shippingTax ?? "",
149-
total: total ?? "",
150-
totalTax: totalTax ?? "",
151-
paymentMethodID: paymentMethodID ?? "",
152-
paymentMethodTitle: paymentMethodTitle ?? "",
153-
paymentURL: paymentURL as URL?,
154-
chargeID: chargeID,
155-
items: [],
156-
billingAddress: createReadOnlyBillingAddress(),
157-
shippingAddress: createReadOnlyShippingAddress(),
158-
shippingLines: [],
159-
coupons: [],
160-
refunds: [],
161-
fees: [],
162-
taxes: [],
163-
customFields: [],
164-
renewalSubscriptionID: renewalSubscriptionID,
165-
appliedGiftCards: [],
166-
attributionInfo: attributionInfo?.toReadOnly(),
167-
shippingLabels: [])
168-
169-
}
170-
171126

172127
// MARK: - Private Helpers
173128

Modules/Sources/Yosemite/Model/Storage/Product+ReadOnlyConvertible.swift

Lines changed: 0 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -200,93 +200,6 @@ extension Storage.Product: ReadOnlyConvertible {
200200
customFields: productCustomFields.sorted { $0.metadataID < $1.metadataID })
201201
}
202202

203-
/// Returns a lightweight ReadOnly version of the receiver.
204-
/// No relationships are populated except for images.
205-
///
206-
public func toLightweightReadOnly() -> Yosemite.Product {
207-
208-
let productImages = imagesArray.map { $0.toReadOnly() }
209-
210-
return Product(siteID: siteID,
211-
productID: productID,
212-
name: name,
213-
slug: slug,
214-
permalink: permalink,
215-
date: date ?? Date(timeIntervalSince1970: 0),
216-
dateCreated: dateCreated ?? Date(timeIntervalSince1970: 0),
217-
dateModified: dateModified,
218-
dateOnSaleStart: dateOnSaleStart,
219-
dateOnSaleEnd: dateOnSaleEnd,
220-
productTypeKey: productTypeKey,
221-
statusKey: statusKey,
222-
featured: featured,
223-
catalogVisibilityKey: catalogVisibilityKey,
224-
fullDescription: fullDescription,
225-
shortDescription: briefDescription,
226-
sku: sku,
227-
globalUniqueID: globalUniqueID,
228-
price: price,
229-
regularPrice: regularPrice,
230-
salePrice: salePrice,
231-
onSale: onSale,
232-
purchasable: purchasable,
233-
totalSales: Int(totalSales),
234-
virtual: virtual,
235-
downloadable: downloadable,
236-
downloads: [],
237-
downloadLimit: downloadLimit,
238-
downloadExpiry: downloadExpiry,
239-
buttonText: buttonText,
240-
externalURL: externalURL,
241-
taxStatusKey: taxStatusKey,
242-
taxClass: taxClass,
243-
manageStock: manageStock,
244-
stockQuantity: nil,
245-
stockStatusKey: stockStatusKey,
246-
backordersKey: backordersKey,
247-
backordersAllowed: backordersAllowed,
248-
backordered: backordered,
249-
soldIndividually: soldIndividually,
250-
weight: weight,
251-
dimensions: createReadOnlyDimensions(),
252-
shippingRequired: shippingRequired,
253-
shippingTaxable: shippingTaxable,
254-
shippingClass: shippingClass,
255-
shippingClassID: shippingClassID,
256-
productShippingClass: nil,
257-
reviewsAllowed: reviewsAllowed,
258-
averageRating: averageRating,
259-
ratingCount: Int(ratingCount),
260-
relatedIDs: convertIDArray(relatedIDs),
261-
upsellIDs: convertIDArray(upsellIDs),
262-
crossSellIDs: convertIDArray(crossSellIDs),
263-
parentID: parentID,
264-
purchaseNote: purchaseNote,
265-
categories: [],
266-
tags: [],
267-
images: productImages,
268-
attributes: [],
269-
defaultAttributes: [],
270-
variations: variations ?? [],
271-
groupedProducts: groupedProducts ?? [],
272-
menuOrder: Int(menuOrder),
273-
addOns: [],
274-
isSampleItem: isSampleItem,
275-
bundleStockStatus: nil,
276-
bundleStockQuantity: bundleStockQuantity as? Int64,
277-
bundleMinSize: bundleMinSize?.decimalValue,
278-
bundleMaxSize: bundleMaxSize?.decimalValue,
279-
bundledItems: [],
280-
password: password,
281-
compositeComponents: [],
282-
subscription: subscription?.toReadOnly(),
283-
minAllowedQuantity: minAllowedQuantity,
284-
maxAllowedQuantity: maxAllowedQuantity,
285-
groupOfQuantity: groupOfQuantity,
286-
combineVariationQuantities: combineVariationQuantities?.boolValue,
287-
customFields: [])
288-
}
289-
290203
// MARK: - Private Helpers
291204

292205
private func createReadOnlyDimensions() -> Yosemite.ProductDimensions {

Modules/Sources/Yosemite/SnapshotsProvider/FetchResultSnapshotsProvider.swift

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -179,16 +179,6 @@ public final class FetchResultSnapshotsProvider<MutableType: FetchResultSnapshot
179179
return nil
180180
}
181181
}
182-
183-
/// The lightweight version of `object(withID:)` method. Returns readonly objects without relationships.
184-
///
185-
public func lightweightObject(withID objectID: FetchResultSnapshotObjectID) -> MutableType.ReadOnlyType? {
186-
if let storageOrder = storage.loadObject(ofType: MutableType.self, with: objectID) {
187-
return storageOrder.toLightweightReadOnly()
188-
} else {
189-
return nil
190-
}
191-
}
192182
}
193183

194184
// MARK: - FetchedResultsController Activation

Modules/Sources/Yosemite/Stores/WooShippingStore.swift

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ public final class WooShippingStore: Store {
8282
updateDestinationAddress(siteID: siteID, orderID: orderID, address: address, completion: completion)
8383
case let .loadConfig(siteID, orderID, completion):
8484
loadConfig(siteID: siteID, orderID: orderID, completion: completion)
85+
case let .syncShippingLabels(siteID, orderID, completion):
86+
syncShippingLabels(siteID: siteID, orderID: orderID, completion: completion)
8587
case let .updateShipment(siteID, orderID, shipmentToUpdate, completion):
8688
updateShipment(siteID: siteID,
8789
orderID: orderID,
@@ -247,6 +249,28 @@ private extension WooShippingStore {
247249
completion: @escaping (Result<Bool, Error>) -> Void) {
248250
remote.acceptUPSTermsOfService(siteID: siteID, originAddress: originAddress, completion: completion)
249251
}
252+
253+
func syncShippingLabels(siteID: Int64,
254+
orderID: Int64,
255+
completion: @escaping (Result<[ShippingLabel], Error>) -> Void) {
256+
remote.loadConfig(siteID: siteID, orderID: orderID, completion: { [weak self] result in
257+
guard let self else { return }
258+
259+
switch result {
260+
case .failure(let error):
261+
completion(.failure(error))
262+
case .success(let config):
263+
guard let labels = config.shippingLabelData?.currentOrderLabels else {
264+
return completion(.success([]))
265+
}
266+
upsertShippingLabelsInBackground(siteID: siteID,
267+
orderID: orderID,
268+
shippingLabels: labels) {
269+
completion(.success(labels))
270+
}
271+
}
272+
})
273+
}
250274
}
251275

252276
// MARK: Helpers
@@ -602,7 +626,7 @@ private extension WooShippingStore {
602626

603627
/// Updates order's `dateModified` locally
604628
/// Used as temp workaround to reflect that the order instance was updated
605-
private func setLastModifiedDateForOrder(siteID: Int64, orderID: Int64) {
629+
func setLastModifiedDateForOrder(siteID: Int64, orderID: Int64) {
606630
storageManager.performAndSave({ derivedStorage in
607631
guard let storedOrder = derivedStorage.loadOrder(
608632
siteID: siteID,
@@ -614,6 +638,70 @@ private extension WooShippingStore {
614638
storedOrder.dateModified = Date()
615639
}, completion: nil, on: .main)
616640
}
641+
642+
/// Updates/inserts the specified readonly shipping label entities *in a background thread*.
643+
/// `onCompletion` will be called on the main thread!
644+
func upsertShippingLabelsInBackground(siteID: Int64,
645+
orderID: Int64,
646+
shippingLabels: [ShippingLabel],
647+
onCompletion: @escaping () -> Void) {
648+
if shippingLabels.isEmpty {
649+
return onCompletion()
650+
}
651+
652+
storageManager.performAndSave ({ [weak self] storage in
653+
guard let self else { return }
654+
guard let order = storage.loadOrder(siteID: siteID, orderID: orderID) else {
655+
return
656+
}
657+
upsertShippingLabels(siteID: siteID, orderID: orderID, shippingLabels: shippingLabels, storageOrder: order, using: storage)
658+
}, completion: onCompletion, on: .main)
659+
}
660+
661+
/// Updates/inserts the specified readonly ShippingLabel entities in the current thread.
662+
func upsertShippingLabels(siteID: Int64,
663+
orderID: Int64,
664+
shippingLabels: [ShippingLabel],
665+
storageOrder: StorageOrder,
666+
using storage: StorageType) {
667+
let storedLabels = storage.loadAllShippingLabels(siteID: siteID, orderID: orderID)
668+
for shippingLabel in shippingLabels {
669+
let storageShippingLabel = storedLabels.first(where: { $0.shippingLabelID == shippingLabel.shippingLabelID }) ??
670+
storage.insertNewObject(ofType: Storage.ShippingLabel.self)
671+
storageShippingLabel.update(with: shippingLabel)
672+
storageShippingLabel.order = storageOrder
673+
674+
update(storageShippingLabel: storageShippingLabel, refund: shippingLabel.refund, using: storage)
675+
676+
let originAddress = storageShippingLabel.originAddress ?? storage.insertNewObject(ofType: Storage.ShippingLabelAddress.self)
677+
originAddress.update(with: shippingLabel.originAddress)
678+
storageShippingLabel.originAddress = originAddress
679+
680+
let destinationAddress = storageShippingLabel.destinationAddress ?? storage.insertNewObject(ofType: Storage.ShippingLabelAddress.self)
681+
destinationAddress.update(with: shippingLabel.destinationAddress)
682+
storageShippingLabel.destinationAddress = destinationAddress
683+
}
684+
685+
// Now, remove any objects that exist in storage but not in shippingLabels
686+
let shippingLabelIDs = shippingLabels.map(\.shippingLabelID)
687+
storedLabels.filter {
688+
!shippingLabelIDs.contains($0.shippingLabelID)
689+
}.forEach {
690+
storage.deleteObject($0)
691+
}
692+
}
693+
694+
func update(storageShippingLabel: StorageShippingLabel,
695+
refund: ShippingLabelRefund?,
696+
using storage: StorageType) {
697+
if let refund {
698+
let storageRefund = storageShippingLabel.refund ?? storage.insertNewObject(ofType: Storage.ShippingLabelRefund.self)
699+
storageRefund.update(with: refund)
700+
storageShippingLabel.refund = storageRefund
701+
} else {
702+
storageShippingLabel.refund = nil
703+
}
704+
}
617705
}
618706

619707
/// Represents errors that can be returned when purchasing a shipping label

Modules/Sources/Yosemite/Tools/ReadOnlyConvertible.swift

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,6 @@ public protocol ReadOnlyConvertible: TypeErasedReadOnlyConvertible {
1717
/// Returns a ReadOnly version of the receiver.
1818
///
1919
func toReadOnly() -> ReadOnlyType
20-
21-
/// Returns a lightweight ReadOnly version of the receiver.
22-
///
23-
func toLightweightReadOnly() -> ReadOnlyType
2420
}
2521

2622

@@ -44,10 +40,4 @@ extension ReadOnlyConvertible {
4440
public func toTypeErasedReadOnly() -> Any {
4541
return toReadOnly()
4642
}
47-
48-
/// Default implementation is the same result with `toReadOnly()`
49-
///
50-
public func toLightweightReadOnly() -> ReadOnlyType {
51-
toReadOnly()
52-
}
5343
}

Modules/Sources/Yosemite/Tools/ResultsController.swift

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,6 @@ public class ResultsController<T: ResultsControllerMutableType> {
143143
return controller.object(at: indexPath).toReadOnly()
144144
}
145145

146-
/// Returns the lightweight fetched object at a given indexPath.
147-
///
148-
public func lightweightObject(at indexPath: IndexPath) -> T.ReadOnlyType {
149-
return controller.object(at: indexPath).toLightweightReadOnly()
150-
}
151-
152146
/// Returns the fetched object at the given `indexPath`. Returns `nil` if the `indexPath`
153147
/// does not exist.
154148
///
@@ -209,17 +203,6 @@ public class ResultsController<T: ResultsControllerMutableType> {
209203
return readOnlyObjects ?? []
210204
}
211205

212-
/// Returns an array of all of the (ReadOnly) Fetched Objects with no relationships populated.
213-
/// Suitable for list views.
214-
///
215-
public var lightweightFetchedObjects: [T.ReadOnlyType] {
216-
let readOnlyObjects = controller.fetchedObjects?.compactMap { mutableObject in
217-
mutableObject.toLightweightReadOnly()
218-
}
219-
220-
return readOnlyObjects ?? []
221-
}
222-
223206
/// Returns an array of SectionInfo Entitites.
224207
///
225208
public var sections: [SectionInfo] {

RELEASE-NOTES.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33

44
22.7
55
-----
6+
- [*] Shipping Labels: Fixed an issue where the purchase button would display a stale price after changing the origin address. [https://github.com/woocommerce/woocommerce-ios/pull/15795]
67
- [*] Order Details: Fix crash when reloading data [https://github.com/woocommerce/woocommerce-ios/pull/15764]
78
- [*] Shipping Labels: Improved shipment management UI by hiding remove/merge options instead of disabling them, hiding merge option for orders with 2 or fewer unfulfilled shipments, and hiding the ellipsis menu when no remove/merge actions are available [https://github.com/woocommerce/woocommerce-ios/pull/15760]
8-
- [*] Improve app performance by fetching lightweight objects when necessary [https://github.com/woocommerce/woocommerce-ios/pull/15774]
99
- [**] POS: a POS tab in the tab bar is now available in the app for stores eligible for Point of Sale, instead of the previous entry point in the Menu tab. [https://github.com/woocommerce/woocommerce-ios/pull/15766]
1010
- [***] POS: Barcode scanning using Bluetooth scanners in Point of Sale (HID-based scanners are supported) [https://github.com/woocommerce/woocommerce-ios/pull/15776]
1111
- [*] POS: Support refreshing product list when it's empty [https://github.com/woocommerce/woocommerce-ios/pull/15782]
1212
- [*] Shipping Labels: Fixed issue displaying fulfilled shipment after purchasing a label and quickly navigating back to the purchase form. [https://github.com/woocommerce/woocommerce-ios/pull/15790]
13+
- [*] Shipping Labels: Fixed mismatched indices of purchased labels on the order details screen. [https://github.com/woocommerce/woocommerce-ios/pull/15800]
1314
- [*] POS: Prevent card reader connection success alert flashing when connecting during a payment [https://github.com/woocommerce/woocommerce-ios/pull/15784]
1415

1516
22.6

WooCommerce/Classes/POS/TabBar/POSTabCoordinator.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ final class POSTabViewController: UIViewController {
1111
override func viewDidLoad() {
1212
super.viewDidLoad()
1313

14-
tabBarItem.title = NSLocalizedString("pos.tab.title", value: "Point of Sale", comment: "Title for the Point of Sale tab.")
15-
tabBarItem.image = .creditCardImage
14+
tabBarItem.title = NSLocalizedString("pos.tab.title", value: "POS", comment: "Title for the Point of Sale tab.")
15+
tabBarItem.image = UIImage(systemName: "cart")
1616
tabBarItem.accessibilityIdentifier = "tab-bar-pos-item"
1717
}
1818
}

WooCommerce/Classes/ViewModels/Order Details/OrderDetailsDataSource.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1331,7 +1331,13 @@ extension OrderDetailsDataSource {
13311331
}
13321332

13331333
let sections = shippingLabels.enumerated().map { index, shippingLabel -> Section in
1334-
let title = String.localizedStringWithFormat(Title.shippingLabelPackageFormat, index + 1)
1334+
let title = {
1335+
guard let shipmentID = shippingLabel.shipmentID,
1336+
let intID = Int(shipmentID) else {
1337+
return String.localizedStringWithFormat(Title.shippingLabelPackageFormat, index + 1)
1338+
}
1339+
return String.localizedStringWithFormat(Title.shippingLabelPackageFormat, intID + 1)
1340+
}()
13351341
let isRefunded = shippingLabel.refund != nil
13361342
let rows: [Row]
13371343
let headerStyle: Section.HeaderStyle

0 commit comments

Comments
 (0)