Skip to content

Commit f26de3a

Browse files
authored
Shipping Labels: Sync shipments (#15899)
2 parents 0525eec + 62a87ec commit f26de3a

File tree

7 files changed

+289
-178
lines changed

7 files changed

+289
-178
lines changed

Modules/Sources/Storage/Tools/StorageType+Extensions.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,13 @@ public extension StorageType {
600600
return allObjects(ofType: WooShippingCustomPackage.self, matching: predicate, sortedBy: [descriptor])
601601
}
602602

603+
/// Returns all stored shipments for a site and order.
604+
///
605+
func loadAllShipments(siteID: Int64, orderID: Int64) -> [WooShippingShipment] {
606+
let predicate = \WooShippingShipment.siteID == siteID && \WooShippingShipment.orderID == orderID
607+
return allObjects(ofType: WooShippingShipment.self, matching: predicate, sortedBy: nil)
608+
}
609+
603610
// MARK: - BlazeCampaignListItem
604611

605612
/// Returns a single BlazeCampaignListItem given a `siteID` and `campaignID`

Modules/Sources/Yosemite/Actions/WooShippingAction.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,13 @@ public enum WooShippingAction: Action {
105105
orderID: Int64,
106106
completion: (Result<WooShippingConfig, Error>) -> Void)
107107

108-
/// Sync shipping labels for a given order.
109-
/// This uses the same endpoint as `loadConfig` but also stores shipping labels to the storage
108+
/// Sync shipments for a given order.
109+
/// This uses the same endpoint as `loadConfig` but also stores shipments and shipping labels to the storage
110110
/// and returns them in the completion closure.
111111
///
112-
case syncShippingLabels(siteID: Int64,
113-
orderID: Int64,
114-
completion: (Result<[ShippingLabel], Error>) -> Void)
112+
case syncShipments(siteID: Int64,
113+
orderID: Int64,
114+
completion: (Result<[WooShippingShipment], Error>) -> Void)
115115

116116
/// Updates shipments for given order
117117
///

Modules/Sources/Yosemite/Stores/WooShippingStore.swift

Lines changed: 88 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ public final class WooShippingStore: Store {
8484
updateDestinationAddress(siteID: siteID, orderID: orderID, address: address, completion: completion)
8585
case let .loadConfig(siteID, orderID, completion):
8686
loadConfig(siteID: siteID, orderID: orderID, completion: completion)
87-
case let .syncShippingLabels(siteID, orderID, completion):
88-
syncShippingLabels(siteID: siteID, orderID: orderID, completion: completion)
87+
case let .syncShipments(siteID, orderID, completion):
88+
syncShipments(siteID: siteID, orderID: orderID, completion: completion)
8989
case let .updateShipment(siteID, orderID, shipmentToUpdate, completion):
9090
updateShipment(siteID: siteID,
9191
orderID: orderID,
@@ -290,26 +290,23 @@ private extension WooShippingStore {
290290
remote.acceptUPSTermsOfService(siteID: siteID, originAddress: originAddress, completion: completion)
291291
}
292292

293-
func syncShippingLabels(siteID: Int64,
294-
orderID: Int64,
295-
completion: @escaping (Result<[ShippingLabel], Error>) -> Void) {
296-
remote.loadConfig(siteID: siteID, orderID: orderID, completion: { [weak self] result in
293+
func syncShipments(siteID: Int64,
294+
orderID: Int64,
295+
completion: @escaping (Result<[WooShippingShipment], Error>) -> Void) {
296+
remote.loadConfig(siteID: siteID, orderID: orderID) { [weak self] result in
297297
guard let self else { return }
298-
299298
switch result {
300299
case .failure(let error):
301300
completion(.failure(error))
302301
case .success(let config):
303-
guard let labels = config.shippingLabelData?.currentOrderLabels else {
304-
return completion(.success([]))
305-
}
306-
upsertShippingLabelsInBackground(siteID: siteID,
307-
orderID: orderID,
308-
shippingLabels: labels) {
309-
completion(.success(labels))
302+
let shipments = config.shipments
303+
upsertShipmentsInBackground(siteID: siteID,
304+
orderID: orderID,
305+
shipments: shipments) {
306+
completion(.success(shipments))
310307
}
311308
}
312-
})
309+
}
313310
}
314311
}
315312

@@ -679,35 +676,60 @@ private extension WooShippingStore {
679676
}, completion: nil, on: .main)
680677
}
681678

682-
/// Updates/inserts the specified readonly shipping label entities *in a background thread*.
679+
/// Updates/inserts the specified readonly shipments entities *in a background thread*.
683680
/// `onCompletion` will be called on the main thread!
684-
func upsertShippingLabelsInBackground(siteID: Int64,
685-
orderID: Int64,
686-
shippingLabels: [ShippingLabel],
687-
onCompletion: @escaping () -> Void) {
688-
if shippingLabels.isEmpty {
689-
return onCompletion()
690-
}
691-
681+
func upsertShipmentsInBackground(siteID: Int64,
682+
orderID: Int64,
683+
shipments: [WooShippingShipment],
684+
onCompletion: @escaping () -> Void) {
692685
storageManager.performAndSave ({ [weak self] storage in
693686
guard let self else { return }
694687
guard let order = storage.loadOrder(siteID: siteID, orderID: orderID) else {
695688
return
696689
}
697-
upsertShippingLabels(siteID: siteID, orderID: orderID, shippingLabels: shippingLabels, storageOrder: order, using: storage)
690+
upsertShipments(siteID: siteID,
691+
orderID: orderID,
692+
shipments: shipments,
693+
storageOrder: order,
694+
using: storage)
698695
}, completion: onCompletion, on: .main)
699696
}
700697

701-
/// Updates/inserts the specified readonly ShippingLabel entities in the current thread.
702-
func upsertShippingLabels(siteID: Int64,
703-
orderID: Int64,
704-
shippingLabels: [ShippingLabel],
705-
storageOrder: StorageOrder,
706-
using storage: StorageType) {
707-
let storedLabels = storage.loadAllShippingLabels(siteID: siteID, orderID: orderID)
708-
for shippingLabel in shippingLabels {
709-
let storageShippingLabel = storedLabels.first(where: { $0.shippingLabelID == shippingLabel.shippingLabelID }) ??
710-
storage.insertNewObject(ofType: Storage.ShippingLabel.self)
698+
/// Updates/inserts the specified readonly WooShippingShipments entities in the current thread.
699+
func upsertShipments(siteID: Int64,
700+
orderID: Int64,
701+
shipments: [WooShippingShipment],
702+
storageOrder: StorageOrder,
703+
using storage: StorageType) {
704+
let storedShipments = storage.loadAllShipments(siteID: siteID, orderID: orderID)
705+
for shipment in shipments {
706+
let storageShipment = storedShipments.first(where: { $0.index == shipment.index }) ??
707+
storage.insertNewObject(ofType: Storage.WooShippingShipment.self)
708+
storageShipment.update(with: shipment)
709+
storageShipment.order = storageOrder
710+
711+
handleShipmentItems(shipment, storageShipment, storage)
712+
update(storageShipment: storageShipment,
713+
storageOrder: storageOrder,
714+
shippingLabel: shipment.shippingLabel,
715+
using: storage)
716+
}
717+
718+
// Now, remove any objects that exist in storage but not in shipments
719+
let shipmentIndices = shipments.map(\.index)
720+
storedShipments.filter {
721+
!shipmentIndices.contains($0.index)
722+
}.forEach {
723+
storage.deleteObject($0)
724+
}
725+
}
726+
727+
func update(storageShipment: StorageWooShippingShipment,
728+
storageOrder: StorageOrder,
729+
shippingLabel: ShippingLabel?,
730+
using storage: StorageType) {
731+
if let shippingLabel {
732+
let storageShippingLabel = storageShipment.shippingLabel ?? storage.insertNewObject(ofType: Storage.ShippingLabel.self)
711733
storageShippingLabel.update(with: shippingLabel)
712734
storageShippingLabel.order = storageOrder
713735

@@ -720,14 +742,39 @@ private extension WooShippingStore {
720742
let destinationAddress = storageShippingLabel.destinationAddress ?? storage.insertNewObject(ofType: Storage.ShippingLabelAddress.self)
721743
destinationAddress.update(with: shippingLabel.destinationAddress)
722744
storageShippingLabel.destinationAddress = destinationAddress
745+
746+
/// Set the shipping label to the shipment's relationship
747+
storageShipment.shippingLabel = storageShippingLabel
748+
} else {
749+
storageShipment.shippingLabel = nil
723750
}
751+
}
724752

725-
// Now, remove any objects that exist in storage but not in shippingLabels
726-
let shippingLabelIDs = shippingLabels.map(\.shippingLabelID)
727-
storedLabels.filter {
728-
!shippingLabelIDs.contains($0.shippingLabelID)
729-
}.forEach {
730-
storage.deleteObject($0)
753+
/// Updates, inserts, or prunes the provided StorageWooShippingShipment's items using the provided read-only WooShippingShipment's items
754+
///
755+
private func handleShipmentItems(_ readOnlyShipment: Networking.WooShippingShipment,
756+
_ storageShipment: Storage.WooShippingShipment,
757+
_ storage: StorageType) {
758+
759+
let storageItemsArray = Array(storageShipment.items ?? [])
760+
761+
// Upsert the items from the read-only shipment
762+
for readOnlyItem in readOnlyShipment.items {
763+
if let existingStorageItem = storageItemsArray.first(where: { $0.id == readOnlyItem.id }) {
764+
existingStorageItem.update(with: readOnlyItem)
765+
} else {
766+
let newStorageItem = storage.insertNewObject(ofType: Storage.WooShippingShipmentItem.self)
767+
newStorageItem.update(with: readOnlyItem)
768+
storageShipment.addToItems(newStorageItem)
769+
}
770+
}
771+
772+
// Now, remove any objects that exist in storageShipment.items but not in readOnlyShipment.items
773+
storageItemsArray.forEach { storageItem in
774+
if readOnlyShipment.items.first(where: { $0.id == storageItem.id } ) == nil {
775+
storageShipment.removeFromItems(storageItem)
776+
storage.deleteObject(storageItem)
777+
}
731778
}
732779
}
733780

Modules/Tests/YosemiteTests/Stores/WooShippingStoreTests.swift

Lines changed: 90 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,80 +1035,6 @@ final class WooShippingStoreTests: XCTestCase {
10351035
XCTAssertEqual(error as? NetworkError, expectedError)
10361036
}
10371037

1038-
// MARK: `syncShippingLabels`
1039-
1040-
func test_syncShippingLabels_persists_shipping_labels_on_success() throws {
1041-
// Given
1042-
let remote = MockWooShippingRemote()
1043-
let orderID: Int64 = 22
1044-
let expectedShippingLabel: Yosemite.ShippingLabel = {
1045-
let origin = ShippingLabelAddress(company: "fun testing",
1046-
name: "Woo seller",
1047-
phone: "6501234567",
1048-
country: "US",
1049-
state: "CA",
1050-
address1: "9999 19TH AVE",
1051-
address2: "",
1052-
city: "SAN FRANCISCO",
1053-
postcode: "94121-2303")
1054-
let destination = ShippingLabelAddress(company: "",
1055-
name: "Woo buyer",
1056-
phone: "1650345689",
1057-
country: "TW",
1058-
state: "Taiwan",
1059-
address1: "No 70 RA St",
1060-
address2: "",
1061-
city: "Taipei",
1062-
postcode: "100")
1063-
let refund = ShippingLabelRefund(dateRequested: Date(timeIntervalSince1970: 1603716266.809), status: .pending)
1064-
return ShippingLabel(siteID: sampleSiteID,
1065-
orderID: orderID,
1066-
shippingLabelID: 1149,
1067-
carrierID: "usps",
1068-
shipmentID: "0",
1069-
dateCreated: Date(timeIntervalSince1970: 1603716274.809),
1070-
packageName: "box",
1071-
rate: 58.81,
1072-
currency: "USD",
1073-
trackingNumber: "CM199912222US",
1074-
serviceName: "USPS - Priority Mail International",
1075-
refundableAmount: 58.81,
1076-
status: .purchased,
1077-
refund: refund,
1078-
originAddress: origin,
1079-
destinationAddress: destination,
1080-
productIDs: [3013],
1081-
productNames: ["Password protected!"],
1082-
commercialInvoiceURL: nil,
1083-
usedDate: nil,
1084-
expiryDate: nil)
1085-
}()
1086-
let expectedResponse = WooShippingConfig.fake().copy(
1087-
shipments: [WooShippingShipment.fake()],
1088-
shippingLabelData: WooShippingLabelData(currentOrderLabels: [expectedShippingLabel])
1089-
)
1090-
remote.whenLoadingConfig(siteID: sampleSiteID, thenReturn: .success(expectedResponse))
1091-
1092-
let store = WooShippingStore(dispatcher: dispatcher, storageManager: storageManager, network: network, remote: remote)
1093-
insertOrder(siteID: sampleSiteID, orderID: orderID)
1094-
1095-
// When
1096-
let result: Result<[Yosemite.ShippingLabel], Error> = waitFor { promise in
1097-
let action = WooShippingAction.syncShippingLabels(siteID: self.sampleSiteID, orderID: orderID) { result in
1098-
promise(result)
1099-
}
1100-
store.onAction(action)
1101-
}
1102-
1103-
// Then
1104-
XCTAssertTrue(result.isSuccess)
1105-
1106-
let persistedOrder = try XCTUnwrap(viewStorage.loadOrder(siteID: sampleSiteID, orderID: orderID))
1107-
let persistedShippingLabels = try XCTUnwrap(viewStorage.loadAllShippingLabels(siteID: sampleSiteID, orderID: orderID))
1108-
XCTAssertEqual(persistedOrder.shippingLabels, Set(persistedShippingLabels))
1109-
XCTAssertEqual(persistedShippingLabels.map { $0.toReadOnly() }, [expectedShippingLabel])
1110-
}
1111-
11121038
// MARK: `updateShipment`
11131039

11141040
func test_updateShipment_returns_success_response() throws {
@@ -1309,6 +1235,96 @@ final class WooShippingStoreTests: XCTestCase {
13091235
let error = try XCTUnwrap(result.failure)
13101236
XCTAssertEqual(error as? NetworkError, expectedError)
13111237
}
1238+
1239+
// MARK: `syncShipments`
1240+
1241+
func test_syncShipments_returns_shipments_on_success() throws {
1242+
// Given
1243+
let remote = MockWooShippingRemote()
1244+
let expectedShipments = [WooShippingShipment.fake(), WooShippingShipment.fake()]
1245+
let config = WooShippingConfig.fake().copy(shipments: expectedShipments)
1246+
remote.whenLoadingConfig(siteID: sampleSiteID, thenReturn: .success(config))
1247+
let store = WooShippingStore(dispatcher: dispatcher, storageManager: storageManager, network: network, remote: remote)
1248+
1249+
// When
1250+
let result: Result<[WooShippingShipment], Error> = waitFor { promise in
1251+
let action = WooShippingAction.syncShipments(siteID: self.sampleSiteID,
1252+
orderID: self.sampleOrderID) { result in
1253+
promise(result)
1254+
}
1255+
store.onAction(action)
1256+
}
1257+
1258+
// Then
1259+
let actualShipments = try XCTUnwrap(result.get())
1260+
XCTAssertEqual(actualShipments, expectedShipments)
1261+
}
1262+
1263+
func test_syncShipments_returns_error_on_failure() throws {
1264+
// Given
1265+
let remote = MockWooShippingRemote()
1266+
let expectedError = NetworkError.timeout()
1267+
remote.whenLoadingConfig(siteID: sampleSiteID, thenReturn: .failure(expectedError))
1268+
let store = WooShippingStore(dispatcher: dispatcher, storageManager: storageManager, network: network, remote: remote)
1269+
1270+
// When
1271+
let result: Result<[WooShippingShipment], Error> = waitFor { promise in
1272+
let action = WooShippingAction.syncShipments(siteID: self.sampleSiteID,
1273+
orderID: self.sampleOrderID) { result in
1274+
promise(result)
1275+
}
1276+
store.onAction(action)
1277+
}
1278+
1279+
// Then
1280+
let error = try XCTUnwrap(result.failure)
1281+
XCTAssertEqual(error as? NetworkError, expectedError)
1282+
}
1283+
1284+
func test_syncShipments_persists_shipments_to_storage_on_success() throws {
1285+
// Given
1286+
let remote = MockWooShippingRemote()
1287+
insertOrder(siteID: sampleSiteID, orderID: sampleOrderID)
1288+
1289+
let shippingLabel = ShippingLabel.fake().copy(
1290+
siteID: sampleSiteID,
1291+
orderID: sampleOrderID,
1292+
shippingLabelID: 123
1293+
)
1294+
let item = WooShippingShipmentItem(id: 11, subItems: [])
1295+
let expectedShipments = [WooShippingShipment.fake().copy(
1296+
siteID: sampleSiteID,
1297+
orderID: sampleOrderID,
1298+
index: "0",
1299+
items: [item],
1300+
shippingLabel: shippingLabel
1301+
)]
1302+
let config = WooShippingConfig.fake().copy(shipments: expectedShipments)
1303+
remote.whenLoadingConfig(siteID: sampleSiteID, thenReturn: .success(config))
1304+
1305+
let store = WooShippingStore(dispatcher: dispatcher,
1306+
storageManager: storageManager,
1307+
network: network,
1308+
remote: remote)
1309+
1310+
// When
1311+
let result: Result<[WooShippingShipment], Error> = waitFor { promise in
1312+
let action = WooShippingAction.syncShipments(siteID: self.sampleSiteID,
1313+
orderID: self.sampleOrderID) { result in
1314+
promise(result)
1315+
}
1316+
store.onAction(action)
1317+
}
1318+
1319+
// Then
1320+
XCTAssertTrue(result.isSuccess)
1321+
XCTAssertEqual(viewStorage.countObjects(ofType: StorageWooShippingShipment.self), expectedShipments.count)
1322+
let object = viewStorage.loadAllShipments(siteID: sampleSiteID, orderID: sampleOrderID).first
1323+
XCTAssertEqual(object?.order?.orderID, sampleOrderID)
1324+
XCTAssertEqual(object?.shippingLabel?.shippingLabelID, shippingLabel.shippingLabelID)
1325+
XCTAssertEqual(object?.items?.count, 1)
1326+
XCTAssertEqual(object?.items?.first?.id, item.id)
1327+
}
13121328
}
13131329

13141330
private extension WooShippingStoreTests {

0 commit comments

Comments
 (0)