diff --git a/Networking/Networking/Model/Address.swift b/Networking/Networking/Model/Address.swift index f90b0a405c4..679d1df0fc1 100644 --- a/Networking/Networking/Model/Address.swift +++ b/Networking/Networking/Model/Address.swift @@ -15,6 +15,22 @@ public struct Address: Decodable { public let country: String public let phone: String? public let email: String? + + /// Designated Initializer. + /// + public init(firstName: String, lastName: String, company: String?, address1: String, address2: String?, city: String, state: String, postcode: String, country: String, phone: String?, email: String?) { + self.firstName = firstName + self.lastName = lastName + self.company = company + self.address1 = address1 + self.address2 = address2 + self.city = city + self.state = state + self.postcode = postcode + self.country = country + self.phone = phone + self.email = email + } } diff --git a/Networking/Networking/Model/Order.swift b/Networking/Networking/Model/Order.swift index b7e986caa9b..343d3d03de9 100644 --- a/Networking/Networking/Model/Order.swift +++ b/Networking/Networking/Model/Order.swift @@ -32,7 +32,7 @@ public struct Order: Decodable { /// Order struct initializer. /// - init(orderID: Int, parentID: Int, customerID: Int, number: String, status: OrderStatus, currency: String, customerNote: String?, dateCreated: Date, dateModified: Date, datePaid: Date?, discountTotal: String, discountTax: String, shippingTotal: String, shippingTax: String, total: String, totalTax: String, paymentMethodTitle: String, items: [OrderItem], billingAddress: Address, shippingAddress: Address, coupons: [OrderCouponLine]) { + public init(orderID: Int, parentID: Int, customerID: Int, number: String, status: OrderStatus, currency: String, customerNote: String?, dateCreated: Date, dateModified: Date, datePaid: Date?, discountTotal: String, discountTax: String, shippingTotal: String, shippingTax: String, total: String, totalTax: String, paymentMethodTitle: String, items: [OrderItem], billingAddress: Address, shippingAddress: Address, coupons: [OrderCouponLine]) { self.orderID = orderID self.parentID = parentID diff --git a/Networking/Networking/Model/OrderCouponLine.swift b/Networking/Networking/Model/OrderCouponLine.swift index cb4b486a168..ee6fb37b5ec 100644 --- a/Networking/Networking/Model/OrderCouponLine.swift +++ b/Networking/Networking/Model/OrderCouponLine.swift @@ -8,6 +8,15 @@ public struct OrderCouponLine: Decodable { public let code: String public let discount: String public let discountTax: String + + /// OrderCouponLine struct initializer. + /// + public init(couponID: Int, code: String, discount: String, discountTax: String) { + self.couponID = couponID + self.code = code + self.discount = discount + self.discountTax = discountTax + } } diff --git a/Networking/Networking/Model/OrderItem.swift b/Networking/Networking/Model/OrderItem.swift index f5688d0119b..a31ef414ea3 100644 --- a/Networking/Networking/Model/OrderItem.swift +++ b/Networking/Networking/Model/OrderItem.swift @@ -15,6 +15,22 @@ public struct OrderItem: Decodable { public let total: String public let totalTax: String public let variationID: Int + + /// OrderItem struct initializer. + /// + public init(itemID: Int, name: String, productID: Int, quantity: Int, sku: String, subtotal: String, subtotalTax: String, taxClass: String, total: String, totalTax: String, variationID: Int) { + self.itemID = itemID + self.name = name + self.productID = productID + self.quantity = quantity + self.sku = sku + self.subtotal = subtotal + self.subtotalTax = subtotalTax + self.taxClass = taxClass + self.total = total + self.totalTax = totalTax + self.variationID = variationID + } } diff --git a/Networking/Networking/Model/OrderNote.swift b/Networking/Networking/Model/OrderNote.swift index 9594dbca06e..da83949e658 100644 --- a/Networking/Networking/Model/OrderNote.swift +++ b/Networking/Networking/Model/OrderNote.swift @@ -9,7 +9,7 @@ public struct OrderNote: Decodable { public let note: String public let isCustomerNote: Bool - /// Order struct initializer. + /// OrderNote struct initializer. /// init(noteId: Int, dateCreated: Date, note: String, isCustomerNote: Bool) { self.noteId = noteId diff --git a/Storage/Storage.xcodeproj/project.pbxproj b/Storage/Storage.xcodeproj/project.pbxproj index 2b8727038ae..7d7bd5a804a 100644 --- a/Storage/Storage.xcodeproj/project.pbxproj +++ b/Storage/Storage.xcodeproj/project.pbxproj @@ -9,6 +9,12 @@ /* Begin PBXBuildFile section */ 68BC97FB41770051C287D1A8 /* Pods_StorageTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 47556EE256120BEE49FF5FD3 /* Pods_StorageTests.framework */; }; 7028A41485A08AC748206184 /* Pods_Storage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DF3D3B298350F68191CD1DAD /* Pods_Storage.framework */; }; + 7426A04720F68F27002A4E07 /* Order+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7426A04320F68F27002A4E07 /* Order+CoreDataClass.swift */; }; + 7426A04820F68F27002A4E07 /* Order+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7426A04420F68F27002A4E07 /* Order+CoreDataProperties.swift */; }; + 7426A05020F69D00002A4E07 /* OrderCoupon+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7426A04E20F69D00002A4E07 /* OrderCoupon+CoreDataClass.swift */; }; + 7426A05120F69D00002A4E07 /* OrderCoupon+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7426A04F20F69D00002A4E07 /* OrderCoupon+CoreDataProperties.swift */; }; + 7426A05420F69DA4002A4E07 /* OrderItem+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7426A05220F69DA4002A4E07 /* OrderItem+CoreDataClass.swift */; }; + 7426A05520F69DA4002A4E07 /* OrderItem+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7426A05320F69DA4002A4E07 /* OrderItem+CoreDataProperties.swift */; }; B505255420EE6914008090F5 /* StorageType+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505255320EE6914008090F5 /* StorageType+Extensions.swift */; }; B505F6DA20BEEA3200BB1B69 /* Account+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505F6D820BEEA3100BB1B69 /* Account+CoreDataProperties.swift */; }; B505F6DB20BEEA3200BB1B69 /* Account+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505F6D920BEEA3200BB1B69 /* Account+CoreDataClass.swift */; }; @@ -46,6 +52,12 @@ /* Begin PBXFileReference section */ 47556EE256120BEE49FF5FD3 /* Pods_StorageTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_StorageTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 5D12CAE2D0EA6AB66F162FF9 /* Pods-StorageTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StorageTests.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-StorageTests/Pods-StorageTests.debug.xcconfig"; sourceTree = ""; }; + 7426A04320F68F27002A4E07 /* Order+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Order+CoreDataClass.swift"; sourceTree = ""; }; + 7426A04420F68F27002A4E07 /* Order+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Order+CoreDataProperties.swift"; sourceTree = ""; }; + 7426A04E20F69D00002A4E07 /* OrderCoupon+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrderCoupon+CoreDataClass.swift"; sourceTree = ""; }; + 7426A04F20F69D00002A4E07 /* OrderCoupon+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrderCoupon+CoreDataProperties.swift"; sourceTree = ""; }; + 7426A05220F69DA4002A4E07 /* OrderItem+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrderItem+CoreDataClass.swift"; sourceTree = ""; }; + 7426A05320F69DA4002A4E07 /* OrderItem+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrderItem+CoreDataProperties.swift"; sourceTree = ""; }; 7C81935EDD982072BBDCC837 /* Pods-Storage.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Storage.release.xcconfig"; path = "../Pods/Target Support Files/Pods-Storage/Pods-Storage.release.xcconfig"; sourceTree = ""; }; A3821B262583F14863740A37 /* Pods-Storage.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Storage.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-Storage/Pods-Storage.debug.xcconfig"; sourceTree = ""; }; B505255320EE6914008090F5 /* StorageType+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "StorageType+Extensions.swift"; sourceTree = ""; }; @@ -226,6 +238,12 @@ B505F6D820BEEA3100BB1B69 /* Account+CoreDataProperties.swift */, B5B914C320EFF03500F2F832 /* Site+CoreDataClass.swift */, B5B914C420EFF03500F2F832 /* Site+CoreDataProperties.swift */, + 7426A04320F68F27002A4E07 /* Order+CoreDataClass.swift */, + 7426A04420F68F27002A4E07 /* Order+CoreDataProperties.swift */, + 7426A04E20F69D00002A4E07 /* OrderCoupon+CoreDataClass.swift */, + 7426A04F20F69D00002A4E07 /* OrderCoupon+CoreDataProperties.swift */, + 7426A05220F69DA4002A4E07 /* OrderItem+CoreDataClass.swift */, + 7426A05320F69DA4002A4E07 /* OrderItem+CoreDataProperties.swift */, ); path = Model; sourceTree = ""; @@ -411,16 +429,22 @@ files = ( B5B914C520EFF03500F2F832 /* Site+CoreDataClass.swift in Sources */, B54CA5BB20A4BD2800F38CD1 /* NSManagedObject+Object.swift in Sources */, + 7426A05120F69D00002A4E07 /* OrderCoupon+CoreDataProperties.swift in Sources */, B5B914C620EFF03500F2F832 /* Site+CoreDataProperties.swift in Sources */, + 7426A04820F68F27002A4E07 /* Order+CoreDataProperties.swift in Sources */, B505F6DB20BEEA3200BB1B69 /* Account+CoreDataClass.swift in Sources */, + 7426A05520F69DA4002A4E07 /* OrderItem+CoreDataProperties.swift in Sources */, + 7426A04720F68F27002A4E07 /* Order+CoreDataClass.swift in Sources */, B54CA5BD20A4BD3B00F38CD1 /* NSManagedObjectContext+Storage.swift in Sources */, B59E11DA20A9D00C004121A4 /* WooCommerce.xcdatamodeld in Sources */, B52B0F7920AA287C00477698 /* StorageManagerType.swift in Sources */, + 7426A05420F69DA4002A4E07 /* OrderItem+CoreDataClass.swift in Sources */, B505F6E020BEEA8100BB1B69 /* StorageType.swift in Sources */, B54CA5C920A4C17800F38CD1 /* NSObject+Storage.swift in Sources */, B505F6DA20BEEA3200BB1B69 /* Account+CoreDataProperties.swift in Sources */, B505255420EE6914008090F5 /* StorageType+Extensions.swift in Sources */, B52B0F7B20AA28A800477698 /* Object.swift in Sources */, + 7426A05020F69D00002A4E07 /* OrderCoupon+CoreDataClass.swift in Sources */, B505F6DE20BEEA4F00BB1B69 /* CoreDataManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Storage/Storage/Model/Order+CoreDataClass.swift b/Storage/Storage/Model/Order+CoreDataClass.swift new file mode 100644 index 00000000000..53fe0dfa3a9 --- /dev/null +++ b/Storage/Storage/Model/Order+CoreDataClass.swift @@ -0,0 +1,8 @@ +import Foundation +import CoreData + + +@objc(Order) +public class Order: NSManagedObject { + +} diff --git a/Storage/Storage/Model/Order+CoreDataProperties.swift b/Storage/Storage/Model/Order+CoreDataProperties.swift new file mode 100644 index 00000000000..63fe9f5b84c --- /dev/null +++ b/Storage/Storage/Model/Order+CoreDataProperties.swift @@ -0,0 +1,86 @@ +import Foundation +import CoreData + + +extension Order { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "Order") + } + + @NSManaged public var currency: String? + @NSManaged public var customerID: Int64 + @NSManaged public var customerNote: String? + @NSManaged public var dateCreated: Date? + @NSManaged public var dateModified: Date? + @NSManaged public var datePaid: Date? + @NSManaged public var discountTax: String? + @NSManaged public var discountTotal: String? + @NSManaged public var number: String? + @NSManaged public var orderID: Int64 + @NSManaged public var parentID: Int64 + @NSManaged public var paymentMethodTitle: String? + @NSManaged public var shippingTax: String? + @NSManaged public var shippingTotal: String? + @NSManaged public var status: String + @NSManaged public var total: String? + @NSManaged public var totalTax: String? + @NSManaged public var billingFirstName: String? + @NSManaged public var billingLastName: String? + @NSManaged public var billingCountry: String? + @NSManaged public var billingPostcode: String? + @NSManaged public var billingState: String? + @NSManaged public var billingCity: String? + @NSManaged public var billingAddress2: String? + @NSManaged public var billingAddress1: String? + @NSManaged public var billingCompany: String? + @NSManaged public var billingPhone: String? + @NSManaged public var billingEmail: String? + @NSManaged public var shippingFirstName: String? + @NSManaged public var shippingLastName: String? + @NSManaged public var shippingCompany: String? + @NSManaged public var shippingAddress1: String? + @NSManaged public var shippingAddress2: String? + @NSManaged public var shippingCity: String? + @NSManaged public var shippingPostcode: String? + @NSManaged public var shippingCountry: String? + @NSManaged public var shippingPhone: String? + @NSManaged public var shippingEmail: String? + @NSManaged public var shippingState: String? + @NSManaged public var coupons: Set? + @NSManaged public var items: Set? +} + +// MARK: Generated accessors for coupons +extension Order { + + @objc(addCouponsObject:) + @NSManaged public func addToCoupons(_ value: OrderCoupon) + + @objc(removeCouponsObject:) + @NSManaged public func removeFromCoupons(_ value: OrderCoupon) + + @objc(addCoupons:) + @NSManaged public func addToCoupons(_ values: NSSet) + + @objc(removeCoupons:) + @NSManaged public func removeFromCoupons(_ values: NSSet) + +} + +// MARK: Generated accessors for items +extension Order { + + @objc(addItemsObject:) + @NSManaged public func addToItems(_ value: OrderItem) + + @objc(removeItemsObject:) + @NSManaged public func removeFromItems(_ value: OrderItem) + + @objc(addItems:) + @NSManaged public func addToItems(_ values: NSSet) + + @objc(removeItems:) + @NSManaged public func removeFromItems(_ values: NSSet) + +} diff --git a/Storage/Storage/Model/OrderCoupon+CoreDataClass.swift b/Storage/Storage/Model/OrderCoupon+CoreDataClass.swift new file mode 100644 index 00000000000..67893e2b9d7 --- /dev/null +++ b/Storage/Storage/Model/OrderCoupon+CoreDataClass.swift @@ -0,0 +1,8 @@ +import Foundation +import CoreData + + +@objc(OrderCoupon) +public class OrderCoupon: NSManagedObject { + +} diff --git a/Storage/Storage/Model/OrderCoupon+CoreDataProperties.swift b/Storage/Storage/Model/OrderCoupon+CoreDataProperties.swift new file mode 100644 index 00000000000..4313f7bc191 --- /dev/null +++ b/Storage/Storage/Model/OrderCoupon+CoreDataProperties.swift @@ -0,0 +1,16 @@ +import Foundation +import CoreData + + +extension OrderCoupon { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "OrderCoupon") + } + + @NSManaged public var couponID: Int64 + @NSManaged public var code: String? + @NSManaged public var discount: String? + @NSManaged public var discountTax: String? + @NSManaged public var order: Order +} diff --git a/Storage/Storage/Model/OrderItem+CoreDataClass.swift b/Storage/Storage/Model/OrderItem+CoreDataClass.swift new file mode 100644 index 00000000000..6add1c83c81 --- /dev/null +++ b/Storage/Storage/Model/OrderItem+CoreDataClass.swift @@ -0,0 +1,8 @@ +import Foundation +import CoreData + + +@objc(OrderItem) +public class OrderItem: NSManagedObject { + +} diff --git a/Storage/Storage/Model/OrderItem+CoreDataProperties.swift b/Storage/Storage/Model/OrderItem+CoreDataProperties.swift new file mode 100644 index 00000000000..348cfe0a6cb --- /dev/null +++ b/Storage/Storage/Model/OrderItem+CoreDataProperties.swift @@ -0,0 +1,23 @@ +import Foundation +import CoreData + + +extension OrderItem { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "OrderItem") + } + + @NSManaged public var itemID: Int64 + @NSManaged public var name: String? + @NSManaged public var quantity: Int16 + @NSManaged public var productID: Int64 + @NSManaged public var sku: String? + @NSManaged public var subtotal: String? + @NSManaged public var subtotalTax: String? + @NSManaged public var taxClass: String? + @NSManaged public var total: String? + @NSManaged public var totalTax: String? + @NSManaged public var variationID: Int64 + @NSManaged public var order: Order +} diff --git a/Storage/Storage/Model/WooCommerce.xcdatamodeld/Model.xcdatamodel/contents b/Storage/Storage/Model/WooCommerce.xcdatamodeld/Model.xcdatamodel/contents index 0adf70177a1..21aff67a592 100644 --- a/Storage/Storage/Model/WooCommerce.xcdatamodeld/Model.xcdatamodel/contents +++ b/Storage/Storage/Model/WooCommerce.xcdatamodeld/Model.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -7,6 +7,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -15,7 +79,10 @@ - - + + + + + \ No newline at end of file diff --git a/Storage/Storage/Tools/StorageType+Extensions.swift b/Storage/Storage/Tools/StorageType+Extensions.swift index 2d4a7f6e64c..10bf15b0b47 100644 --- a/Storage/Storage/Tools/StorageType+Extensions.swift +++ b/Storage/Storage/Tools/StorageType+Extensions.swift @@ -18,4 +18,25 @@ public extension StorageType { let predicate = NSPredicate(format: "siteID = %ld", siteID) return firstObject(ofType: Site.self, matching: predicate) } + + /// Retrieves the Stored Order. + /// + public func loadOrder(orderID: Int) -> Order? { + let predicate = NSPredicate(format: "orderID = %ld", orderID) + return firstObject(ofType: Order.self, matching: predicate) + } + + /// Retrieves the Stored Order Item. + /// + public func loadOrderItem(itemID: Int) -> OrderItem? { + let predicate = NSPredicate(format: "itemID = %ld", itemID) + return firstObject(ofType: OrderItem.self, matching: predicate) + } + + /// Retrieves the Stored Order Coupon. + /// + public func loadCouponItem(couponID: Int) -> OrderCoupon? { + let predicate = NSPredicate(format: "couponID = %ld", couponID) + return firstObject(ofType: OrderCoupon.self, matching: predicate) + } } diff --git a/Yosemite/Yosemite.xcodeproj/project.pbxproj b/Yosemite/Yosemite.xcodeproj/project.pbxproj index 0ae8acb7d8a..b98f8765436 100644 --- a/Yosemite/Yosemite.xcodeproj/project.pbxproj +++ b/Yosemite/Yosemite.xcodeproj/project.pbxproj @@ -10,6 +10,8 @@ 0E67B79585034C4DD75C8117 /* Pods_Yosemite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C25501C7F936D2FD32FAF3F4 /* Pods_Yosemite.framework */; }; 36941EA7B9242CAB1FF828BC /* Pods_YosemiteTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 991BBCE6E4A92F0A028885D8 /* Pods_YosemiteTests.framework */; }; 7424B49420EAD37C00CC62F6 /* order.json in Resources */ = {isa = PBXBuildFile; fileRef = 7424B49320EAD37C00CC62F6 /* order.json */; }; + 74685D4E20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74685D4D20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift */; }; + 74685D5020F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74685D4F20F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift */; }; 7499936420EFBC1B00CF01CD /* OrderNoteAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936320EFBC1A00CF01CD /* OrderNoteAction.swift */; }; 7499936620EFBC7200CF01CD /* OrderNoteStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936520EFBC7200CF01CD /* OrderNoteStore.swift */; }; 7499936820EFC0ED00CF01CD /* OrderNoteStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936720EFC0ED00CF01CD /* OrderNoteStoreTests.swift */; }; @@ -18,6 +20,7 @@ 74A7688E20D45ED400F9D437 /* OrderStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A7688D20D45ED400F9D437 /* OrderStoreTests.swift */; }; 74A7689020D45F9300F9D437 /* OrderAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A7688F20D45F9300F9D437 /* OrderAction.swift */; }; 74A7689220D47F9E00F9D437 /* orders.json in Resources */ = {isa = PBXBuildFile; fileRef = 74A7689120D47F9E00F9D437 /* orders.json */; }; + 74D7F29B20F6A7FB0058B2F0 /* Order+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74D7F29A20F6A7FB0058B2F0 /* Order+ReadOnlyConvertible.swift */; }; B505254C20EE6491008090F5 /* Site+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505254B20EE6491008090F5 /* Site+ReadOnlyConvertible.swift */; }; B53D89E520E6C22B00F90866 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53D89E420E6C22B00F90866 /* Model.swift */; }; B546CCF22093636A007CDA5F /* Yosemite.h in Headers */ = {isa = PBXBuildFile; fileRef = B546CCF12093636A007CDA5F /* Yosemite.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -60,6 +63,8 @@ /* Begin PBXFileReference section */ 7424B49320EAD37C00CC62F6 /* order.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = order.json; sourceTree = ""; }; 745D21C120D8043A00BBE7C3 /* generic_error.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = generic_error.json; sourceTree = ""; }; + 74685D4D20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrderItem+ReadOnlyConvertible.swift"; sourceTree = ""; }; + 74685D4F20F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrderCoupon+ReadOnlyConvertible.swift"; sourceTree = ""; }; 7499936320EFBC1A00CF01CD /* OrderNoteAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNoteAction.swift; sourceTree = ""; }; 7499936520EFBC7200CF01CD /* OrderNoteStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNoteStore.swift; sourceTree = ""; }; 7499936720EFC0ED00CF01CD /* OrderNoteStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderNoteStoreTests.swift; sourceTree = ""; }; @@ -68,6 +73,7 @@ 74A7688D20D45ED400F9D437 /* OrderStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStoreTests.swift; sourceTree = ""; }; 74A7688F20D45F9300F9D437 /* OrderAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderAction.swift; sourceTree = ""; }; 74A7689120D47F9E00F9D437 /* orders.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = orders.json; sourceTree = ""; }; + 74D7F29A20F6A7FB0058B2F0 /* Order+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Order+ReadOnlyConvertible.swift"; sourceTree = ""; }; 79402E7AD394EEB60C39A4B8 /* Pods-YosemiteTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-YosemiteTests.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-YosemiteTests/Pods-YosemiteTests.debug.xcconfig"; sourceTree = ""; }; 7F47E19203B04CF6BB5EA659 /* Pods-Yosemite.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Yosemite.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-Yosemite/Pods-Yosemite.debug.xcconfig"; sourceTree = ""; }; 93D120F57D5D14A10B3AFFFD /* Pods-Yosemite.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Yosemite.release.xcconfig"; path = "../Pods/Target Support Files/Pods-Yosemite/Pods-Yosemite.release.xcconfig"; sourceTree = ""; }; @@ -134,6 +140,9 @@ children = ( B53D89E420E6C22B00F90866 /* Model.swift */, B5EED1A720F4F3CF00652449 /* Account+ReadOnlyConvertible.swift */, + 74D7F29A20F6A7FB0058B2F0 /* Order+ReadOnlyConvertible.swift */, + 74685D4F20F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift */, + 74685D4D20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift */, B505254B20EE6491008090F5 /* Site+ReadOnlyConvertible.swift */, ); path = Model; @@ -493,6 +502,7 @@ buildActionMask = 2147483647; files = ( B5C9DE152087FF0E006B910A /* Dispatcher.swift in Sources */, + 74685D4E20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift in Sources */, B5DC3CB120D1B8720063AC41 /* AccountAction.swift in Sources */, B5BC736520D1A98500B5B6FA /* AccountStore.swift in Sources */, B56C1EC220EAE2E500D749F9 /* ReadOnlyConvertible.swift in Sources */, @@ -500,9 +510,11 @@ B5B5C797208E49B600642956 /* Action+Internal.swift in Sources */, 74A7688C20D45EBA00F9D437 /* OrderStore.swift in Sources */, B53D89E520E6C22B00F90866 /* Model.swift in Sources */, + 74685D5020F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift in Sources */, 74A7689020D45F9300F9D437 /* OrderAction.swift in Sources */, 7499936620EFBC7200CF01CD /* OrderNoteStore.swift in Sources */, B5C9DE182087FF0E006B910A /* Assert.swift in Sources */, + 74D7F29B20F6A7FB0058B2F0 /* Order+ReadOnlyConvertible.swift in Sources */, B5C9DE162087FF0E006B910A /* Store.swift in Sources */, B56C1EBE20EABD2B00D749F9 /* ResultsController.swift in Sources */, B5EED1A820F4F3CF00652449 /* Account+ReadOnlyConvertible.swift in Sources */, diff --git a/Yosemite/Yosemite/Model/Order+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/Order+ReadOnlyConvertible.swift new file mode 100644 index 00000000000..19182db66f7 --- /dev/null +++ b/Yosemite/Yosemite/Model/Order+ReadOnlyConvertible.swift @@ -0,0 +1,116 @@ +import Foundation +import Storage + + +// MARK: - Storage.Order: ReadOnlyConvertible +// +extension Storage.Order: ReadOnlyConvertible { + + /// Updates the Storage.Order with the ReadOnly. + /// + public func update(with order: Yosemite.Order) { + orderID = Int64(order.orderID) + parentID = Int64(order.parentID) + customerID = Int64(order.customerID) + number = order.number + status = order.status.rawValue + currency = order.currency + customerNote = order.customerNote + + dateCreated = order.dateCreated + dateModified = order.dateModified + datePaid = order.datePaid + + discountTotal = order.discountTotal + discountTax = order.discountTax + shippingTotal = order.shippingTotal + shippingTax = order.shippingTax + total = order.total + totalTax = order.totalTax + paymentMethodTitle = order.paymentMethodTitle + + billingFirstName = order.billingAddress.firstName + billingLastName = order.billingAddress.lastName + billingCompany = order.billingAddress.company + billingAddress1 = order.billingAddress.address1 + billingAddress2 = order.billingAddress.address2 + billingCity = order.billingAddress.city + billingState = order.billingAddress.state + billingPostcode = order.billingAddress.postcode + billingCountry = order.billingAddress.country + billingPhone = order.billingAddress.phone + billingEmail = order.billingAddress.email + + shippingFirstName = order.shippingAddress.firstName + shippingLastName = order.shippingAddress.lastName + shippingCompany = order.shippingAddress.company + shippingAddress1 = order.shippingAddress.address1 + shippingAddress2 = order.shippingAddress.address2 + shippingCity = order.shippingAddress.city + shippingState = order.shippingAddress.state + shippingPostcode = order.shippingAddress.postcode + shippingCountry = order.shippingAddress.country + shippingPhone = order.shippingAddress.phone + shippingEmail = order.shippingAddress.email + } + + /// Returns a ReadOnly version of the receiver. + /// + public func toReadOnly() -> Yosemite.Order { + let orderItems = items?.map { $0.toReadOnly() } ?? [Yosemite.OrderItem]() + let orderCoupons = coupons?.map { $0.toReadOnly() } ?? [Yosemite.OrderCouponLine]() + + return Order(orderID: Int(orderID), + parentID: Int(parentID), + customerID: Int(customerID), + number: number ?? "", + status: OrderStatus(rawValue: status), + currency: currency ?? "", + customerNote: customerNote ?? "", + dateCreated: dateCreated ?? Date(), + dateModified: dateModified ?? Date(), + datePaid: datePaid ?? Date(), + discountTotal: discountTotal ?? "", + discountTax: discountTax ?? "", + shippingTotal: shippingTotal ?? "", + shippingTax: shippingTax ?? "", + total: total ?? "", + totalTax: totalTax ?? "", + paymentMethodTitle: paymentMethodTitle ?? "", + items: orderItems, + billingAddress: createReadOnlyBillingAddress(), + shippingAddress: createReadOnlyShippingAddress(), + coupons: orderCoupons) + } + + + // MARK: - Private Helpers + + private func createReadOnlyBillingAddress() -> Yosemite.Address { + return Address(firstName: billingFirstName ?? "", + lastName: billingLastName ?? "", + company: billingCompany ?? "", + address1: billingAddress1 ?? "", + address2: billingAddress2 ?? "", + city: billingCity ?? "", + state: billingState ?? "", + postcode: billingPostcode ?? "", + country: billingCountry ?? "", + phone: billingPhone ?? "", + email: billingEmail ?? "") + } + + private func createReadOnlyShippingAddress() -> Yosemite.Address { + return Address(firstName: shippingFirstName ?? "", + lastName: shippingLastName ?? "", + company: shippingCompany ?? "", + address1: shippingAddress1 ?? "", + address2: shippingAddress2 ?? "", + city: shippingCity ?? "", + state: shippingState ?? "", + postcode: shippingPostcode ?? "", + country: shippingCountry ?? "", + phone: shippingPhone ?? "", + email: shippingEmail ?? "") + } +} diff --git a/Yosemite/Yosemite/Model/OrderCoupon+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/OrderCoupon+ReadOnlyConvertible.swift new file mode 100644 index 00000000000..8e513589622 --- /dev/null +++ b/Yosemite/Yosemite/Model/OrderCoupon+ReadOnlyConvertible.swift @@ -0,0 +1,26 @@ +import Foundation +import Storage + + +// MARK: - Storage.OrderCoupon: ReadOnlyConvertible +// +extension Storage.OrderCoupon: ReadOnlyConvertible { + + /// Updates the Storage.OrderCoupon with the ReadOnly. + /// + public func update(with orderCoupon: Yosemite.OrderCouponLine) { + couponID = Int64(orderCoupon.couponID) + code = orderCoupon.code + discount = orderCoupon.discount + discountTax = orderCoupon.discountTax + } + + /// Returns a ReadOnly version of the receiver. + /// + public func toReadOnly() -> Yosemite.OrderCouponLine { + return OrderCouponLine(couponID: Int(couponID), + code: code ?? "", + discount: discount ?? "", + discountTax: discountTax ?? "") + } +} diff --git a/Yosemite/Yosemite/Model/OrderItem+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/OrderItem+ReadOnlyConvertible.swift new file mode 100644 index 00000000000..e25a65b2149 --- /dev/null +++ b/Yosemite/Yosemite/Model/OrderItem+ReadOnlyConvertible.swift @@ -0,0 +1,40 @@ +import Foundation +import Storage + + +// MARK: - Storage.OrderItem: ReadOnlyConvertible +// +extension Storage.OrderItem: ReadOnlyConvertible { + + /// Updates the Storage.OrderItem with the ReadOnly. + /// + public func update(with orderItem: Yosemite.OrderItem) { + itemID = Int64(orderItem.itemID) + name = orderItem.name + quantity = Int16(orderItem.quantity) + productID = Int64(orderItem.productID) + sku = orderItem.sku + subtotal = orderItem.subtotal + subtotalTax = orderItem.subtotalTax + taxClass = orderItem.taxClass + total = orderItem.total + totalTax = orderItem.totalTax + variationID = Int64(orderItem.variationID) + } + + /// Returns a ReadOnly version of the receiver. + /// + public func toReadOnly() -> Yosemite.OrderItem { + return OrderItem(itemID: Int(itemID), + name: name ?? "", + productID: Int(productID), + quantity: Int(quantity), + sku: sku ?? "", + subtotal: subtotal ?? "", + subtotalTax: subtotalTax ?? "", + taxClass: taxClass ?? "", + total: total ?? "", + totalTax: totalTax ?? "", + variationID: Int(variationID)) + } +} diff --git a/Yosemite/Yosemite/Stores/OrderStore.swift b/Yosemite/Yosemite/Stores/OrderStore.swift index 9b32f5fe4d3..8cf9f829b24 100644 --- a/Yosemite/Yosemite/Stores/OrderStore.swift +++ b/Yosemite/Yosemite/Stores/OrderStore.swift @@ -1,5 +1,6 @@ import Foundation import Networking +import Storage // MARK: - OrderStore @@ -32,19 +33,20 @@ public class OrderStore: Store { // MARK: - Services! // -extension OrderStore { +private extension OrderStore { /// Retrieves the orders associated with a given Site ID (if any!). /// func retrieveOrders(siteId: Int, onCompletion: @escaping ([Order]?, Error?) -> Void) { let remote = OrdersRemote(network: network) - remote.loadAllOrders(for: siteId) { (orders, error) in + remote.loadAllOrders(for: siteId) { [weak self] (orders, error) in guard let orders = orders else { onCompletion(nil, error) return } + self?.upsertStoredOrders(readOnlyOrders: orders) onCompletion(orders, nil) } } @@ -54,13 +56,105 @@ extension OrderStore { func retrieveOrder(siteId: Int, orderId: Int, onCompletion: @escaping (Order?, Error?) -> Void) { let remote = OrdersRemote(network: network) - remote.loadOrder(for: siteId, orderID: orderId) { (order, error) in + remote.loadOrder(for: siteId, orderID: orderId) { [weak self] (order, error) in guard let order = order else { onCompletion(nil, error) return } + self?.upsertStoredOrder(readOnlyOrder: order) onCompletion(order, nil) } } } + + +// MARK: - Persistance +// +private extension OrderStore { + + /// Updates (OR Inserts) the specified ReadOnly Order Entity into the Storage Layer. + /// + func upsertStoredOrder(readOnlyOrder: Networking.Order) { + assert(Thread.isMainThread) + + let storage = storageManager.viewStorage + let storageOrder = storage.loadOrder(orderID: readOnlyOrder.orderID) ?? storage.insertNewObject(ofType: Storage.Order.self) + storageOrder.update(with: readOnlyOrder) + handleOrderItems(readOnlyOrder, storageOrder, storage) + handleOrderCoupons(readOnlyOrder, storageOrder, storage) + storage.saveIfNeeded() + } + + /// Updates (OR Inserts) the specified ReadOnly Order Entities into the Storage Layer. + /// + func upsertStoredOrders(readOnlyOrders: [Networking.Order]) { + assert(Thread.isMainThread) + + let storage = storageManager.viewStorage + for readOnlyOrder in readOnlyOrders { + let storageOrder = storage.loadOrder(orderID: readOnlyOrder.orderID) ?? storage.insertNewObject(ofType: Storage.Order.self) + storageOrder.update(with: readOnlyOrder) + handleOrderItems(readOnlyOrder, storageOrder, storage) + handleOrderCoupons(readOnlyOrder, storageOrder, storage) + } + + storage.saveIfNeeded() + } + + /// Updates, inserts, or prunes the provided StorageOrder's items using the provided read-only Order's items + /// + func handleOrderItems(_ readOnlyOrder: Networking.Order, _ storageOrder: Storage.Order, _ storage: StorageType) { + guard !readOnlyOrder.items.isEmpty else { + // No items in the read-only order, so remove all the items in Storage.Order + storageOrder.items?.forEach { storageOrder.removeFromItems($0) } + return + } + + // Upsert the items from the read-only order + for readOnlyItem in readOnlyOrder.items { + if let existingStorageItem = storage.loadOrderItem(itemID: readOnlyItem.itemID) { + existingStorageItem.update(with: readOnlyItem) + } else { + let newStorageItem = storage.insertNewObject(ofType: Storage.OrderItem.self) + newStorageItem.update(with: readOnlyItem) + storageOrder.addToItems(newStorageItem) + } + } + + // Now, remove any objects that exist in storageOrder.items but not in readOnlyOrder.items + storageOrder.items?.forEach({ storageItem in + if readOnlyOrder.items.first(where: { $0.itemID == storageItem.itemID } ) == nil { + storageOrder.removeFromItems(storageItem) + } + }) + } + + /// Updates, inserts, or prunes the provided StorageOrder's coupons using the provided read-only Order's coupons + /// + func handleOrderCoupons(_ readOnlyOrder: Networking.Order, _ storageOrder: Storage.Order, _ storage: StorageType) { + guard !readOnlyOrder.coupons.isEmpty else { + // No coupons in the read-only order, so remove all the coupons in Storage.Order + storageOrder.coupons?.forEach { storageOrder.removeFromCoupons($0) } + return + } + + // Upsert the coupons from the read-only order + for readOnlyCoupon in readOnlyOrder.coupons { + if let existingStorageCoupon = storage.loadCouponItem(couponID: readOnlyCoupon.couponID) { + existingStorageCoupon.update(with: readOnlyCoupon) + } else { + let newStorageCoupon = storage.insertNewObject(ofType: Storage.OrderCoupon.self) + newStorageCoupon.update(with: readOnlyCoupon) + storageOrder.addToCoupons(newStorageCoupon) + } + } + + // Now, remove any objects that exist in storageOrder.coupons but not in readOnlyOrder.coupons + storageOrder.coupons?.forEach({ storageCoupon in + if readOnlyOrder.coupons.first(where: { $0.couponID == storageCoupon.couponID } ) == nil { + storageOrder.removeFromCoupons(storageCoupon) + } + }) + } +} diff --git a/Yosemite/YosemiteTests/Stores/OrderStoreTests.swift b/Yosemite/YosemiteTests/Stores/OrderStoreTests.swift index 94bf712fac6..06366695670 100644 --- a/Yosemite/YosemiteTests/Stores/OrderStoreTests.swift +++ b/Yosemite/YosemiteTests/Stores/OrderStoreTests.swift @@ -20,6 +20,12 @@ class OrderStoreTests: XCTestCase { /// private var network: MockupNetwork! + /// Convenience Property: Returns the StorageType associated with the main thread. + /// + private var viewStorage: StorageType { + return storageManager.viewStorage + } + override func setUp() { super.setUp() @@ -28,6 +34,8 @@ class OrderStoreTests: XCTestCase { network = MockupNetwork() } + // MARK: - OrderAction.retrieveOrders + /// Verifies that OrderAction.retrieveOrders returns the expected Orders. /// func testRetrieveOrdersReturnsExpectedFields() { @@ -51,6 +59,53 @@ class OrderStoreTests: XCTestCase { wait(for: [expectation], timeout: Constants.expectationTimeout) } + /// Verifies that `OrderAction.retrieveOrders` effectively persists any retrieved orders. + /// + func testRetrieveOrdersEffectivelyPersistsRetrievedOrders() { + let expectation = self.expectation(description: "Persist order list") + let orderStore = OrderStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + + network.simulateResponse(requestUrlSuffix: "orders", filename: "orders") + XCTAssertEqual(viewStorage.countObjects(ofType: Storage.Order.self), 0) + + let action = OrderAction.retrieveOrders(siteID: 123) { (orders, error) in + XCTAssertEqual(self.viewStorage.countObjects(ofType: Storage.Order.self), 3) + XCTAssertNil(error) + expectation.fulfill() + } + + orderStore.onAction(action) + wait(for: [expectation], timeout: Constants.expectationTimeout) + } + + /// Verifies that `OrderAction.retrieveOrders` effectively persists all of the order fields correctly across all of the related Order objects (items, coupons, etc). + /// + func testRetrieveOrdersEffectivelyPersistsOrderFieldsAndRelatedObjects() { + let expectation = self.expectation(description: "Persist order list") + let orderStore = OrderStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + let remoteOrder = sampleOrder() + + network.simulateResponse(requestUrlSuffix: "orders", filename: "orders") + XCTAssertEqual(viewStorage.countObjects(ofType: Storage.Order.self), 0) + + let action = OrderAction.retrieveOrders(siteID: 123) { (orders, error) in + XCTAssertNotNil(orders) + XCTAssertNil(error) + + let predicate = NSPredicate(format: "orderID = %ld", remoteOrder.orderID) + let storedOrder = self.viewStorage.firstObject(ofType: Storage.Order.self, matching: predicate) + let readOnlyStoredOrder = storedOrder?.toReadOnly() + XCTAssertNotNil(storedOrder) + XCTAssertNotNil(readOnlyStoredOrder) + XCTAssertEqual(readOnlyStoredOrder, remoteOrder) + + expectation.fulfill() + } + + orderStore.onAction(action) + wait(for: [expectation], timeout: Constants.expectationTimeout) + } + /// Verifies that OrderAction.retrieveOrders returns an error whenever there is an error response from the backend. /// func testRetrieveOrdersReturnsErrorUponReponseError() { @@ -92,6 +147,9 @@ class OrderStoreTests: XCTestCase { wait(for: [expectation], timeout: Constants.expectationTimeout) } + + // MARK: - OrderAction.retrieveOrder + /// Verifies that OrderAction.retrieveOrder returns the expected Order. /// func testRetrieveSingleOrderReturnsExpectedFields() { @@ -114,6 +172,34 @@ class OrderStoreTests: XCTestCase { wait(for: [expectation], timeout: Constants.expectationTimeout) } + /// Verifies that `OrderAction.retrieveOrder` effectively persists all of the remote order fields correctly across all of the related `Order` objects (items, coupons, etc). + /// + func testRetrieveSingleOrderEffectivelyPersistsOrderFieldsAndRelatedObjects() { + let expectation = self.expectation(description: "Persist order") + let orderStore = OrderStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + let remoteOrder = sampleOrder() + + network.simulateResponse(requestUrlSuffix: "orders/963", filename: "order") + XCTAssertEqual(viewStorage.countObjects(ofType: Storage.Order.self), 0) + + let action = OrderAction.retrieveOrder(siteID: 123, orderID: 963) { (order, error) in + XCTAssertNotNil(order) + XCTAssertNil(error) + + let predicate = NSPredicate(format: "orderID = %ld", remoteOrder.orderID) + let storedOrder = self.viewStorage.firstObject(ofType: Storage.Order.self, matching: predicate) + let readOnlyStoredOrder = storedOrder?.toReadOnly() + XCTAssertNotNil(storedOrder) + XCTAssertNotNil(readOnlyStoredOrder) + XCTAssertEqual(readOnlyStoredOrder, remoteOrder) + + expectation.fulfill() + } + + orderStore.onAction(action) + wait(for: [expectation], timeout: Constants.expectationTimeout) + } + /// Verifies that OrderAction.retrieveOrder returns an error whenever there is an error response from the backend. /// func testRetrieveSingleOrderReturnsErrorUponReponseError() {