Skip to content

Commit d40c93d

Browse files
authored
Merge pull request #4950 from woocommerce/issue/4745-service-packages-endpoint
Shipping Labels: Create/activate new package on Add New Package screen
2 parents 213da87 + a4687a7 commit d40c93d

File tree

23 files changed

+532
-85
lines changed

23 files changed

+532
-85
lines changed

Fakes/Fakes/Fakes.generated.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,6 +1179,7 @@ extension ShippingLabelPredefinedOption {
11791179
public static func fake() -> ShippingLabelPredefinedOption {
11801180
.init(
11811181
title: .fake(),
1182+
providerID: .fake(),
11821183
predefinedPackages: .fake()
11831184
)
11841185
}

Networking/Networking/Model/ShippingLabel/Packages/Predefined package/ShippingLabelPredefinedOption.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@ public struct ShippingLabelPredefinedOption: Equatable, GeneratedFakeable {
88
/// The title of the predefined option. It works like an ID, and it is unique.
99
public let title: String
1010

11+
/// The ID of the predefined option (shipping provider), e.g. "usps". This is required for activating predefined packages remotely.
12+
public let providerID: String
13+
1114
/// List of predefined packages
1215
public let predefinedPackages: [ShippingLabelPredefinedPackage]
1316

1417

15-
public init(title: String, predefinedPackages: [ShippingLabelPredefinedPackage]) {
18+
public init(title: String, providerID: String, predefinedPackages: [ShippingLabelPredefinedPackage]) {
1619
self.title = title
20+
self.providerID = providerID
1721
self.predefinedPackages = predefinedPackages
1822
}
1923
}
@@ -25,12 +29,14 @@ extension ShippingLabelPredefinedOption: Decodable {
2529

2630
let title = try container.decode(String.self, forKey: .title)
2731
let predefinedPackages = try container.decodeIfPresent([ShippingLabelPredefinedPackage].self, forKey: .predefinedPackages) ?? []
32+
let providerID = try container.decodeIfPresent(String.self, forKey: .providerID) ?? ""
2833

29-
self.init(title: title, predefinedPackages: predefinedPackages)
34+
self.init(title: title, providerID: providerID, predefinedPackages: predefinedPackages)
3035
}
3136

3237
private enum CodingKeys: String, CodingKey {
3338
case title
3439
case predefinedPackages
40+
case providerID
3541
}
3642
}

Networking/Networking/Model/ShippingLabel/Packages/ShippingLabelPackagesResponse.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,15 @@ extension ShippingLabelPackagesResponse: Decodable {
6767

6868
if !activatedPredefinedPackages.isEmpty {
6969
let titleOption: String = providerValueDict?["title"] as? String ?? ""
70-
let option = ShippingLabelPredefinedOption(title: titleOption, predefinedPackages: activatedPredefinedPackages)
70+
let option = ShippingLabelPredefinedOption(title: titleOption, providerID: key, predefinedPackages: activatedPredefinedPackages)
7171
predefinedOptions.append(option)
7272
}
7373

7474
let unactivatedPredefinedPackages = packages.filter({ !activatedPredefinedPackages.contains($0) })
7575

7676
if !unactivatedPredefinedPackages.isEmpty {
7777
let titleOption: String = providerValueDict?["title"] as? String ?? ""
78-
let option = ShippingLabelPredefinedOption(title: titleOption, predefinedPackages: unactivatedPredefinedPackages)
78+
let option = ShippingLabelPredefinedOption(title: titleOption, providerID: key, predefinedPackages: unactivatedPredefinedPackages)
7979
unactivatedPredefinedOptions.append(option)
8080
}
8181
})

