Skip to content

Commit 21e9663

Browse files
authored
Shipping Labels: Show shipment details on order details screen (#15889)
2 parents 216c380 + 504e7b3 commit 21e9663

File tree

12 files changed

+573
-68
lines changed

12 files changed

+573
-68
lines changed

Modules/Sources/Networking/Model/ShippingLabel/Shipments/WooShippingShipmentItem.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,10 @@ public struct WooShippingShipmentItem: Codable, Equatable, GeneratedFakeable, Ge
4949
}
5050

5151
public typealias WooShippingShipments = [String: [WooShippingShipmentItem]]
52+
53+
public extension WooShippingShipmentItem {
54+
var quantity: Decimal {
55+
guard let subItems else { return 0 }
56+
return subItems.count > 0 ? Decimal(subItems.count) : 1
57+
}
58+
}

Modules/Sources/Yosemite/Stores/WooShippingStore.swift

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,12 @@ private extension WooShippingStore {
326326
// If label has PURCHASED status, stop polling
327327
if labelStatusResponse.status == .purchased,
328328
let label = labelStatusResponse.getPurchasedLabel() {
329-
completion(.success(label))
329+
guard let self else {
330+
return completion(.success(label))
331+
}
332+
insertPurchasedLabelInBackground(siteID: siteID, orderID: orderID, shippingLabel: label) {
333+
completion(.success(label))
334+
}
330335
}
331336

332337
// If label has PURCHASE_ERROR status, return error and stop polling
@@ -410,7 +415,23 @@ private extension WooShippingStore {
410415
orderID: Int64,
411416
shipmentToUpdate: WooShippingUpdateShipment,
412417
completion: @escaping (Result<WooShippingShipments, Error>) -> Void) {
413-
remote.updateShipment(siteID: siteID, orderID: orderID, shipmentToUpdate: shipmentToUpdate, completion: completion)
418+
remote.updateShipment(siteID: siteID, orderID: orderID, shipmentToUpdate: shipmentToUpdate) { [weak self] result in
419+
guard let self, let contents = try? result.get() else {
420+
return completion(result)
421+
}
422+
let shipments = contents.map { (index, items) in
423+
WooShippingShipment(siteID: siteID,
424+
orderID: orderID,
425+
index: index,
426+
items: items,
427+
shippingLabel: nil)
428+
}
429+
upsertShipmentsInBackground(siteID: siteID,
430+
orderID: orderID,
431+
shipments: shipments) {
432+
completion(.success(contents))
433+
}
434+
}
414435
}
415436
}
416437

@@ -644,10 +665,15 @@ private extension WooShippingStore {
644665
DDLogWarn("⚠️ No shipping label found in storage when updating refund")
645666
return shippingLabel.copy(refund: refund)
646667
}
668+
let storageShipment = storageShippingLabel.shipment
647669

648670
let storageRefund = storageShippingLabel.refund ?? storage.insertNewObject(ofType: Storage.ShippingLabelRefund.self)
649671
storageRefund.update(with: refund)
650672
storageShippingLabel.refund = storageRefund
673+
674+
// update stored shipment to trigger onDidChangeContent notification
675+
storageShipment?.shippingLabel = storageShippingLabel
676+
651677
return storageShippingLabel.toReadOnly()
652678

653679
}, completion: { result in
@@ -676,6 +702,28 @@ private extension WooShippingStore {
676702
}, completion: nil, on: .main)
677703
}
678704

