Skip to content

Commit 895f41e

Browse files
authored
Shipping Labels: Migrate to the config endpoint for syncing shipping labels in Woo Shipping plugin (#15800)
2 parents 09b4002 + 5301b3f commit 895f41e

File tree

9 files changed

+430
-37
lines changed

9 files changed

+430
-37
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/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

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- [***] POS: Barcode scanning using Bluetooth scanners in Point of Sale (HID-based scanners are supported) [https://github.com/woocommerce/woocommerce-ios/pull/15776]
1010
- [*] POS: Support refreshing product list when it's empty [https://github.com/woocommerce/woocommerce-ios/pull/15782]
1111
- [*] 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]
12+
- [*] Shipping Labels: Fixed mismatched indices of purchased labels on the order details screen. [https://github.com/woocommerce/woocommerce-ios/pull/15800]
1213
- [*] POS: Prevent card reader connection success alert flashing when connecting during a payment [https://github.com/woocommerce/woocommerce-ios/pull/15784]
1314

1415
22.6

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1307,7 +1307,13 @@ extension OrderDetailsDataSource {
13071307
}
13081308

13091309
let sections = shippingLabels.enumerated().map { index, shippingLabel -> Section in
1310-
let title = String.localizedStringWithFormat(Title.shippingLabelPackageFormat, index + 1)
1310+
let title = {
1311+
guard let shipmentID = shippingLabel.shipmentID,
1312+
let intID = Int(shipmentID) else {
1313+
return String.localizedStringWithFormat(Title.shippingLabelPackageFormat, index + 1)
1314+
}
1315+
return String.localizedStringWithFormat(Title.shippingLabelPackageFormat, intID + 1)
1316+
}()
13111317
let isRefunded = shippingLabel.refund != nil
13121318
let rows: [Row]
13131319
let headerStyle: Section.HeaderStyle

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,13 @@ final class OrderDetailsResultsControllers {
108108
/// Shipping labels for an Order
109109
///
110110
var shippingLabels: [ShippingLabel] {
111-
return order.shippingLabels
111+
order.shippingLabels.sorted(by: { label1, label2 in
112+
if let shipmentID1 = label1.shipmentID,
113+
let shipmentID2 = label2.shipmentID {
114+
return shipmentID1.localizedStandardCompare(shipmentID2) == .orderedAscending
115+
}
116+
return label1.dateCreated < label2.dateCreated
117+
})
112118
}
113119

114120
/// Site's add-on groups.

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

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -696,32 +696,25 @@ extension OrderDetailsViewModel {
696696

697697
@discardableResult
698698
@MainActor func syncShippingLabels() async -> [ShippingLabel] {
699-
guard await localRequirementsForShippingLabelsAreFulfilled() else {
699+
let isRevampedFlow = featureFlagService.isFeatureFlagEnabled(.revampedShippingLabelCreation)
700+
guard isRevampedFlow else {
701+
/// old logic for syncing labels
702+
if await localRequirementsForShippingLabelsAreFulfilled() {
703+
return await syncShippingLabelsForLegacyPlugin(isRevampedFlow: isRevampedFlow)
704+
}
700705
return []
701706
}
702-
let isRevampedFlow = featureFlagService.isFeatureFlagEnabled(.revampedShippingLabelCreation)
703-
return await withCheckedContinuation { continuation in
704-
stores.dispatch(ShippingLabelAction.synchronizeShippingLabels(siteID: order.siteID, orderID: order.orderID) { result in
705-
switch result {
706-
case .success(let shippingLabels):
707-
ServiceLocator.analytics.track(event: .shippingLabelsAPIRequest(
708-
result: .success,
709-
isRevampedFlow: isRevampedFlow
710-
))
711-
continuation.resume(returning: shippingLabels)
712-
case .failure(let error):
713-
ServiceLocator.analytics.track(event: .shippingLabelsAPIRequest(
714-
result: .failed(error: error),
715-
isRevampedFlow: isRevampedFlow
716-
))
717-
if error as? DotcomError == .noRestRoute {
718-
DDLogError("⚠️ Endpoint for synchronizing shipping labels is unreachable. WC Shipping plugin may be missing.")
719-
} else {
720-
DDLogError("⛔️ Error synchronizing shipping labels: \(error)")
721-
}
722-
continuation.resume(returning: [])
723-
}
724-
})
707+
708+
guard !orderContainsOnlyVirtualProducts else {
709+
return []
710+
}
711+
712+
if await isPluginActive(pluginPath: SitePlugin.SupportedPluginPath.WooShipping) {
713+
return await syncShippingLabelsForWooShipping()
714+
} else if await isPluginActive(pluginPath: SitePlugin.SupportedPluginPath.LegacyWCShip) {
715+
return await syncShippingLabelsForLegacyPlugin(isRevampedFlow: isRevampedFlow)
716+
} else {
717+
return []
725718
}
726719
}
727720

@@ -956,6 +949,47 @@ extension OrderDetailsViewModel {
956949
}
957950

958951
private extension OrderDetailsViewModel {
952+
953+
@MainActor func syncShippingLabelsForWooShipping() async -> [ShippingLabel] {
954+
await withCheckedContinuation { continuation in
955+
stores.dispatch(WooShippingAction.syncShippingLabels(siteID: order.siteID, orderID: order.orderID) { [weak self] result in
956+
let labels = self?.handleShippingLabelSyncingResult(result: result, isRevampedFlow: true) ?? []
957+
continuation.resume(returning: labels)
958+
})
959+
}
960+
}
961+
962+
@MainActor func syncShippingLabelsForLegacyPlugin(isRevampedFlow: Bool) async -> [ShippingLabel] {
963+
await withCheckedContinuation { continuation in
964+
stores.dispatch(ShippingLabelAction.synchronizeShippingLabels(siteID: order.siteID, orderID: order.orderID) { [weak self] result in
965+
let labels = self?.handleShippingLabelSyncingResult(result: result, isRevampedFlow: isRevampedFlow) ?? []
966+
continuation.resume(returning: labels)
967+
})
968+
}
969+
}
970+
971+
func handleShippingLabelSyncingResult(result: Result<[ShippingLabel], Error>, isRevampedFlow: Bool) -> [ShippingLabel] {
972+
switch result {
973+
case .success(let shippingLabels):
974+
ServiceLocator.analytics.track(event: .shippingLabelsAPIRequest(
975+
result: .success,
976+
isRevampedFlow: isRevampedFlow
977+
))
978+
return shippingLabels
979+
case .failure(let error):
980+
ServiceLocator.analytics.track(event: .shippingLabelsAPIRequest(
981+
result: .failed(error: error),
982+
isRevampedFlow: isRevampedFlow
983+
))
984+
if error as? DotcomError == .noRestRoute {
985+
DDLogError("⚠️ Endpoint for synchronizing shipping labels is unreachable. WC Shipping plugin may be missing.")
986+
} else {
987+
DDLogError("⛔️ Error synchronizing shipping labels: \(error)")
988+
}
989+
return []
990+
}
991+
}
992+
959993
@MainActor
960994
func isPluginActive(_ plugin: String) async -> Bool {
961995
return await isPluginActive([plugin])

0 commit comments

Comments
 (0)