Skip to content

Commit 8ae9c75

Browse files
committed
Merge branch 'trunk' into feat/WOOMOB-858-pos-tab-i2-analytics
2 parents 9f2295b + af9b54d commit 8ae9c75

File tree

79 files changed

+1902
-931
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+1902
-931
lines changed

Modules/Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ enum XcodeSupport {
342342
.xcodeTarget(
343343
XcodeTargetNames.notificationExtension,
344344
dependencies: [
345-
"Networking",
345+
"NetworkingCore",
346346
"WooFoundation",
347347
.product(name: "KeychainAccess", package: "KeychainAccess"),
348348
]

Modules/Sources/Fakes/Networking.generated.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1451,6 +1451,7 @@ extension Networking.ShippingLabelAccountSettings {
14511451
isEmailReceiptsEnabled: .fake(),
14521452
paperSize: .fake(),
14531453
lastSelectedPackageID: .fake(),
1454+
lastOrderCompleted: .fake(),
14541455
addPaymentMethodURL: .fake()
14551456
)
14561457
}

Modules/Sources/Networking/Model/Copiable/Models+Copiable.generated.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2424,6 +2424,7 @@ extension Networking.ShippingLabelAccountSettings {
24242424
isEmailReceiptsEnabled: CopiableProp<Bool> = .copy,
24252425
paperSize: CopiableProp<ShippingLabelPaperSize> = .copy,
24262426
lastSelectedPackageID: CopiableProp<String> = .copy,
2427+
lastOrderCompleted: CopiableProp<Bool> = .copy,
24272428
addPaymentMethodURL: NullableCopiableProp<URL> = .copy
24282429
) -> Networking.ShippingLabelAccountSettings {
24292430
let siteID = siteID ?? self.siteID
@@ -2438,6 +2439,7 @@ extension Networking.ShippingLabelAccountSettings {
24382439
let isEmailReceiptsEnabled = isEmailReceiptsEnabled ?? self.isEmailReceiptsEnabled
24392440
let paperSize = paperSize ?? self.paperSize
24402441
let lastSelectedPackageID = lastSelectedPackageID ?? self.lastSelectedPackageID
2442+
let lastOrderCompleted = lastOrderCompleted ?? self.lastOrderCompleted
24412443
let addPaymentMethodURL = addPaymentMethodURL ?? self.addPaymentMethodURL
24422444

24432445
return Networking.ShippingLabelAccountSettings(
@@ -2453,6 +2455,7 @@ extension Networking.ShippingLabelAccountSettings {
24532455
isEmailReceiptsEnabled: isEmailReceiptsEnabled,
24542456
paperSize: paperSize,
24552457
lastSelectedPackageID: lastSelectedPackageID,
2458+
lastOrderCompleted: lastOrderCompleted,
24562459
addPaymentMethodURL: addPaymentMethodURL
24572460
)
24582461
}

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/Networking/Model/ShippingLabel/ShippingLabelAccountSettings.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ public struct ShippingLabelAccountSettings: Equatable, GeneratedFakeable, Genera
4343
/// Uses the `id` for predefined packages or `name` for custom packages.
4444
public let lastSelectedPackageID: String
4545

46+
/// Whether to mark order as completed after last label purchase
47+
/// This is available only in the new Woo Shipping plugin.
48+
public let lastOrderCompleted: Bool
49+
4650
/// URL to open the web view for adding new payment methods
4751
public let addPaymentMethodURL: URL?
4852

@@ -58,6 +62,7 @@ public struct ShippingLabelAccountSettings: Equatable, GeneratedFakeable, Genera
5862
isEmailReceiptsEnabled: Bool,
5963
paperSize: ShippingLabelPaperSize,
6064
lastSelectedPackageID: String,
65+
lastOrderCompleted: Bool,
6166
addPaymentMethodURL: URL?) {
6267
self.siteID = siteID
6368
self.canManagePayments = canManagePayments
@@ -71,6 +76,7 @@ public struct ShippingLabelAccountSettings: Equatable, GeneratedFakeable, Genera
7176
self.isEmailReceiptsEnabled = isEmailReceiptsEnabled
7277
self.paperSize = paperSize
7378
self.lastSelectedPackageID = lastSelectedPackageID
79+
self.lastOrderCompleted = lastOrderCompleted
7480
self.addPaymentMethodURL = addPaymentMethodURL
7581
}
7682
}
@@ -105,6 +111,7 @@ extension ShippingLabelAccountSettings: Decodable {
105111

106112
let userMetaContainer = try container.nestedContainer(keyedBy: UserMetaKeys.self, forKey: .userMeta)
107113
let lastSelectedPackageID = try userMetaContainer.decode(String.self, forKey: .lastSelectedPackageID)
114+
let lastOrderCompleted = (try? userMetaContainer.decodeIfPresent(Bool.self, forKey: .lastOrderCompleted)) ?? false
108115

109116
self.init(siteID: siteID,
110117
canManagePayments: canManagePayments,
@@ -118,6 +125,7 @@ extension ShippingLabelAccountSettings: Decodable {
118125
isEmailReceiptsEnabled: isEmailReceiptsEnabled,
119126
paperSize: paperSize,
120127
lastSelectedPackageID: lastSelectedPackageID,
128+
lastOrderCompleted: lastOrderCompleted,
121129
addPaymentMethodURL: addPaymentMethodURL)
122130
}
123131
}
@@ -150,6 +158,7 @@ private extension ShippingLabelAccountSettings {
150158

151159
private enum UserMetaKeys: String, CodingKey {
152160
case lastSelectedPackageID = "last_box_id"
161+
case lastOrderCompleted = "last_order_completed"
153162
}
154163
}
155164

Modules/Sources/Networking/Remote/SiteSettingsRemote.swift

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import Foundation
22

3+
/// Protocol for SiteSettingsRemote to enable testing.
4+
public protocol SiteSettingsRemoteProtocol {
5+
func setFeature(for siteID: Int64, feature: SiteSettingsFeature, enabled: Bool) async throws -> Bool
6+
}
7+
38
/// Features that can be enabled/disabled in core, under WC Settings > Advanced > Features.
49
public enum SiteSettingsFeature {
510
case pointOfSale
@@ -114,16 +119,49 @@ public class SiteSettingsRemote: Remote {
114119
let mapper = SiteSettingMapper(siteID: siteID, settingsGroup: .advanced)
115120
let response = try await enqueue(request, mapper: mapper)
116121
switch response.value {
117-
case "yes":
122+
case Constants.featureEnabledValue:
123+
return true
124+
case Constants.featureDisabledValue:
125+
return false
126+
default:
127+
throw SiteSettingsRemoteError.invalidResponse
128+
}
129+
}
130+
131+
/// Enables or disables a specific feature in the site WC settings.
132+
///
133+
/// - Parameters:
134+
/// - siteID: Site for which we'll update the feature status.
135+
/// - feature: The feature to enable or disable.
136+
/// - enabled: Whether the feature should be enabled (true) or disabled (false).
137+
/// - Returns: A boolean indicating the updated feature status.
138+
/// - Throws: Error if the request fails.
139+
///
140+
public func setFeature(for siteID: Int64, feature: SiteSettingsFeature, enabled: Bool) async throws -> Bool {
141+
let value = enabled ? Constants.featureEnabledValue : Constants.featureDisabledValue
142+
let parameters: [String: Any] = [Constants.valueParameter: value]
143+
let path = Constants.siteSettingsPath + Constants.advancedSettingsGroup + "/woocommerce_feature_\(feature.rawValue)_enabled"
144+
let request = JetpackRequest(wooApiVersion: .mark3,
145+
method: .put,
146+
siteID: siteID,
147+
path: path,
148+
parameters: parameters,
149+
availableAsRESTRequest: true)
150+
let mapper = SiteSettingMapper(siteID: siteID, settingsGroup: .advanced)
151+
let response = try await enqueue(request, mapper: mapper)
152+
switch response.value {
153+
case Constants.featureEnabledValue:
118154
return true
119-
case "no":
155+
case Constants.featureDisabledValue:
120156
return false
121157
default:
122158
throw SiteSettingsRemoteError.invalidResponse
123159
}
124160
}
125161
}
126162

163+
extension SiteSettingsRemote: SiteSettingsRemoteProtocol {}
164+
127165
public extension SiteSettingsFeature {
128166
var rawValue: String {
129167
switch self {
@@ -142,6 +180,8 @@ private extension SiteSettingsRemote {
142180
static let productSettingsGroup: String = "products"
143181
static let advancedSettingsGroup: String = "advanced"
144182
static let valueParameter: String = "value"
183+
static let featureEnabledValue: String = "yes"
184+
static let featureDisabledValue: String = "no"
145185
}
146186
}
147187

Modules/Sources/Networking/Remote/WooShippingRemote.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public protocol WooShippingRemoteProtocol {
3737
originAddress: WooShippingAddress,
3838
destinationAddress: WooShippingAddress,
3939
package: WooShippingPackagePurchase,
40+
markOrderComplete: Bool?,
4041
completion: @escaping (Result<[ShippingLabelPurchase], Error>) -> Void)
4142
func checkLabelStatus(siteID: Int64,
4243
orderID: Int64,
@@ -284,7 +285,14 @@ public final class WooShippingRemote: Remote, WooShippingRemoteProtocol {
284285
originAddress: WooShippingAddress,
285286
destinationAddress: WooShippingAddress,
286287
package: WooShippingPackagePurchase,
288+
markOrderComplete: Bool?,
287289
completion: @escaping (Result<[ShippingLabelPurchase], Error>) -> Void) {
290+
let userMeta: [String: Any]? = {
291+
guard let markOrderComplete else {
292+
return nil
293+
}
294+
return [ParameterKey.lastOrderCompleted: markOrderComplete]
295+
}()
288296
do {
289297
let parameters: [String: Any] = [
290298
ParameterKey.async: true,
@@ -296,7 +304,8 @@ public final class WooShippingRemote: Remote, WooShippingRemoteProtocol {
296304
ParameterKey.featuresSupported: [Values.upsdap],
297305
ParameterKey.hazmat: package.encodedHazmat(),
298306
ParameterKey.customs: try package.encodedCustomsForm(),
299-
]
307+
ParameterKey.userMeta: userMeta,
308+
].compactMapValues { $0 }
300309
let path = "\(Path.purchase)/\(orderID)"
301310
let request = JetpackRequest(wooApiVersion: .wooShipping,
302311
method: .post,
@@ -635,6 +644,8 @@ private extension WooShippingRemote {
635644
static let enabled = "enabled"
636645
static let featuresSupported = "features_supported_by_client"
637646
static let confirmed = "confirmed"
647+
static let userMeta = "user_meta"
648+
static let lastOrderCompleted = "last_order_completed"
638649
}
639650

640651
enum Values {

Modules/Sources/Yosemite/Actions/WooShippingAction.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public enum WooShippingAction: Action {
5656
originAddress: WooShippingAddress,
5757
destinationAddress: WooShippingAddress,
5858
package: WooShippingPackagePurchase,
59+
markOrderComplete: Bool?,
5960
backendProcessingDelay: TimeInterval = 2.0,
6061
pollingDelay: TimeInterval = 1.0,
6162
pollingMaximumRetries: Int64 = 3,

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ extension Storage.ShippingLabelAccountSettings: ReadOnlyConvertible {
2626
let paymentMethodItems = paymentMethods?.map { $0.toReadOnly() } ?? []
2727

2828
/// Since account settings are not persisted for the new shipping label flow,
29-
/// the conversion for the new property `addPaymentMethodURL` is ignored.
30-
/// This avoids the complication of unnecessary Core Data migration for the new property.
29+
/// the conversion for the new properties `addPaymentMethodURL` & `lastOrderCompleted` is ignored.
30+
/// This avoids the complication of unnecessary Core Data migration for the new properties.
3131
return ShippingLabelAccountSettings(siteID: siteID,
3232
canManagePayments: canManagePayments,
3333
canEditSettings: canEditSettings,
@@ -40,6 +40,7 @@ extension Storage.ShippingLabelAccountSettings: ReadOnlyConvertible {
4040
isEmailReceiptsEnabled: isEmailReceiptsEnabled,
4141
paperSize: .init(rawValue: paperSize ?? ""),
4242
lastSelectedPackageID: lastSelectedPackageID ?? "",
43+
lastOrderCompleted: false,
4344
addPaymentMethodURL: nil)
4445
}
4546
}

Modules/Sources/Yosemite/Stores/WooShippingStore.swift

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public final class WooShippingStore: Store {
5757
originAddress,
5858
destinationAddress,
5959
package,
60+
markOrderComplete,
6061
backendProcessingDelay,
6162
pollingDelay,
6263
pollingMaximumRetries,
@@ -66,6 +67,7 @@ public final class WooShippingStore: Store {
6667
originAddress: originAddress,
6768
destinationAddress: destinationAddress,
6869
package: package,
70+
markOrderComplete: markOrderComplete,
6971
backendProcessingDelay: backendProcessingDelay,
7072
pollingDelay: pollingDelay,
7173
pollingMaximumRetries: pollingMaximumRetries,
@@ -216,6 +218,7 @@ private extension WooShippingStore {
216218
originAddress: WooShippingAddress,
217219
destinationAddress: WooShippingAddress,
218220
package: WooShippingPackagePurchase,
221+
markOrderComplete: Bool?,
219222
backendProcessingDelay: TimeInterval,
220223
pollingDelay: TimeInterval,
221224
pollingMaximumRetries: Int64,
@@ -225,7 +228,8 @@ private extension WooShippingStore {
225228
orderID: orderID,
226229
originAddress: originAddress,
227230
destinationAddress: destinationAddress,
228-
package: package) { result in
231+
package: package,
232+
markOrderComplete: markOrderComplete) { result in
229233
switch result {
230234
case .success(let labelPurchases):
231235
// Purchase endpoint returns an array of labels, but the polling endpoint only takes a single label at a time.
@@ -326,7 +330,12 @@ private extension WooShippingStore {
326330
// If label has PURCHASED status, stop polling
327331
if labelStatusResponse.status == .purchased,
328332
let label = labelStatusResponse.getPurchasedLabel() {
329-
completion(.success(label))
333+
guard let self else {
334+
return completion(.success(label))
335+
}
336+
insertPurchasedLabelInBackground(siteID: siteID, orderID: orderID, shippingLabel: label) {
337+
completion(.success(label))
338+
}
330339
}
331340

332341
// If label has PURCHASE_ERROR status, return error and stop polling
@@ -410,7 +419,23 @@ private extension WooShippingStore {
410419
orderID: Int64,
411420
shipmentToUpdate: WooShippingUpdateShipment,
412421
completion: @escaping (Result<WooShippingShipments, Error>) -> Void) {
413-
remote.updateShipment(siteID: siteID, orderID: orderID, shipmentToUpdate: shipmentToUpdate, completion: completion)
422+
remote.updateShipment(siteID: siteID, orderID: orderID, shipmentToUpdate: shipmentToUpdate) { [weak self] result in
423+
guard let self, let contents = try? result.get() else {
424+
return completion(result)
425+
}
426+
let shipments = contents.map { (index, items) in
427+
WooShippingShipment(siteID: siteID,
428+
orderID: orderID,
429+
index: index,
430+
items: items,
431+
shippingLabel: nil)
432+
}
433+
upsertShipmentsInBackground(siteID: siteID,
434+
orderID: orderID,
435+
shipments: shipments) {
436+
completion(.success(contents))
437+
}
438+
}
414439
}
415440
}
416441

@@ -644,10 +669,15 @@ private extension WooShippingStore {
644669
DDLogWarn("⚠️ No shipping label found in storage when updating refund")
645670
return shippingLabel.copy(refund: refund)
646671
}
672+
let storageShipment = storageShippingLabel.shipment
647673

648674
let storageRefund = storageShippingLabel.refund ?? storage.insertNewObject(ofType: Storage.ShippingLabelRefund.self)
649675
storageRefund.update(with: refund)
650676
storageShippingLabel.refund = storageRefund
677+
678+
// update stored shipment to trigger onDidChangeContent notification
679+
storageShipment?.shippingLabel = storageShippingLabel
680+
651681
return storageShippingLabel.toReadOnly()
652682

653683
}, completion: { result in
@@ -676,6 +706,28 @@ private extension WooShippingStore {
676706
}, completion: nil, on: .main)
677707
}
678708

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

0 commit comments

Comments
 (0)