705+
/// Inserts the specified readonly shipping label entity *in a background thread*.
706+
/// `onCompletion` will be called on the main thread!
707+
func insertPurchasedLabelInBackground(siteID: Int64,
708+
orderID: Int64,
709+
shippingLabel: ShippingLabel,
710+
onCompletion: @escaping () -> Void) {
711+
storageManager.performAndSave({ [weak self] storage in
712+
guard let self else { return }
713+
714+
let storageOrder = storage.loadOrder(siteID: siteID, orderID: orderID)
715+
let storageShipment = storage.loadAllShipments(siteID: siteID, orderID: orderID)
716+
.first(where: { $0.index == shippingLabel.shipmentID })
717+
718+
guard let storageOrder, let storageShipment else { return }
719+
720+
update(storageShipment: storageShipment,
721+
storageOrder: storageOrder,
722+
shippingLabel: shippingLabel,
723+
using: storage)
724+
}, completion: onCompletion, on: .main)
725+
}
726+
679727
/// Updates/inserts the specified readonly shipments entities *in a background thread*.
680728
/// `onCompletion` will be called on the main thread!
681729
func upsertShipmentsInBackground(siteID: Int64,

Modules/Tests/YosemiteTests/Stores/WooShippingStoreTests.swift

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -531,17 +531,22 @@ final class WooShippingStoreTests: XCTestCase {
531531

532532
// MARK: `purchaseShippingLabel`
533533

534-
func test_purchaseShippingLabel_returns_shipping_label_on_success() throws {
534+
func test_purchaseShippingLabel_returns_shipping_label_on_success_and_persists_label_in_storage() throws {
535535
// Given
536-
let expectedLabel = ShippingLabel.fake().copy(shippingLabelID: 13579)
536+
let expectedLabel = ShippingLabel.fake().copy(siteID: sampleSiteID, orderID: sampleOrderID, shippingLabelID: 13579, shipmentID: "0")
537537
let labelStatusResponse = ShippingLabelStatusPollingResponse.purchased(expectedLabel)
538538
let remote = MockWooShippingRemote()
539539
remote.whenPurchaseShippingLabel(siteID: sampleSiteID, thenReturn: .success([ShippingLabelPurchase.fake().copy(shippingLabelID: 13579)]))
540540
remote.whenCheckLabelStatus(siteID: sampleSiteID, thenReturn: .success(labelStatusResponse))
541+
542+
let order = insertOrder(siteID: sampleSiteID, orderID: sampleOrderID)
543+
let shipment = insertShipment(siteID: sampleSiteID, orderID: sampleOrderID, index: "0")
544+
shipment.order = order
545+
541546
let store = WooShippingStore(dispatcher: dispatcher, storageManager: storageManager, network: network, remote: remote)
542547

543548
// When
544-
let result: Result<ShippingLabel, Error> = waitFor { promise in
549+
let result: Result<ShippingLabel, Error> = waitFor(timeout: 10) { promise in
545550
let action = WooShippingAction.purchaseShippingLabel(siteID: self.sampleSiteID,
546551
orderID: self.sampleOrderID,
547552
originAddress: .fake(),
@@ -558,6 +563,15 @@ final class WooShippingStoreTests: XCTestCase {
558563
XCTAssertTrue(result.isSuccess)
559564
let actualLabel = try XCTUnwrap(result.get())
560565
XCTAssertEqual(actualLabel, expectedLabel)
566+
567+
// label is persisted
568+
let storedLabels = storageManager.viewStorage.loadAllShippingLabels(siteID: sampleSiteID, orderID: sampleOrderID)
569+
XCTAssertEqual(storedLabels.count, 1)
570+
XCTAssertEqual(storedLabels.first?.shippingLabelID, expectedLabel.shippingLabelID)
571+
572+
let storedShipments = storageManager.viewStorage.loadAllShipments(siteID: sampleSiteID, orderID: sampleOrderID)
573+
XCTAssertEqual(storedShipments.count, 1)
574+
XCTAssertEqual(storedShipments.first?.shippingLabel?.shippingLabelID, expectedLabel.shippingLabelID)
561575
}
562576

563577
func test_purchaseShippingLabel_returns_error_on_purchaseShippingLabel_request_failure() throws {
@@ -1037,11 +1051,13 @@ final class WooShippingStoreTests: XCTestCase {
10371051

10381052
// MARK: `updateShipment`
10391053

1040-
func test_updateShipment_returns_success_response() throws {
1054+
func test_updateShipment_returns_success_response_and_persists_shipments() throws {
10411055
// Given
10421056
let remote = MockWooShippingRemote()
10431057
let expected = ["0": [WooShippingShipmentItem.fake()]]
10441058
remote.whenUpdatingShipment(siteID: sampleSiteID, thenReturn: .success(expected))
1059+
1060+
insertOrder(siteID: sampleSiteID, orderID: sampleOrderID)
10451061
let store = WooShippingStore(dispatcher: dispatcher, storageManager: storageManager, network: network, remote: remote)
10461062

10471063
// When
@@ -1057,6 +1073,10 @@ final class WooShippingStoreTests: XCTestCase {
10571073
// Then
10581074
let actual = try XCTUnwrap(result.get())
10591075
XCTAssertEqual(actual, expected)
1076+
1077+
let storedShipments = storageManager.viewStorage.loadAllShipments(siteID: sampleSiteID, orderID: sampleOrderID)
1078+
XCTAssertEqual(storedShipments.count, 1)
1079+
XCTAssertEqual(storedShipments.first?.index, "0")
10601080
}
10611081

10621082
func test_updateShipment_returns_error_on_failure() throws {
@@ -1088,16 +1108,18 @@ final class WooShippingStoreTests: XCTestCase {
10881108
let sampleOrderID: Int64 = 134
10891109
let remote = MockWooShippingRemote()
10901110
let expectedRefund = Yosemite.ShippingLabelRefund(dateRequested: Date(), status: .pending)
1091-
let shippingLabel = MockShippingLabel.emptyLabel().copy(siteID: sampleSiteID, orderID: sampleOrderID, shippingLabelID: 123)
1111+
let shippingLabel = MockShippingLabel.emptyLabel().copy(siteID: sampleSiteID, orderID: sampleOrderID, shippingLabelID: 123, shipmentID: "0")
10921112

10931113
remote.whenRefundingShippingLabel(siteID: shippingLabel.siteID,
10941114
orderID: shippingLabel.orderID,
10951115
shippingLabelID: shippingLabel.shippingLabelID,
10961116
thenReturn: .success(expectedRefund))
10971117
let store = WooShippingStore(dispatcher: dispatcher, storageManager: storageManager, network: network, remote: remote)
10981118

1119+
let shipment = insertShipment(siteID: sampleSiteID, orderID: sampleOrderID, index: "0")
10991120
// Inserts a shipping label without a refund.
1100-
insertShippingLabel(shippingLabel)
1121+
let storedLabel = insertShippingLabel(shippingLabel)
1122+
shipment.shippingLabel = storedLabel
11011123

11021124
XCTAssertEqual(viewStorage.countObjects(ofType: StorageShippingLabel.self), 1)
11031125
XCTAssertEqual(viewStorage.countObjects(ofType: StorageShippingLabelRefund.self), 0)
@@ -1121,6 +1143,10 @@ final class WooShippingStoreTests: XCTestCase {
11211143

11221144
XCTAssertEqual(viewStorage.countObjects(ofType: StorageShippingLabel.self), 1)
11231145
XCTAssertEqual(viewStorage.countObjects(ofType: StorageShippingLabelRefund.self), 1)
1146+
1147+
let storedShipments = viewStorage.loadAllShipments(siteID: sampleSiteID, orderID: sampleOrderID)
1148+
XCTAssertEqual(storedShipments.first?.shippingLabel?.shippingLabelID, shippingLabel.shippingLabelID)
1149+
XCTAssertNotNil(storedShipments.first?.shippingLabel?.refund)
11241150
}
11251151

11261152
func test_refundShippingLabel_returns_error_on_failure() throws {
@@ -1390,15 +1416,28 @@ private extension WooShippingStoreTests {
13901416
groupId: "")])])
13911417
}
13921418

1393-
func insertShippingLabel(_ readOnlyShippingLabel: Yosemite.ShippingLabel) {
1419+
@discardableResult
1420+
func insertShippingLabel(_ readOnlyShippingLabel: Yosemite.ShippingLabel) -> StorageShippingLabel {
13941421
let shippingLabel = viewStorage.insertNewObject(ofType: StorageShippingLabel.self)
13951422
shippingLabel.update(with: readOnlyShippingLabel)
1423+
return shippingLabel
13961424
}
13971425

1398-
func insertOrder(siteID: Int64, orderID: Int64) {
1426+
@discardableResult
1427+
func insertOrder(siteID: Int64, orderID: Int64) -> StorageOrder {
13991428
let order = viewStorage.insertNewObject(ofType: StorageOrder.self)
14001429
order.siteID = siteID
14011430
order.orderID = orderID
14021431
order.statusKey = ""
1432+
return order
1433+
}
1434+
1435+
@discardableResult
1436+
func insertShipment(siteID: Int64, orderID: Int64, index: String) -> StorageWooShippingShipment {
1437+
let shipment = viewStorage.insertNewObject(ofType: StorageWooShippingShipment.self)
1438+
shipment.siteID = siteID
1439+
shipment.orderID = orderID
1440+
shipment.index = index
1441+
return shipment
14031442
}
14041443
}

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
22.9
55
-----
6+
- [**] Order Details: Update Shipping Labels section for stores with Woo Shipping extension [https://github.com/woocommerce/woocommerce-ios/pull/15889]
67
- [*] Order List: New orders made through Point of Sale are now filterable via the Order List filters menu [https://github.com/woocommerce/woocommerce-ios/pull/15910]
78

89
22.8

0 commit comments

Comments
 (0)