Networking/Networking/Remote/ShippingLabelRemote.swift

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ public protocol ShippingLabelRemoteProtocol {
1717
func packagesDetails(siteID: Int64,
1818
completion: @escaping (Result<ShippingLabelPackagesResponse, Error>) -> Void)
1919
func createPackage(siteID: Int64,
20-
customPackage: ShippingLabelCustomPackage,
20+
customPackage: ShippingLabelCustomPackage?,
21+
predefinedOption: ShippingLabelPredefinedOption?,
2122
completion: @escaping (Result<Bool, Error>) -> Void)
2223
func loadCarriersAndRates(siteID: Int64,
2324
orderID: Int64,
@@ -130,18 +131,33 @@ public final class ShippingLabelRemote: Remote, ShippingLabelRemoteProtocol {
130131
enqueue(request, mapper: mapper, completion: completion)
131132
}
132133

133-
/// Creates a new custom package.
134+
/// Creates a new custom package or activates a service package.
134135
/// - Parameters:
135136
/// - siteID: Remote ID of the site that owns the shipping label.
136137
/// - customPackage: The custom package that should be created.
138+
/// - predefinedOption: The predefined option (shipping provider and service packages) to activate.
137139
/// - completion: Closure to be executed upon completion.
138140
public func createPackage(siteID: Int64,
139-
customPackage: ShippingLabelCustomPackage,
141+
customPackage: ShippingLabelCustomPackage?,
142+
predefinedOption: ShippingLabelPredefinedOption?,
140143
completion: @escaping (Result<Bool, Error>) -> Void) {
141144
do {
142-
let customPackageDictionary = try customPackage.toDictionary()
143-
let parameters = [
144-
ParameterKey.custom: [customPackageDictionary]
145+
var customPackageList: [[String: Any]] = []
146+
var predefinedOptionDictionary: [String: [String]] = [:]
147+
148+
if let customPackage = customPackage {
149+
let customPackageDictionary = try customPackage.toDictionary()
150+
customPackageList = [customPackageDictionary]
151+
} else if let predefinedOption = predefinedOption {
152+
let packageIDs = predefinedOption.predefinedPackages.map({ $0.id })
153+
predefinedOptionDictionary = [predefinedOption.providerID: packageIDs]
154+
} else {
155+
throw ShippingLabelError.missingPackage
156+
}
157+
158+
let parameters: [String: Any] = [
159+
ParameterKey.custom: customPackageList,
160+
ParameterKey.predefined: predefinedOptionDictionary
145161
]
146162
let path = Path.packages
147163
let request = JetpackRequest(wooApiVersion: .wcConnectV1, method: .post, siteID: siteID, path: path, parameters: parameters)
@@ -308,6 +324,7 @@ private extension ShippingLabelRemote {
308324
static let captionCSV = "caption_csv"
309325
static let json = "json"
310326
static let custom = "custom"
327+
static let predefined = "predefined"
311328
static let canCreatePaymentMethod = "can_create_payment_method"
312329
static let canCreateCustomsForm = "can_create_customs_form"
313330
static let canCreatePackage = "can_create_package"
@@ -320,3 +337,10 @@ private extension ShippingLabelRemote {
320337
static let async = "async"
321338
}
322339
}
340+
341+
// MARK: Errors {
342+
extension ShippingLabelRemote {
343+
enum ShippingLabelError: Error {
344+
case missingPackage
345+
}
346+
}

Networking/NetworkingTests/Mapper/ShippingLabelPackagesMapperTests.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,15 @@ private extension ShippingLabelPackagesMapperTests {
7272
isLetter: false,
7373
dimensions: "28.57 x 22.22 x 15.24")]
7474
let predefinedOption1 = ShippingLabelPredefinedOption(title: "USPS Priority Mail Flat Rate Boxes",
75+
providerID: "usps",
7576
predefinedPackages: predefinedPackages1)
7677

7778
let predefinedPackages2 = [ShippingLabelPredefinedPackage(id: "LargePaddedPouch",
7879
title: "Large Padded Pouch",
7980
isLetter: true,
8081
dimensions: "30.22 x 35.56 x 2.54")]
8182
let predefinedOption2 = ShippingLabelPredefinedOption(title: "DHL Express",
83+
providerID: "dhlexpress",
8284
predefinedPackages: predefinedPackages2)
8385

8486
return [predefinedOption1, predefinedOption2]
@@ -106,7 +108,8 @@ private extension ShippingLabelPackagesMapperTests {
106108
isLetter: false,
107109
dimensions: "44.45 x 31.75 x 7.62")]
108110
let predefinedOption = ShippingLabelPredefinedOption(title: "DHL Express",
109-
predefinedPackages: predefinedPackages)
111+
providerID: "dhlexpress",
112+
predefinedPackages: predefinedPackages)
110113

111114
return predefinedOption
112115
}

Networking/NetworkingTests/Remote/ShippingLabelRemoteTests.swift

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,47 @@ final class ShippingLabelRemoteTests: XCTestCase {
189189

190190
// When
191191
let result: Result<Bool, Error> = waitFor { promise in
192-
remote.createPackage(siteID: self.sampleSiteID, customPackage: self.sampleShippingLabelCustomPackage()) { result in
192+
remote.createPackage(siteID: self.sampleSiteID,
193+
customPackage: ShippingLabelCustomPackage.fake(),
194+
predefinedOption: ShippingLabelPredefinedOption.fake()) { result in
195+
promise(result)
196+
}
197+
}
198+
199+
// Then
200+
let successResponse = try XCTUnwrap(result.get())
201+
XCTAssertTrue(successResponse)
202+
}
203+
204+
func test_createPackage_returns_success_response_with_only_custom_package() throws {
205+
// Given
206+
let remote = ShippingLabelRemote(network: network)
207+
network.simulateResponse(requestUrlSuffix: "packages", filename: "generic_success_data")
208+
209+
// When
210+
let result: Result<Bool, Error> = waitFor { promise in
211+
remote.createPackage(siteID: self.sampleSiteID,
212+
customPackage: ShippingLabelCustomPackage.fake(),
213+
predefinedOption: nil) { result in
214+
promise(result)
215+
}
216+
}
217+
218+
// Then
219+
let successResponse = try XCTUnwrap(result.get())
220+
XCTAssertTrue(successResponse)
221+
}
222+
223+
func test_createPackage_returns_success_response_with_only_service_package() throws {
224+
// Given
225+
let remote = ShippingLabelRemote(network: network)
226+
network.simulateResponse(requestUrlSuffix: "packages", filename: "generic_success_data")
227+
228+
// When
229+
let result: Result<Bool, Error> = waitFor { promise in
230+
remote.createPackage(siteID: self.sampleSiteID,
231+
customPackage: nil,
232+
predefinedOption: ShippingLabelPredefinedOption.fake()) { result in
193233
promise(result)
194234
}
195235
}
@@ -206,7 +246,9 @@ final class ShippingLabelRemoteTests: XCTestCase {
206246

207247
// When
208248
let result: Result<Bool, Error> = waitFor { promise in
209-
remote.createPackage(siteID: self.sampleSiteID, customPackage: self.sampleShippingLabelCustomPackage()) { result in
249+
remote.createPackage(siteID: self.sampleSiteID,
250+
customPackage: ShippingLabelCustomPackage.fake(),
251+
predefinedOption: ShippingLabelPredefinedOption.fake()) { result in
210252
promise(result)
211253
}
212254
}
@@ -218,6 +260,24 @@ final class ShippingLabelRemoteTests: XCTestCase {
218260
XCTAssertEqual(result.failure as? DotcomError, expectedError)
219261
}
220262

263+
func test_createPackage_returns_missingPackage_error_with_no_packages() throws {
264+
// Given
265+
let remote = ShippingLabelRemote(network: network)
266+
267+
// When
268+
let result: Result<Bool, Error> = waitFor { promise in
269+
remote.createPackage(siteID: self.sampleSiteID,
270+
customPackage: nil,
271+
predefinedOption: nil) { result in
272+
promise(result)
273+
}
274+
}
275+
276+
// Then
277+
let expectedError = ShippingLabelRemote.ShippingLabelError.missingPackage
278+
XCTAssertEqual(result.failure as? ShippingLabelRemote.ShippingLabelError, expectedError)
279+
}
280+
221281
func test_loadCarriersAndRates_parses_success_response() throws {
222282
// Given
223283
let remote = ShippingLabelRemote(network: network)
@@ -471,15 +531,6 @@ private extension ShippingLabelRemoteTests {
471531
postcode: "94110-4929")
472532
}
473533

474-
func sampleShippingLabelCustomPackage() -> ShippingLabelCustomPackage {
475-
return ShippingLabelCustomPackage(isUserDefined: true,
476-
title: "Test Package",
477-
isLetter: false,
478-
dimensions: "10 x 10 x 10",
479-
boxWeight: 5,
480-
maxWeight: 1)
481-
}
482-
483534
func sampleShippingLabelCarrierRate() -> ShippingLabelCarrierRate {
484535
let rate = ShippingLabelCarrierRate(title: "USPS - Parcel Select Mail",
485536
insurance: "0",

WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/Create Shipping Label Form/Package Details/Package Selection/Package Creation/ShippingLabelAddNewPackage.swift

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Yosemite
44
struct ShippingLabelAddNewPackage: View {
55
@ObservedObject var viewModel: ShippingLabelAddNewPackageViewModel
66
@Environment(\.presentationMode) var presentation
7+
@State var isSyncing = false
78

89
var body: some View {
910
GeometryReader { geometry in
@@ -40,19 +41,34 @@ struct ShippingLabelAddNewPackage: View {
4041
}
4142
// Done button
4243
ToolbarItem(placement: .navigationBarTrailing, content: {
43-
Button(Localization.doneButton, action: {
44+
Button(action: {
4445
switch viewModel.selectedView {
4546
case .customPackage:
4647
viewModel.customPackageVM.validatePackage()
47-
if viewModel.customPackageVM.validatedCustomPackage != nil {
48-
// TODO-3909: Save custom package and add it to package list
48+
guard viewModel.customPackageVM.validatedCustomPackage != nil else { return }
49+
isSyncing = true
50+
viewModel.createCustomPackage() { success in
51+
isSyncing = false
52+
guard success else { return }
4953
presentation.wrappedValue.dismiss()
5054
}
5155
case .servicePackage:
52-
// TODO-3909: Add selected service package and go back to package list
53-
presentation.wrappedValue.dismiss()
56+
isSyncing = true
57+
viewModel.activateServicePackage() { success in
58+
isSyncing = false
59+
guard success else { return }
60+
presentation.wrappedValue.dismiss()
61+
}
62+
}
63+
}, label: {
64+
if isSyncing {
65+
ActivityIndicator(isAnimating: .constant(true), style: .medium)
66+
.accentColor(Color(.navigationBarLoadingIndicator))
67+
} else {
68+
Text(Localization.doneButton)
5469
}
5570
})
71+
.disabled(isSyncing)
5672
})
5773
}
5874
}
@@ -70,7 +86,9 @@ private extension ShippingLabelAddNewPackage {
7086

7187
struct ShippingLabelAddNewPackage_Previews: PreviewProvider {
7288
static var previews: some View {
73-
let viewModel = ShippingLabelAddNewPackageViewModel(packagesResponse: ShippingLabelPackageDetailsViewModel.samplePackageDetails())
89+
let viewModel = ShippingLabelAddNewPackageViewModel(siteID: 12345,
90+
packagesResponse: ShippingLabelPackageDetailsViewModel.samplePackageDetails(),
91+
onCompletion: { _, _, _ in })
7492

7593
ShippingLabelAddNewPackage(viewModel: viewModel)
7694
}

0 commit comments

Comments
 (0)