Skip to content

Commit

Permalink
Merge branch 'trunk' into task/14122-shipping-method-store
Browse files Browse the repository at this point in the history
  • Loading branch information
itsmeichigo authored Dec 4, 2024
2 parents c6259ae + 914fdf0 commit 829a218
Show file tree
Hide file tree
Showing 73 changed files with 1,333 additions and 1,417 deletions.
4 changes: 3 additions & 1 deletion Hardware/Hardware/CardReader/CardReaderServiceError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ extension CardReaderServiceError: LocalizedError {
return underlyingError.errorDescription ?? underlyingError.localizedDescription
case .bluetoothDenied:
return NSLocalizedString(
"This app needs permission to access Bluetooth to connect to a card reader, please change the privacy settings if you wish to allow this.",
"hardware.cardReader.cardReaderServiceError.bluetoothDenied",
value: "This app needs permission to access Bluetooth to connect to your card reader. " +
"You can grant permission in the system's Settings app, in the Woo section.",
comment: "Explanation in the alert presented when the user tries to connect a Bluetooth card reader with insufficient permissions"
)
case .retryNotPossibleNoActivePayment,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ extension UnderlyingError {
self = .bluetoothConnectTimedOut
case ErrorCode.Code.bluetoothDisconnected.rawValue:
self = .bluetoothDisconnected
case ErrorCode.Code.bluetoothAccessDenied.rawValue:
self = .bluetoothDenied
case ErrorCode.Code.unsupportedReaderVersion.rawValue:
self = .unsupportedReaderVersion
case ErrorCode.Code.connectFailedReaderIsInUse.rawValue:
Expand Down
10 changes: 10 additions & 0 deletions Hardware/Hardware/CardReader/UnderlyingError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ public enum UnderlyingError: Error, Equatable {
/// The Bluetooth device was disconnected unexpectedly.
case bluetoothDisconnected

/// The user has denied the app permission to use Bluetooth
case bluetoothDenied

/// An attempt to process a payment was made from a reader with an unsupported reader version.
/// You will need to update your reader to the most recent version in order to accept payments
case unsupportedReaderVersion
Expand Down Expand Up @@ -324,6 +327,13 @@ extension UnderlyingError: LocalizedError {
return NSLocalizedString("Unable to connect to reader - the reader has a critically low battery - charge the reader and try again.",
comment: "Error message the card reader battery level is too low to connect to the phone or tablet.")

case .bluetoothDenied:
return NSLocalizedString(
"hardware.cardReader.underlyingError.bluetoothDenied",
value: "This app needs permission to access Bluetooth to connect to your card reader. " +
"You can grant permission in the system's Settings app, in the Woo section.",
comment: "Explanation in the alert presented when the user tries to connect a Bluetooth card reader with insufficient permissions"
)
case .readerSoftwareUpdateFailedBatteryLow:
return NSLocalizedString("Unable to update card reader software - the reader battery is too low.",
comment: "Error message when the card reader battery level is too low to safely perform a software update.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4122,7 +4122,7 @@ extension Networking.WooShippingPackagesResponse {
storeOptions: CopiableProp<ShippingLabelStoreOptions> = .copy,
customPackages: CopiableProp<[WooShippingCustomPackage]> = .copy,
savedPredefinedPackages: CopiableProp<[WooShippingSavedPredefinedPackage]> = .copy,
allPredefinedOptions: CopiableProp<[WooShippingPredefinedOption]> = .copy
allPredefinedOptions: CopiableProp<[WooShippingCarrierPredefinedOptions]> = .copy
) -> Networking.WooShippingPackagesResponse {
let storeOptions = storeOptions ?? self.storeOptions
let customPackages = customPackages ?? self.customPackages
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Codegen

/// Represents an item in a Product Bundle
///
public struct ProductBundleItem: Codable, Equatable, GeneratedCopiable, GeneratedFakeable {
public struct ProductBundleItem: Codable, Equatable, Hashable, GeneratedCopiable, GeneratedFakeable {
/// Bundled item ID
public let bundledItemID: Int64

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ public struct WooShippingPredefinedOption: Equatable, GeneratedFakeable {
}
}

/// Represents a predefined carrier option in Shipping Labels for the WooCommerce Shipping extension.
///
public struct WooShippingCarrierPredefinedOptions: Equatable, GeneratedFakeable {
public let carrierID: String
public let predefinedOptions: [WooShippingPredefinedOption]
}

// MARK: Decodable
extension WooShippingPredefinedOption: Decodable {
public init(from decoder: Decoder) throws {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,21 @@ extension WooShippingPredefinedPackage: Decodable {
let id = try container.decode(String.self, forKey: .id)
let name = try container.decode(String.self, forKey: .name)
let isLetter = try container.decodeIfPresent(Bool.self, forKey: .isLetter) ?? false
let dimensions = try container.decode(String.self, forKey: .dimensions)
var dimensions: String = ""
if let dimensionsString = try? container.decodeIfPresent(String.self, forKey: .dimensions) {
dimensions = dimensionsString
}
else if let dimensionsDict = try? container.decodeIfPresent([String: String].self, forKey: .dimensions) {
if let dimensionsOuter = dimensionsDict["outer"] {
dimensions = dimensionsOuter
}
else if let dimensionsInner = dimensionsDict["inner"] {
dimensions = dimensionsInner
}
}
else if let outerDimensionsString = try? container.decodeIfPresent(String.self, forKey: .outerDimensions) {
dimensions = outerDimensionsString
}
let groupId = try container.decode(String.self, forKey: .groupId)
var boxWeight: String = ""
// Looks like some endpoints have boxWeight as String and some as Double
Expand All @@ -86,7 +100,10 @@ extension WooShippingPredefinedPackage: Decodable {
case name
case isLetter = "is_letter"
case dimensions
case outer
case inner
case groupId = "group_id"
case boxWeight = "box_weight"
case outerDimensions = "outer_dimensions"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import WooFoundation
/// Container struct for a predefined Shipping Label Packages for the WooCommerce Shipping extension with group title and provider identifier.
///
public struct WooShippingSavedPredefinedPackage: Equatable, GeneratedFakeable, Identifiable {
let groupTitle: String
let providerID: String
let package: WooShippingPredefinedPackage
public let groupTitle: String
public let providerID: String
public let package: WooShippingPredefinedPackage

public var id: String {
return package.id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ public struct WooShippingPackagesResponse: Equatable, GeneratedFakeable, Generat
public let savedPredefinedPackages: [WooShippingSavedPredefinedPackage]

/// All predefined options
public let allPredefinedOptions: [WooShippingPredefinedOption]
public let allPredefinedOptions: [WooShippingCarrierPredefinedOptions]

public init(storeOptions: ShippingLabelStoreOptions,
customPackages: [WooShippingCustomPackage],
savedPredefinedPackages: [WooShippingSavedPredefinedPackage],
allPredefinedOptions: [WooShippingPredefinedOption]) {
allPredefinedOptions: [WooShippingCarrierPredefinedOptions]) {
self.storeOptions = storeOptions
self.customPackages = customPackages
self.savedPredefinedPackages = savedPredefinedPackages
Expand All @@ -44,21 +44,23 @@ extension WooShippingPackagesResponse: Decodable {
}

let allPredefinedPackagesData: [String: AnyCodable] = try packagesData.decode([String: AnyCodable].self, forKey: .predefined)
var allPredefinedOptions: [WooShippingPredefinedOption] = []
var allPredefinedOptions: [WooShippingCarrierPredefinedOptions] = []
var allSavedPredefinedPackages: [WooShippingSavedPredefinedPackage] = []

allPredefinedPackagesData.forEach { (key, value) in
// key is a carrier id, for example "usps"
if let provider: [String: Any]? = try? value.toDictionary() {
var providerOptions: [WooShippingPredefinedOption] = []
provider?.forEach({ (providerKey, providerValue) in
// providerKey is package group id, for example "pri_flat_boxes"
let providerValueDict = providerValue as? [String: Any]
let title: String = providerValueDict?["title"] as? String ?? ""
let packages = WooShippingPackagesResponse.getAllPredefinedPackages(packageDefinitions: providerValueDict)
let option = WooShippingPredefinedOption(title: title, providerID: key, predefinedPackages: packages)
allPredefinedOptions.append(option)
providerOptions.append(option)
allSavedPredefinedPackages.append(contentsOf: WooShippingPackagesResponse.savedPackages(savedOptions: savedPredefinedOptions, option: option))
})
allPredefinedOptions.append(WooShippingCarrierPredefinedOptions(carrierID: key, predefinedOptions: providerOptions))
}
}

Expand Down
7 changes: 7 additions & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,16 @@
- [*] Payments onboarding: the custom background color of the "Choose your Payment Provider" UI when both WooPayments and Stripe Extension are available was removed to enable use cases with different container background colors. [https://github.com/woocommerce/woocommerce-ios/pull/14546]
- [*] Media: Media upload will work for sites without XML-RPC. [https://github.com/woocommerce/woocommerce-ios/pull/14537]
- [Internal] Updated CoreDataManager to be thread-safe [https://github.com/woocommerce/woocommerce-ios/pull/14534]
- [Internal] Core Data: Migrate storage usage in SiteStore [https://github.com/woocommerce/woocommerce-ios/pull/14548]
- [Internal] Updated storage usage in CouponStore [https://github.com/woocommerce/woocommerce-ios/pull/14530]
- [Internal] Update storage usage for BlazeStore [https://github.com/woocommerce/woocommerce-ios/pull/14532]
- [Internal] Updated storage usage in ProductShippingClassStore [https://github.com/woocommerce/woocommerce-ios/pull/14520]
- [Internal] Updated storage usage in ShippingMethodStore [https://github.com/woocommerce/woocommerce-ios/pull/14568]
- [Internal] Updated storage usage in TaxStore [https://github.com/woocommerce/woocommerce-ios/pull/14567]
- [Internal] Updated storage usage in SystemStatusStore [https://github.com/woocommerce/woocommerce-ios/pull/14559]
- [Internal] Updated storage usage in SitePluginStore [https://github.com/woocommerce/woocommerce-ios/pull/14560]
- [Internal] Updated storage usage in ShippingLabelStore [https://github.com/woocommerce/woocommerce-ios/pull/14566]
- [*] Fixed: Improved the error message displayed when Bluetooth permission is denied during the card reader connection process. [https://github.com/woocommerce/woocommerce-ios/pull/14561]

21.2
-----
Expand Down
15 changes: 15 additions & 0 deletions Storage/Storage/Tools/StorageType+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,13 @@ public extension StorageType {
return allObjects(ofType: BlazeCampaignListItem.self, matching: predicate, sortedBy: nil)
}

/// Returns stored BlazeCampaignListItem s for a site matching the given IDs
///
func loadBlazeCampaignListItems(siteID: Int64, with campaignIDs: [String]) -> [BlazeCampaignListItem] {
let predicate = NSPredicate(format: "siteID == %lld && campaignID in %@", siteID, campaignIDs)
return allObjects(ofType: BlazeCampaignListItem.self, matching: predicate, sortedBy: nil)
}

// MARK: BlazeTargetDevice

/// Returns all Blaze target devices with the given locale.
Expand Down Expand Up @@ -741,6 +748,14 @@ public extension StorageType {
return allObjects(ofType: SystemPlugin.self, matching: predicate, sortedBy: [descriptor])
}

/// Returns stored system plugins for a provided `siteID` matching the given `names`
///
func loadSystemPlugins(siteID: Int64, matching names: [String]) -> [SystemPlugin] {
let predicate = NSPredicate(format: "siteID == %lld && name in %@", siteID, names)
let descriptor = NSSortDescriptor(keyPath: \SystemPlugin.name, ascending: true)
return allObjects(ofType: SystemPlugin.self, matching: predicate, sortedBy: [descriptor])
}

/// Returns a system plugin with a specified `siteID` and `name`
///
func loadSystemPlugin(siteID: Int64, name: String) -> SystemPlugin? {
Expand Down
84 changes: 82 additions & 2 deletions Storage/StorageTests/Tools/StorageTypeExtensionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1344,7 +1344,7 @@ final class StorageTypeExtensionsTests: XCTestCase {

// MARK: - System plugins

func test_loadSystemPlugins_by_siteID_and_sorted_by_name() throws {
func test_loadSystemPlugins_by_siteID_and_sorted_by_name() {
// Given
let systemPlugin1 = storage.insertNewObject(ofType: SystemPlugin.self)
systemPlugin1.name = "Plugin 1"
Expand All @@ -1359,12 +1359,37 @@ final class StorageTypeExtensionsTests: XCTestCase {
systemPlugin3.siteID = sampleSiteID

// When
let storedSystemPlugins = try XCTUnwrap(storage.loadSystemPlugins(siteID: sampleSiteID))
let storedSystemPlugins = storage.loadSystemPlugins(siteID: sampleSiteID)

// Then
XCTAssertEqual(storedSystemPlugins, [systemPlugin1, systemPlugin3])
}

func test_loadSystemPlugins_by_siteID_matching_names() {
// Given
let systemPlugin1 = storage.insertNewObject(ofType: SystemPlugin.self)
systemPlugin1.name = "Plugin 1"
systemPlugin1.siteID = sampleSiteID

let systemPlugin2 = storage.insertNewObject(ofType: SystemPlugin.self)
systemPlugin2.name = "Plugin 1"
systemPlugin2.siteID = sampleSiteID + 1

let systemPlugin3 = storage.insertNewObject(ofType: SystemPlugin.self)
systemPlugin3.name = "Plugin 3"
systemPlugin3.siteID = sampleSiteID

let systemPlugin4 = storage.insertNewObject(ofType: SystemPlugin.self)
systemPlugin4.name = "Plugin 4"
systemPlugin4.siteID = sampleSiteID

// When
let storedSystemPlugins = storage.loadSystemPlugins(siteID: sampleSiteID, matching: ["Plugin 1", "Plugin 4"])

// Then
XCTAssertEqual(storedSystemPlugins, [systemPlugin1, systemPlugin4])
}

func test_loadSystemPlugin_by_siteID_and_name() throws {
// Given
let systemPlugin1 = storage.insertNewObject(ofType: SystemPlugin.self)
Expand Down Expand Up @@ -1416,6 +1441,61 @@ final class StorageTypeExtensionsTests: XCTestCase {
XCTAssertEqual(foundCharge, charge2)
}

func test_loadBlazeCampaigns_by_siteID() {
// Given
let campaign1 = storage.insertNewObject(ofType: BlazeCampaignListItem.self)
campaign1.siteID = 1
campaign1.campaignID = "1"

let campaign2 = storage.insertNewObject(ofType: BlazeCampaignListItem.self)
campaign2.siteID = sampleSiteID
campaign2.campaignID = "2"

// When
let campaigns = storage.loadAllBlazeCampaignListItems(siteID: sampleSiteID)

// Then
XCTAssertEqual(campaigns, [campaign2])
}

func test_loadBlazeCampaign_by_siteID_campaignID() throws {
// Given
let campaign1 = storage.insertNewObject(ofType: BlazeCampaignListItem.self)
campaign1.siteID = sampleSiteID
campaign1.campaignID = "1"

let campaign2 = storage.insertNewObject(ofType: BlazeCampaignListItem.self)
campaign2.siteID = sampleSiteID
campaign2.campaignID = "2"

// When
let campaign = try XCTUnwrap(storage.loadBlazeCampaignListItem(siteID: sampleSiteID, campaignID: "1"))

// Then
XCTAssertEqual(campaign, campaign1)
}

func test_loadBlazeCampaigns_by_siteID_and_campaignIDs() {
// Given
let campaign1 = storage.insertNewObject(ofType: BlazeCampaignListItem.self)
campaign1.siteID = 1
campaign1.campaignID = "1"

let campaign2 = storage.insertNewObject(ofType: BlazeCampaignListItem.self)
campaign2.siteID = sampleSiteID
campaign2.campaignID = "2"

let campaign3 = storage.insertNewObject(ofType: BlazeCampaignListItem.self)
campaign3.siteID = sampleSiteID
campaign3.campaignID = "3"

// When
let campaigns = storage.loadBlazeCampaignListItems(siteID: sampleSiteID, with: ["2", "3"])

// Then
XCTAssertEqual(Set(campaigns), Set([campaign2, campaign3]))
}

func test_loadAllBlazeTargetDevices_with_locale() throws {
// Given
let device1 = storage.insertNewObject(ofType: BlazeTargetDevice.self)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ private extension CardReaderServiceUnderlyingError {
.bluetoothError,
.bluetoothScanTimedOut,
.bluetoothConnectionFailedBatteryCriticallyLow,
.bluetoothDenied,
.readerSoftwareUpdateFailedBatteryLow,
.readerSoftwareUpdateFailedInterrupted,
.readerSoftwareUpdateFailed,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation
import Combine
import protocol Yosemite.POSItem
import protocol Yosemite.POSDisplayableItem
import protocol Yosemite.PointOfSaleItemServiceProtocol
import enum Yosemite.PointOfSaleProductServiceError

Expand All @@ -14,7 +14,7 @@ protocol PointOfSaleItemsControllerProtocol {
class PointOfSaleItemsController: PointOfSaleItemsControllerProtocol {
private(set) var itemListStatePublisher: any Publisher<ItemListState, Never>
private var itemListStateSubject: PassthroughSubject<ItemListState, Never> = .init()
private var allItems: [POSItem] = []
private var allItems: [POSDisplayableItem] = []
private var currentPage: Int = Constants.initialPage
private var mightHaveMorePages: Bool = true
private let itemProvider: PointOfSaleItemServiceProtocol
Expand Down Expand Up @@ -76,7 +76,7 @@ class PointOfSaleItemsController: PointOfSaleItemsControllerProtocol {
private func fetchItems(pageNumber: Int) async throws {
let newItems = try await itemProvider.providePointOfSaleItems(pageNumber: pageNumber)
let uniqueNewItems = newItems.filter { newItem in
!allItems.contains(where: { $0.productID == newItem.productID })
!allItems.contains(where: { $0.isEqual(to: newItem) })
}
allItems.append(contentsOf: uniqueNewItems)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,21 @@ final class PointOfSaleOrderController: PointOfSaleOrderControllerProtocol {
private(set) var order: Order? = nil

@MainActor
func syncOrder(for cartProducts: [CartItem],
func syncOrder(for cartItems: [CartItem],
retryHandler: @escaping () async -> Void) async {
let posCartItems = cartItems.map {
POSCartItem(item: $0.item, quantity: Decimal($0.quantity))
}

guard !orderState.isSyncing,
CartItem.areOrderAndCartDifferent(order: order, cartItems: cartProducts) else {
!posCartItems.matches(order: order) else {
return
}

orderState = .syncing
let cartItems = cartProducts.map {
POSCartItem(product: $0.item, quantity: Decimal($0.quantity))
}

do {
let syncedOrder = try await orderService.syncOrder(cart: cartItems, order: order)
let syncedOrder = try await orderService.syncOrder(cart: posCartItems, order: order)
self.order = syncedOrder
orderState = .loaded(totals(for: syncedOrder), syncedOrder)
DDLogInfo("🟢 [POS] Synced order: \(syncedOrder)")
Expand Down
Loading

0 comments on commit 829a218

Please sign in to comment.