Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Modules/Sources/Fakes/Networking.generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2383,6 +2383,7 @@ extension Networking.WooShippingOriginAddress {
///
public static func fake() -> Networking.WooShippingOriginAddress {
.init(
siteID: .fake(),
id: .fake(),
company: .fake(),
address1: .fake(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import Foundation

struct WooShippingOriginAddressUpdateMapper: Mapper {
/// Site ID associated to the origin addresses that will be parsed.
///
/// We're injecting this field via `JSONDecoder.userInfo` because SiteID is not returned from the remote.
///
let siteID: Int64

/// (Attempts) to convert a dictionary into a WooShippingOriginAddressUpdate.
///
func map(response: Data) throws -> WooShippingOriginAddressUpdate {
let decoder = JSONDecoder()
decoder.userInfo = [.siteID: siteID]
if hasDataEnvelope(in: response) {
return try decoder.decode(WooShippingOriginAddressUpdateMapperEnvelope.self, from: response).data
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import Foundation

struct WooShippingOriginAddressesMapper: Mapper {

/// Site ID associated to the origin addresses that will be parsed.
///
/// We're injecting this field via `JSONDecoder.userInfo` because SiteID is not returned from the remote.
///
let siteID: Int64

/// (Attempts) to convert a dictionary into WooShippingOriginAddress array.
///
func map(response: Data) throws -> [WooShippingOriginAddress] {
let decoder = JSONDecoder()
decoder.userInfo = [.siteID: siteID]
if hasDataEnvelope(in: response) {
return try decoder.decode(WooShippingOriginAddressesMapperEnvelope.self, from: response).data
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3636,6 +3636,7 @@ extension Networking.WooShippingNormalizedAddress {

extension Networking.WooShippingOriginAddress {
public func copy(
siteID: CopiableProp<Int64> = .copy,
id: CopiableProp<String> = .copy,
company: CopiableProp<String> = .copy,
address1: CopiableProp<String> = .copy,
Expand All @@ -3651,6 +3652,7 @@ extension Networking.WooShippingOriginAddress {
defaultAddress: CopiableProp<Bool> = .copy,
isVerified: CopiableProp<Bool> = .copy
) -> Networking.WooShippingOriginAddress {
let siteID = siteID ?? self.siteID
let id = id ?? self.id
let company = company ?? self.company
let address1 = address1 ?? self.address1
Expand All @@ -3667,6 +3669,7 @@ extension Networking.WooShippingOriginAddress {
let isVerified = isVerified ?? self.isVerified

return Networking.WooShippingOriginAddress(
siteID: siteID,
id: id,
company: company,
address1: address1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Foundation
import Codegen

public struct WooShippingOriginAddress: Identifiable, Equatable, GeneratedFakeable, GeneratedCopiable {
public let siteID: Int64
public let id: String
public let company: String
public let address1: String
Expand All @@ -17,7 +18,8 @@ public struct WooShippingOriginAddress: Identifiable, Equatable, GeneratedFakeab
public let defaultAddress: Bool
public let isVerified: Bool

public init(id: String,
public init(siteID: Int64,
id: String,
company: String,
address1: String,
address2: String,
Expand All @@ -32,6 +34,7 @@ public struct WooShippingOriginAddress: Identifiable, Equatable, GeneratedFakeab
defaultAddress:
Bool,
isVerified: Bool) {
self.siteID = siteID
self.id = id
self.company = company
self.address1 = address1
Expand All @@ -52,6 +55,9 @@ public struct WooShippingOriginAddress: Identifiable, Equatable, GeneratedFakeab
// MARK: Decodable
extension WooShippingOriginAddress: Codable {
public init(from decoder: Decoder) throws {
guard let siteID = decoder.userInfo[.siteID] as? Int64 else {
throw DecodingError.missingSiteID
}
let container = try decoder.container(keyedBy: CodingKeys.self)

let id = try container.decode(String.self, forKey: CodingKeys.id)
Expand All @@ -70,7 +76,8 @@ extension WooShippingOriginAddress: Codable {
let defaultAddress = try container.decodeIfPresent(Bool.self, forKey: CodingKeys.defaultAddress) ?? false
let isVerified = try container.decodeIfPresent(Bool.self, forKey: CodingKeys.isVerified) ?? false

self.init(id: id,
self.init(siteID: siteID,
id: id,
company: company,
address1: address1,
address2: address2,
Expand Down Expand Up @@ -120,4 +127,9 @@ extension WooShippingOriginAddress: Codable {
case defaultAddress = "default_address"
case isVerified = "is_verified"
}

/// Decoding Errors
enum DecodingError: Error {
case missingSiteID
}
}
4 changes: 2 additions & 2 deletions Modules/Sources/Networking/Remote/WooShippingRemote.swift
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ public final class WooShippingRemote: Remote, WooShippingRemoteProtocol {
path: Path.originAddresses,
parameters: nil,
availableAsRESTRequest: true)
let mapper = WooShippingOriginAddressesMapper()
let mapper = WooShippingOriginAddressesMapper(siteID: siteID)

enqueue(request, mapper: mapper, completion: completion)
}
Expand Down Expand Up @@ -431,7 +431,7 @@ public final class WooShippingRemote: Remote, WooShippingRemoteProtocol {
path: Path.updateOrigin,
parameters: parameters,
availableAsRESTRequest: true)
let mapper = WooShippingOriginAddressUpdateMapper()
let mapper = WooShippingOriginAddressUpdateMapper(siteID: siteID)
enqueue(request, mapper: mapper, completion: completion)
} catch {
completion(.failure(error))
Expand Down
2 changes: 2 additions & 0 deletions Modules/Sources/Storage/Model/MIGRATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ This file documents changes in the WCiOS Storage data model. Please explain any
- Added `WooShippingShipmentItem` entity.
- Added `shipment` relationship to `ShippingLabel` entity.
- Added `shipments` relationship to `Order` entity.
- @itsmeichigo 2025-07-22
- Added `WooShippingOriginAddress` entity.

## Model 123 (Release 22.8.0.0)
- @iamgabrielma 2025-06-30
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation
import CoreData

@objc(WooShippingOriginAddress)
public class WooShippingOriginAddress: NSManagedObject {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Foundation
import CoreData

extension WooShippingOriginAddress {

@nonobjc public class func fetchRequest() -> NSFetchRequest<WooShippingOriginAddress> {
return NSFetchRequest<WooShippingOriginAddress>(entityName: "WooShippingOriginAddress")
}

@NSManaged public var siteID: Int64
@NSManaged public var id: String
@NSManaged public var company: String
@NSManaged public var address1: String
@NSManaged public var address2: String
@NSManaged public var city: String
@NSManaged public var state: String
@NSManaged public var postcode: String
@NSManaged public var country: String
@NSManaged public var phone: String
@NSManaged public var firstName: String
@NSManaged public var lastName: String
@NSManaged public var email: String
@NSManaged public var defaultAddress: Bool
@NSManaged public var isVerified: Bool

}
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,23 @@
<attribute name="rawType" attributeType="String" defaultValueString=""/>
<relationship name="packagesResponse" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="WooShippingPackagesResponse" inverseName="customPackages" inverseEntity="WooShippingPackagesResponse"/>
</entity>
<entity name="WooShippingOriginAddress" representedClassName="WooShippingOriginAddress" syncable="YES">
<attribute name="address1" attributeType="String" defaultValueString=""/>
<attribute name="address2" attributeType="String" defaultValueString=""/>
<attribute name="city" attributeType="String" defaultValueString=""/>
<attribute name="company" attributeType="String" defaultValueString=""/>
<attribute name="country" attributeType="String" defaultValueString=""/>
<attribute name="defaultAddress" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="email" attributeType="String" defaultValueString=""/>
<attribute name="firstName" attributeType="String" defaultValueString=""/>
<attribute name="id" attributeType="String" defaultValueString=""/>
<attribute name="isVerified" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="lastName" attributeType="String" defaultValueString=""/>
<attribute name="phone" attributeType="String" defaultValueString=""/>
<attribute name="postcode" attributeType="String" defaultValueString=""/>
<attribute name="siteID" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="state" attributeType="String" defaultValueString=""/>
</entity>
<entity name="WooShippingPackagesResponse" representedClassName="WooShippingPackagesResponse" syncable="YES">
<attribute name="siteID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="allPredefinedOptions" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="WooShippingCarrierPredefinedOptions" inverseName="packagesResponse" inverseEntity="WooShippingCarrierPredefinedOptions"/>
Expand Down
7 changes: 7 additions & 0 deletions Modules/Sources/Storage/Tools/StorageType+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,13 @@ public extension StorageType {
return allObjects(ofType: WooShippingShipment.self, matching: predicate, sortedBy: nil)
}

/// Returns all stored origin addresses for a site.
///
func loadAllOriginAddresses(siteID: Int64) -> [WooShippingOriginAddress] {
let predicate = \WooShippingOriginAddress.siteID == siteID
return allObjects(ofType: WooShippingOriginAddress.self, matching: predicate, sortedBy: nil)
}

// MARK: - BlazeCampaignListItem

/// Returns a single BlazeCampaignListItem given a `siteID` and `campaignID`
Expand Down
1 change: 1 addition & 0 deletions Modules/Sources/Yosemite/Model/Model.swift
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ public typealias StorageWooShippingPredefinedPackage = Storage.WooShippingPredef
public typealias StorageWooShippingCustomPackage = Storage.WooShippingCustomPackage
public typealias StorageWooShippingSavedPredefinedPackage = Storage.WooShippingSavedPredefinedPackage
public typealias StorageWooShippingShipment = Storage.WooShippingShipment
public typealias StorageWooShippingOriginAddress = Storage.WooShippingOriginAddress

// MARK: - Internal ReadOnly Models

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Foundation
import Storage

// Storage.WooShippingOriginAddress: ReadOnlyConvertible Conformance.
//
extension Storage.WooShippingOriginAddress: ReadOnlyConvertible {
/// Updates the Storage.ShippingLabelAddress with the a ReadOnly ShippingLabelAddress.
///
public func update(with address: Yosemite.WooShippingOriginAddress) {
siteID = address.siteID
id = address.id
company = address.company
firstName = address.firstName
lastName = address.lastName
phone = address.phone
country = address.country
state = address.state
address1 = address.address1
address2 = address.address2
city = address.city
postcode = address.postcode
email = address.email
defaultAddress = address.defaultAddress
isVerified = address.isVerified
}

/// Returns a ReadOnly version of the receiver.
///
public func toReadOnly() -> Yosemite.WooShippingOriginAddress {
.init(siteID: siteID,
id: id,
company: company,
address1: address1,
address2: address2,
city: city,
state: state,
postcode: postcode,
country: country,
phone: phone,
firstName: firstName,
lastName: lastName,
email: email,
defaultAddress: defaultAddress,
isVerified: isVerified)
}
}
35 changes: 34 additions & 1 deletion Modules/Sources/Yosemite/Stores/WooShippingStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,17 @@ private extension WooShippingStore {

func loadOriginAddresses(siteID: Int64,
completion: @escaping (Result<[WooShippingOriginAddress], Error>) -> Void) {
remote.loadOriginAddresses(siteID: siteID, completion: completion)
remote.loadOriginAddresses(siteID: siteID, completion: { [weak self] result in
guard let self else { return }
switch result {
case .success(let addresses):
upsertOriginAddressesInBackground(siteID: siteID, originAddresses: addresses) {
completion(.success(addresses))
}
case .failure(let error):
completion(.failure(error))
}
})
}

func refundShippingLabel(shippingLabel: ShippingLabel,
Expand Down Expand Up @@ -656,6 +666,29 @@ private extension WooShippingStore {
storageSavedPackage.package = predefinedPackage
}

/// Updates the specified origin addresses with the given refund *in a background thread*.
/// `onCompletion` will be called on the main thread!
func upsertOriginAddressesInBackground(siteID: Int64,
originAddresses: [WooShippingOriginAddress],
onCompletion: @escaping () -> Void) {
storageManager.performAndSave({ storage in
let storedOriginAddresses = storage.loadAllOriginAddresses(siteID: siteID)
for address in originAddresses {
let storageAddress = storedOriginAddresses.first(where: { $0.id == address.id }) ??
storage.insertNewObject(ofType: Storage.WooShippingOriginAddress.self)
storageAddress.update(with: address)
}

// Now, remove any objects that exist in storage but not in `originAddresses`
let addressIDs = originAddresses.map(\.id)
storedOriginAddresses.filter {
!addressIDs.contains($0.id)
}.forEach {
storage.deleteObject($0)
}
}, completion: onCompletion, on: .main)
}

/// Updates the specified shipping label with the given refund *in a background thread*.
/// `onCompletion` will be called on the main thread!
func upsertShippingLabelRefundInBackground(shippingLabel: ShippingLabel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1084,7 +1084,8 @@ private extension WooShippingRemoteTests {
}

func sampleOriginAddress() -> WooShippingOriginAddress {
WooShippingOriginAddress(id: "store_details",
WooShippingOriginAddress(siteID: sampleSiteID,
id: "store_details",
company: "Superlative Centaur",
address1: "60 29TH ST PMB 343",
address2: "",
Expand Down
33 changes: 33 additions & 0 deletions Modules/Tests/StorageTests/CoreData/MigrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3493,6 +3493,31 @@ final class MigrationTests: XCTestCase {

XCTAssertEqual(migratedOrder.value(forKey: "shipments") as? Set<NSManagedObject>, [shipment])
}

func test_migrating_from_123_to_124_enables_creating_new_WooShippingOriginAddress_entity() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 123")
let sourceContext = sourceContainer.viewContext

try sourceContext.save()

// Confidence Check. WooShippingOriginAddress should not exist in Model 123
XCTAssertNil(NSEntityDescription.entity(forEntityName: "WooShippingOriginAddress", in: sourceContext))

// When
let targetContainer = try migrate(sourceContainer, to: "Model 124")
let targetContext = targetContainer.viewContext

// Then
XCTAssertEqual(try targetContext.count(entityName: "WooShippingOriginAddress"), 0)

let address = insertWooShippingOriginAddress(to: targetContext)
XCTAssertNoThrow(try targetContext.save())

XCTAssertEqual(try targetContext.count(entityName: "WooShippingOriginAddress"), 1)
let insertedAddress = try XCTUnwrap(targetContext.firstObject(ofType: WooShippingOriginAddress.self))
XCTAssertEqual(insertedAddress, address)
}
}

// MARK: - Persistent Store Setup and Migrations
Expand Down Expand Up @@ -4409,4 +4434,12 @@ private extension MigrationTests {
"subItems": ["sub_1", "sub_2"]
])
}

@discardableResult
func insertWooShippingOriginAddress(to context: NSManagedObjectContext) -> NSManagedObject {
context.insert(entityName: "WooShippingOriginAddress", properties: [
"siteID": 1,
"id": "test-address"
])
}
}
Loading