From 1816b327d51a688004572059ef7e73f711ca49d4 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 14:44:45 +0000 Subject: [PATCH 01/24] Enable animation on item lists --- .../PointOfSale/Presentation/Item Selector/ItemList.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/Sources/PointOfSale/Presentation/Item Selector/ItemList.swift b/Modules/Sources/PointOfSale/Presentation/Item Selector/ItemList.swift index 551cf0f56a2..8e170b31da4 100644 --- a/Modules/Sources/PointOfSale/Presentation/Item Selector/ItemList.swift +++ b/Modules/Sources/PointOfSale/Presentation/Item Selector/ItemList.swift @@ -68,6 +68,7 @@ struct ItemList: View { .frame(maxWidth: .infinity) .padding(.horizontal, Constants.itemListPadding) .padding(.bottom, keyboardObserver.isFullSizeKeyboardVisible ? Constants.itemListPadding : floatingControlAreaSize.height) + .animation(.default, value: state?.items) } ) From c89c7e3dd17b9cdacbd28ce055b408d6140b4101 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 15:50:23 +0000 Subject: [PATCH 02/24] Rename Identifier to POSItemIdentifier and remove siteID MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename generic Identifier to POSItemIdentifier - Remove siteID property (only work with one site at a time) - Structure simplified to (underlyingType, itemID) - Update all POSItem types to use POSItemIdentifier This makes the identifier system simpler and more focused on its purpose: stable identification for animations and deduplication. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../PointOfSale/Coupons/POSCoupon.swift | 4 ++-- .../PointOfSale/Items/POSSimpleProduct.swift | 4 ++-- .../Items/POSVariableParentProduct.swift | 9 +++++--- .../PointOfSale/Items/POSVariation.swift | 4 ++-- .../PointOfSaleItemServiceProtocol.swift | 21 +++++++++++++++++-- 5 files changed, 31 insertions(+), 11 deletions(-) diff --git a/Modules/Sources/Yosemite/PointOfSale/Coupons/POSCoupon.swift b/Modules/Sources/Yosemite/PointOfSale/Coupons/POSCoupon.swift index f47e8473a3e..b8239139d4a 100644 --- a/Modules/Sources/Yosemite/PointOfSale/Coupons/POSCoupon.swift +++ b/Modules/Sources/Yosemite/PointOfSale/Coupons/POSCoupon.swift @@ -1,12 +1,12 @@ import Foundation public struct POSCoupon: Equatable, Hashable { - public let id: UUID + public let id: POSItemIdentifier public let code: String public let summary: String public var dateExpires: Date? - public init(id: UUID, code: String, summary: String = "", dateExpires: Date? = nil) { + public init(id: POSItemIdentifier, code: String, summary: String = "", dateExpires: Date? = nil) { self.id = id self.code = code self.summary = summary diff --git a/Modules/Sources/Yosemite/PointOfSale/Items/POSSimpleProduct.swift b/Modules/Sources/Yosemite/PointOfSale/Items/POSSimpleProduct.swift index 4d15af25460..16810d44dfd 100644 --- a/Modules/Sources/Yosemite/PointOfSale/Items/POSSimpleProduct.swift +++ b/Modules/Sources/Yosemite/PointOfSale/Items/POSSimpleProduct.swift @@ -5,7 +5,7 @@ import Networking public struct POSSimpleProduct: POSOrderableItem, OrderSyncProductTypeProtocol { // POSOrderableItem - public let id: UUID + public let id: POSItemIdentifier public let name: String public let formattedPrice: String public var productImageSource: String? @@ -24,7 +24,7 @@ public struct POSSimpleProduct: POSOrderableItem, OrderSyncProductTypeProtocol { return ProductStockStatus(rawValue: stockStatusKey) } - public init(id: UUID, + public init(id: POSItemIdentifier, name: String, formattedPrice: String, productImageSource: String? = nil, diff --git a/Modules/Sources/Yosemite/PointOfSale/Items/POSVariableParentProduct.swift b/Modules/Sources/Yosemite/PointOfSale/Items/POSVariableParentProduct.swift index e8252cf1b55..fcb62295795 100644 --- a/Modules/Sources/Yosemite/PointOfSale/Items/POSVariableParentProduct.swift +++ b/Modules/Sources/Yosemite/PointOfSale/Items/POSVariableParentProduct.swift @@ -1,13 +1,16 @@ import Foundation public struct POSVariableParentProduct: Equatable, Hashable, Identifiable { - public let id: UUID + public let id: POSItemIdentifier public let name: String public let productImageSource: String? public let productID: Int64 let allAttributes: [ProductAttribute] - init(id: UUID, name: String, productImageSource: String?, productID: Int64, allAttributes: [ProductAttribute]) { + init(id: POSItemIdentifier, + name: String, productImageSource: String?, + productID: Int64, + allAttributes: [ProductAttribute]) { self.id = id self.name = name self.productImageSource = productImageSource @@ -18,7 +21,7 @@ public struct POSVariableParentProduct: Equatable, Hashable, Identifiable { #if DEBUG /// Initializer for SwiftUI previews. - public init(id: UUID, name: String, productImageSource: String?, productID: Int64) { + public init(id: POSItemIdentifier, name: String, productImageSource: String?, productID: Int64) { self.init(id: id, name: name, productImageSource: productImageSource, productID: productID, allAttributes: []) } diff --git a/Modules/Sources/Yosemite/PointOfSale/Items/POSVariation.swift b/Modules/Sources/Yosemite/PointOfSale/Items/POSVariation.swift index 51d9c91df80..52e14310f29 100644 --- a/Modules/Sources/Yosemite/PointOfSale/Items/POSVariation.swift +++ b/Modules/Sources/Yosemite/PointOfSale/Items/POSVariation.swift @@ -2,7 +2,7 @@ import Foundation public struct POSVariation: OrderSyncProductVariationTypeProtocol, Equatable, Hashable, Identifiable { // Identifiable & POSOrderableItem - public let id: UUID + public let id: POSItemIdentifier // POSOrderableItem public let name: String @@ -17,7 +17,7 @@ public struct POSVariation: OrderSyncProductVariationTypeProtocol, Equatable, Ha // Variation specific public let parentProductName: String - public init(id: UUID, + public init(id: POSItemIdentifier, name: String, formattedPrice: String, price: String, diff --git a/Modules/Sources/Yosemite/PointOfSale/Items/PointOfSaleItemServiceProtocol.swift b/Modules/Sources/Yosemite/PointOfSale/Items/PointOfSaleItemServiceProtocol.swift index 2cd8a3bd5bb..411931f1c64 100644 --- a/Modules/Sources/Yosemite/PointOfSale/Items/PointOfSaleItemServiceProtocol.swift +++ b/Modules/Sources/Yosemite/PointOfSale/Items/PointOfSaleItemServiceProtocol.swift @@ -7,7 +7,7 @@ public enum POSItem: Equatable, Identifiable, Hashable { case variation(POSVariation) case coupon(POSCoupon) - public var id: UUID { + public var id: POSItemIdentifier { switch self { case .simpleProduct(let product): return product.id @@ -21,12 +21,29 @@ public enum POSItem: Equatable, Identifiable, Hashable { } } +public struct POSItemIdentifier: Hashable, Sendable { + public let underlyingType: UnderlyingType + public let itemID: Int64 + + public init(underlyingType: UnderlyingType, itemID: Int64) { + self.underlyingType = underlyingType + self.itemID = itemID + } + + public enum UnderlyingType: Sendable { + case product + case variation + case coupon + case loading + } +} + /// POSOrderableItem extends a displayable item with the functions required for using it in an order. /// This currently includes adding it, and checking whether it's already in an order. /// This may need to become less specific in future, e.g. we currently convert it to a product input, but /// other order items might be added as fees or similar. at that point, we will need a different function requirement here. public protocol POSOrderableItem { - var id: UUID { get } + var id: POSItemIdentifier { get } var name: String { get } var productImageSource: String? { get } var formattedPrice: String { get } From ea8f0b57dc0fb30f76652aed8e58d630fbf5a403 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 15:50:28 +0000 Subject: [PATCH 03/24] Update item mappers to use POSItemIdentifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix variation mapping to use .variation underlyingType - Remove siteID from all identifier creation - Update coupon mapping in fetch strategy 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Coupons/PointOfSaleCouponFetchStrategy.swift | 2 +- .../PointOfSale/Items/PointOfSaleItemMapper.swift | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Modules/Sources/Yosemite/PointOfSale/Coupons/PointOfSaleCouponFetchStrategy.swift b/Modules/Sources/Yosemite/PointOfSale/Coupons/PointOfSaleCouponFetchStrategy.swift index 38e5efa8a8a..59fab0ed320 100644 --- a/Modules/Sources/Yosemite/PointOfSale/Coupons/PointOfSaleCouponFetchStrategy.swift +++ b/Modules/Sources/Yosemite/PointOfSale/Coupons/PointOfSaleCouponFetchStrategy.swift @@ -169,7 +169,7 @@ private struct CouponResultsControllerAdapter { private func mapCouponsToPOSItems(coupons: [Coupon]) -> [POSItem] { coupons.compactMap { coupon in .coupon(POSCoupon( - id: UUID(), + id: POSItemIdentifier(underlyingType: .coupon, itemID: coupon.couponID), code: coupon.code, summary: coupon.summary(currencySettings: currencySettings), dateExpires: coupon.dateExpires diff --git a/Modules/Sources/Yosemite/PointOfSale/Items/PointOfSaleItemMapper.swift b/Modules/Sources/Yosemite/PointOfSale/Items/PointOfSaleItemMapper.swift index a50300ec28c..504d873c146 100644 --- a/Modules/Sources/Yosemite/PointOfSale/Items/PointOfSaleItemMapper.swift +++ b/Modules/Sources/Yosemite/PointOfSale/Items/PointOfSaleItemMapper.swift @@ -20,10 +20,11 @@ final class PointOfSaleItemMapper: PointOfSaleItemMapperProtocol { func mapProductsToPOSItems(products: [POSProduct]) -> [POSItem] { return products.compactMap { product in let thumbnailSource = product.images.first?.src + let id = POSItemIdentifier(underlyingType: .product, itemID: product.productID) switch product.productType { case .simple: - return .simpleProduct(POSSimpleProduct(id: UUID(), + return .simpleProduct(POSSimpleProduct(id: id, name: product.name, formattedPrice: formatPrice(product.price), productImageSource: thumbnailSource, @@ -34,7 +35,7 @@ final class PointOfSaleItemMapper: PointOfSaleItemMapperProtocol { stockStatusKey: product.stockStatusKey)) case .variable: return .variableParentProduct(POSVariableParentProduct( - id: UUID(), + id: id, name: product.name, productImageSource: thumbnailSource, productID: product.productID, @@ -48,13 +49,14 @@ final class PointOfSaleItemMapper: PointOfSaleItemMapperProtocol { func mapVariationsToPOSItems(variations: [POSProductVariation], parentProduct: POSVariableParentProduct) -> [POSItem] { return variations.compactMap { variation in + let id = POSItemIdentifier(underlyingType: .variation, itemID: variation.productVariationID) let variationName = ProductVariationFormatter().generateNameWithAttributeNames( for: variation, from: parentProduct.allAttributes, separator: ", " ) return POSItem - .variation(.init(id: UUID(), + .variation(.init(id: id, name: variationName, formattedPrice: formatPrice(variation.price), price: variation.price, From f064c79459b793fc2702c81da21aef4a0f7a17c7 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 15:50:34 +0000 Subject: [PATCH 04/24] Add posItemIdentifier to CartItem protocol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add posItemIdentifier property to CartItem protocol - PurchasableItem: Compute from loaded POSOrderableItem - CouponItem: Store POSItemIdentifier on creation - Update cart add() method to capture coupon identifier This enables comparison between cart items and POSItems using stable identifiers while maintaining UUID for cart animations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Modules/Sources/PointOfSale/Models/Cart.swift | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Modules/Sources/PointOfSale/Models/Cart.swift b/Modules/Sources/PointOfSale/Models/Cart.swift index 1aa8ee114d5..5d6e6299625 100644 --- a/Modules/Sources/PointOfSale/Models/Cart.swift +++ b/Modules/Sources/PointOfSale/Models/Cart.swift @@ -2,6 +2,7 @@ import Foundation import protocol Yosemite.POSOrderableItem import enum Yosemite.POSItem import enum Yosemite.PointOfSaleBarcodeScanError +import struct Yosemite.POSItemIdentifier struct Cart { var purchasableItems: [Cart.PurchasableItem] = [] @@ -12,6 +13,7 @@ struct Cart { protocol CartItem { var id: UUID { get } + var posItemIdentifier: POSItemIdentifier { get } var type: CartItemType { get } } @@ -36,6 +38,17 @@ extension Cart { case error } + var posItemIdentifier: POSItemIdentifier { + switch state { + case .loaded(let item): + return item.id + case .loading: + return POSItemIdentifier(underlyingType: .loading, itemID: 0) + case .error: + return POSItemIdentifier(underlyingType: .loading, itemID: 0) + } + } + var formattedPrice: String? { switch state { case .loaded(let item): @@ -76,6 +89,7 @@ extension Cart { struct CouponItem: CartItem { let id: UUID + let posItemIdentifier: POSItemIdentifier let code: String let summary: String let type: CartItemType = .coupon @@ -89,7 +103,7 @@ extension Cart { if let purchasableItem = createPurchasableItem(from: posItem) { purchasableItems.insert(purchasableItem, at: purchasableItems.startIndex) } else if case .coupon(let coupon) = posItem { - let couponItem = Cart.CouponItem(id: coupon.id, code: coupon.code, summary: coupon.summary) + let couponItem = Cart.CouponItem(id: UUID(), posItemIdentifier: coupon.id, code: coupon.code, summary: coupon.summary) coupons.insert(couponItem, at: coupons.startIndex) } } From c0c77074204950fb9db2d6aa4e13dd75f706a63c Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 15:50:40 +0000 Subject: [PATCH 05/24] Enable coupon duplicate detection using posItemIdentifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Uncomment and fix coupon duplicate check - Compare cart coupons using posItemIdentifier - Prevents duplicate coupons from being added to cart 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Presentation/Item Selector/POSItemActionHandler.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Sources/PointOfSale/Presentation/Item Selector/POSItemActionHandler.swift b/Modules/Sources/PointOfSale/Presentation/Item Selector/POSItemActionHandler.swift index 247cfecfca6..f15eaed2e48 100644 --- a/Modules/Sources/PointOfSale/Presentation/Item Selector/POSItemActionHandler.swift +++ b/Modules/Sources/PointOfSale/Presentation/Item Selector/POSItemActionHandler.swift @@ -58,7 +58,7 @@ extension POSItemActionHandler { func shouldSkipDuplicate(_ item: POSItem, posModel: PointOfSaleAggregateModelProtocol) -> Bool { switch item { case .coupon: - return posModel.cart.coupons.contains(where: { $0.id == item.id }) + return posModel.cart.coupons.contains(where: { $0.posItemIdentifier == item.id }) default: return false } From 5a83e8a5dca79a323c546cbf4827a46fa915c2ca Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 15:50:46 +0000 Subject: [PATCH 06/24] Update preview helpers and mocks for POSItemIdentifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update PreviewHelpers to use POSItemIdentifier - Update ScreenshotMock to use POSItemIdentifier - Fix ChildItemList previews with proper identifiers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Item Selector/ChildItemList.swift | 10 +++++----- .../PointOfSale/Utils/PreviewHelpers.swift | 15 ++++++++------- .../PointOfSaleItemServiceScreenshotMock.swift | 8 ++++---- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/Modules/Sources/PointOfSale/Presentation/Item Selector/ChildItemList.swift b/Modules/Sources/PointOfSale/Presentation/Item Selector/ChildItemList.swift index d744c17d5d1..75409604bbc 100644 --- a/Modules/Sources/PointOfSale/Presentation/Item Selector/ChildItemList.swift +++ b/Modules/Sources/PointOfSale/Presentation/Item Selector/ChildItemList.swift @@ -120,7 +120,7 @@ private extension ChildItemList { #Preview("Variable product child items") { let parentProduct = POSVariableParentProduct( - id: .init(), + id: .init(underlyingType: .product, itemID: 1), name: "Variable latte", productImageSource: nil, productID: 1 @@ -134,7 +134,7 @@ private extension ChildItemList { [ .variation( POSVariation( - id: .init(), + id: .init(underlyingType: .variation, itemID: 256), name: "Cinamon chestnut latte", formattedPrice: "$5.75", price: "5.75", @@ -145,12 +145,12 @@ private extension ChildItemList { ), .variation( POSVariation( - id: .init(), + id: .init(underlyingType: .variation, itemID: 2567), name: "Choco latte", formattedPrice: "$6.5", price: "6.5", productID: 134, - variationID: 256, + variationID: 2567, parentProductName: parentProduct.name ) ) @@ -170,7 +170,7 @@ private extension ChildItemList { #Preview("Variable items load error") { let parentProduct = POSVariableParentProduct( - id: .init(), + id: .init(underlyingType: .product, itemID: 1), name: "Variable latte", productImageSource: nil, productID: 1 diff --git a/Modules/Sources/PointOfSale/Utils/PreviewHelpers.swift b/Modules/Sources/PointOfSale/Utils/PreviewHelpers.swift index 131c0485644..4253d9e283a 100644 --- a/Modules/Sources/PointOfSale/Utils/PreviewHelpers.swift +++ b/Modules/Sources/PointOfSale/Utils/PreviewHelpers.swift @@ -48,11 +48,12 @@ import protocol Yosemite.POSItemFetchAnalyticsTracking import protocol Yosemite.POSOrderListFetchStrategyFactoryProtocol import protocol Yosemite.POSOrderListFetchStrategy import protocol Yosemite.PointOfSaleCouponFetchStrategyFactoryProtocol +import struct Yosemite.POSItemIdentifier // MARK: - PreviewProvider helpers // struct POSProductPreview: POSOrderableItem, Equatable { - let id: UUID + let id: POSItemIdentifier let name: String let formattedPrice: String var productImageSource: String? @@ -86,7 +87,7 @@ final class PointOfSalePreviewItemService: PointOfSaleItemServiceProtocol { } func providePointOfSaleItem() -> POSOrderableItem { - POSProductPreview(id: UUID(), + POSProductPreview(id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Product 1", formattedPrice: "$1.00") } @@ -168,7 +169,7 @@ private var mockItems: [POSItem] { mockSimpleProductItem(id: 3, price: "3.00"), .variableParentProduct( .init( - id: .init(), + id: POSItemIdentifier(underlyingType: .product, itemID: 5), name: "Variable product 1", productImageSource: nil, productID: 5 @@ -179,7 +180,7 @@ private var mockItems: [POSItem] { } private func mockSimpleProductItem(id: Int, price: String) -> POSItem { - .simpleProduct(POSSimpleProduct(id: UUID(), + .simpleProduct(POSSimpleProduct(id: POSItemIdentifier(underlyingType: .product, itemID: Int64(id)), name: "Product \(id)", formattedPrice: "$\(price)", productID: Int64(id), @@ -191,19 +192,19 @@ private func mockSimpleProductItem(id: Int, price: String) -> POSItem { private var mockVariationItems: [POSItem] { [ - .variation(.init(id: UUID(), + .variation(.init(id: POSItemIdentifier(underlyingType: .variation, itemID: 256), name: "Variation 1", formattedPrice: "$1.00", price: "1.00", productID: 134, variationID: 256, parentProductName: "Variable product")), - .variation(.init(id: UUID(), + .variation(.init(id: POSItemIdentifier(underlyingType: .variation, itemID: 257), name: "Variation 2", formattedPrice: "$2.00", price: "2.00", productID: 134, - variationID: 256, + variationID: 257, parentProductName: "Variable product")), ] } diff --git a/WooCommerce/Classes/POS/Mocks/PointOfSaleItemServiceScreenshotMock.swift b/WooCommerce/Classes/POS/Mocks/PointOfSaleItemServiceScreenshotMock.swift index 0c855690292..e45f5352882 100644 --- a/WooCommerce/Classes/POS/Mocks/PointOfSaleItemServiceScreenshotMock.swift +++ b/WooCommerce/Classes/POS/Mocks/PointOfSaleItemServiceScreenshotMock.swift @@ -22,7 +22,7 @@ final class PointOfSaleItemServiceScreenshotMock: Yosemite.PointOfSaleItemServic private static func makeScreenshotMockItems(mockResourceUrlHost: String) -> [Yosemite.POSItem] { let product1 = Yosemite.POSSimpleProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Rose Gold Shades", formattedPrice: "$35.00", productImageSource: mockResourceUrlHost + "rose-gold-shades", @@ -34,7 +34,7 @@ final class PointOfSaleItemServiceScreenshotMock: Yosemite.PointOfSaleItemServic ) let product2 = Yosemite.POSSimpleProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: 2), name: "Black Coral Shades", formattedPrice: "$45.00", productImageSource: mockResourceUrlHost + "black-coral-shades", @@ -46,7 +46,7 @@ final class PointOfSaleItemServiceScreenshotMock: Yosemite.PointOfSaleItemServic ) let product3 = Yosemite.POSSimpleProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: 3), name: "Akoya Pearl Shades", formattedPrice: "$50.00", productImageSource: mockResourceUrlHost + "akoya-pearl-shades", @@ -58,7 +58,7 @@ final class PointOfSaleItemServiceScreenshotMock: Yosemite.PointOfSaleItemServic ) let product4 = Yosemite.POSSimpleProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: 4), name: "Malaya Shades", formattedPrice: "$40.00", productImageSource: mockResourceUrlHost + "malaya-shades", From 68616cc7e36fe88f9b651be01889ea5f6a8e0348 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 15:59:02 +0000 Subject: [PATCH 07/24] Fix hardcoded itemID and update test mocks for POSItemIdentifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix hardcoded coupon itemID in PointOfSaleOrderController - Use posItemIdentifier from CartItem instead of hardcoding - Update test mocks to use POSItemIdentifier - Fix all UUID() usages in PointOfSaleItemServiceTests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../PointOfSaleOrderController.swift | 2 +- .../Mocks/MockPOSItemProvider.swift | 40 +++++++------------ .../MockPointOfSaleBarcodeScanService.swift | 2 +- .../PointOfSaleItemServiceTests.swift | 24 +++++------ 4 files changed, 28 insertions(+), 40 deletions(-) diff --git a/Modules/Sources/PointOfSale/Controllers/PointOfSaleOrderController.swift b/Modules/Sources/PointOfSale/Controllers/PointOfSaleOrderController.swift index 9206f423127..1dd7d27fc76 100644 --- a/Modules/Sources/PointOfSale/Controllers/PointOfSaleOrderController.swift +++ b/Modules/Sources/PointOfSale/Controllers/PointOfSaleOrderController.swift @@ -291,7 +291,7 @@ private extension POSCart { guard case let .loaded(item) = purchasableItem.state else { return nil } return POSCartItem(item: item, quantity: Decimal(purchasableItem.quantity)) } - let coupons = cart.coupons.map { POSCoupon(id: $0.id, code: $0.code, summary: $0.summary) } + let coupons = cart.coupons.map { POSCoupon(id: $0.posItemIdentifier, code: $0.code, summary: $0.summary) } self.init(items: items, coupons: coupons) } } diff --git a/Modules/Tests/PointOfSaleTests/Mocks/MockPOSItemProvider.swift b/Modules/Tests/PointOfSaleTests/Mocks/MockPOSItemProvider.swift index 4c618ee96ad..cbd379c997b 100644 --- a/Modules/Tests/PointOfSaleTests/Mocks/MockPOSItemProvider.swift +++ b/Modules/Tests/PointOfSaleTests/Mocks/MockPOSItemProvider.swift @@ -50,10 +50,7 @@ final class MockPointOfSaleItemService: PointOfSaleItemServiceProtocol { extension MockPointOfSaleItemService { static func makeInitialItems() -> [POSItem] { - let fakeUUID1 = UUID(uuidString: "DC55E3B9-9D83-4C07-82A7-4C300A50E84E") ?? UUID() - let fakeUUID2 = UUID(uuidString: "DC55E3B8-9D82-4C06-82A5-4C300A50E84A") ?? UUID() - - let product1 = POSSimpleProduct(id: fakeUUID1, + let product1 = POSSimpleProduct(id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Choco", formattedPrice: "$2.00", productID: 1, @@ -62,11 +59,11 @@ extension MockPointOfSaleItemService { stockQuantity: nil, stockStatusKey: "") - let product2 = POSSimpleProduct(id: fakeUUID2, + let product2 = POSSimpleProduct(id: POSItemIdentifier(underlyingType: .product, itemID: 2), name: "Vanilla", formattedPrice: "$3.00", - productID: 1, - price: "2.00", + productID: 2, + price: "3.00", manageStock: false, stockQuantity: nil, stockStatusKey: "") @@ -74,23 +71,20 @@ extension MockPointOfSaleItemService { } static func makeSecondPageItems() -> [POSItem] { - let fakeUUID3 = UUID(uuidString: "DC55E3B9-9D83-4C07-82A7-4C300A50E86D") ?? UUID() - let fakeUUID4 = UUID(uuidString: "DC55E3B8-9D82-4C06-82A5-4C300A50E86F") ?? UUID() - - let product3 = POSSimpleProduct(id: fakeUUID3, + let product3 = POSSimpleProduct(id: POSItemIdentifier(underlyingType: .product, itemID: 3), name: "Strawberry", formattedPrice: "$2.00", - productID: 1, + productID: 3, price: "2.00", manageStock: false, stockQuantity: nil, stockStatusKey: "") - let product4 = POSSimpleProduct(id: fakeUUID4, + let product4 = POSSimpleProduct(id: POSItemIdentifier(underlyingType: .product, itemID: 4), name: "Pistachio", formattedPrice: "$3.00", - productID: 1, - price: "2.00", + productID: 4, + price: "3.00", manageStock: false, stockQuantity: nil, stockStatusKey: "") @@ -98,10 +92,7 @@ extension MockPointOfSaleItemService { } static func makeInitialVariationItems() -> [POSItem] { - let fakeUUID1 = UUID(uuidString: "B04AF636-CF6C-11EF-A45C-FA719FB6C0F0") ?? UUID() - let fakeUUID2 = UUID(uuidString: "B04AF727-CF6C-11EF-A45C-FA719FB6C0F0") ?? UUID() - - let variation1 = POSVariation(id: fakeUUID1, + let variation1 = POSVariation(id: POSItemIdentifier(underlyingType: .variation, itemID: 1), name: "Choco", formattedPrice: "$2.00", price: "2.00", @@ -109,7 +100,7 @@ extension MockPointOfSaleItemService { variationID: 1, parentProductName: "Ice cream") - let variation2 = POSVariation(id: fakeUUID2, + let variation2 = POSVariation(id: POSItemIdentifier(underlyingType: .variation, itemID: 2), name: "Vanilla", formattedPrice: "$2.00", price: "2.00", @@ -120,10 +111,7 @@ extension MockPointOfSaleItemService { } static func makeSecondPageVariationItems() -> [POSItem] { - let fakeUUID3 = UUID(uuidString: "B04AF758-CF6C-11EF-A45C-FA719FB6C0F0") ?? UUID() - let fakeUUID4 = UUID(uuidString: "B04AF78A-CF6C-11EF-A45C-FA719FB6C0F0") ?? UUID() - - let variation3 = POSVariation(id: fakeUUID3, + let variation3 = POSVariation(id: POSItemIdentifier(underlyingType: .variation, itemID: 3), name: "Strawberry", formattedPrice: "$2.00", price: "2.00", @@ -131,10 +119,10 @@ extension MockPointOfSaleItemService { variationID: 3, parentProductName: "Ice cream") - let variation4 = POSVariation(id: fakeUUID4, + let variation4 = POSVariation(id: POSItemIdentifier(underlyingType: .variation, itemID: 4), name: "Pistachio", formattedPrice: "$3.00", - price: "2.00", + price: "3.00", productID: 1, variationID: 4, parentProductName: "Ice cream") diff --git a/Modules/Tests/PointOfSaleTests/Mocks/MockPointOfSaleBarcodeScanService.swift b/Modules/Tests/PointOfSaleTests/Mocks/MockPointOfSaleBarcodeScanService.swift index 0c8b0ff5e49..d927ee58fc4 100644 --- a/Modules/Tests/PointOfSaleTests/Mocks/MockPointOfSaleBarcodeScanService.swift +++ b/Modules/Tests/PointOfSaleTests/Mocks/MockPointOfSaleBarcodeScanService.swift @@ -10,7 +10,7 @@ class MockPointOfSaleBarcodeScanService: PointOfSaleBarcodeScanServiceProtocol { } return .simpleProduct(POSSimpleProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Scanned Item", formattedPrice: "$10.00", productID: 1, diff --git a/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleItemServiceTests.swift b/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleItemServiceTests.swift index e74ca2d3533..466aaf0dd4a 100644 --- a/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleItemServiceTests.swift +++ b/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleItemServiceTests.swift @@ -79,7 +79,7 @@ final class PointOfSaleItemServiceTests: XCTestCase { totalItems: 1)) let expectedItem = POSItem.simpleProduct(POSSimpleProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: 208), name: "Dymo LabelWriter 4XL", formattedPrice: "$216.00", productID: 208, @@ -118,7 +118,7 @@ final class PointOfSaleItemServiceTests: XCTestCase { let mockMappedProducts = mockProducts.map { product in POSItem.simpleProduct(POSSimpleProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: product.productID), name: product.name, formattedPrice: "$0.00", productID: product.productID, @@ -176,7 +176,7 @@ final class PointOfSaleItemServiceTests: XCTestCase { // Given let parentProductID: Int64 = 123 let parentProduct = POSVariableParentProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: parentProductID), name: "Tea", productImageSource: nil, productID: parentProductID, @@ -196,7 +196,7 @@ final class PointOfSaleItemServiceTests: XCTestCase { totalItems: 1)) let expectedVariation = POSItem.variation(POSVariation( - id: UUID(), + id: POSItemIdentifier(underlyingType: .variation, itemID: 1274), name: "Shape: brick, Flavor: nuts, Darkness: 99%, Size: Any", formattedPrice: "$0.00", price: "", @@ -226,7 +226,7 @@ final class PointOfSaleItemServiceTests: XCTestCase { // Given let parentProductID: Int64 = 123 let parentProduct = POSVariableParentProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: parentProductID), name: "Tea", productImageSource: nil, productID: parentProductID, @@ -270,7 +270,7 @@ final class PointOfSaleItemServiceTests: XCTestCase { do { _ = try await itemProvider.providePointOfSaleVariationItems( for: .init( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: parentProductID), name: "Tea", productImageSource: nil, productID: parentProductID, @@ -289,7 +289,7 @@ final class PointOfSaleItemServiceTests: XCTestCase { // Given let parentProductID: Int64 = 123 let parentProduct = POSVariableParentProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: parentProductID), name: "Tea", productImageSource: nil, productID: parentProductID, @@ -302,7 +302,7 @@ final class PointOfSaleItemServiceTests: XCTestCase { totalItems: 1)) let expectedVariation = POSItem.variation(POSVariation( - id: UUID(), + id: POSItemIdentifier(underlyingType: .variation, itemID: 1274), name: "Shape: brick, Flavor: nuts, Darkness: 99%, Size: Any", formattedPrice: "$0.00", price: "", @@ -347,7 +347,7 @@ final class PointOfSaleItemServiceTests: XCTestCase { totalItems: 1)) let expectedItem = POSItem.simpleProduct(POSSimpleProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Test Product", formattedPrice: "$10.00", productID: 1, @@ -376,7 +376,7 @@ final class PointOfSaleItemServiceTests: XCTestCase { totalItems: 1)) let expectedItem = POSItem.variation(POSVariation( - id: UUID(), + id: POSItemIdentifier(underlyingType: .variation, itemID: 10), name: "Test Variation", formattedPrice: "$20.00", price: "20.00", @@ -386,7 +386,7 @@ final class PointOfSaleItemServiceTests: XCTestCase { )) mockItemMapper.mockMappedVariations = [expectedItem] - let parentProduct = POSVariableParentProduct.init(id: UUID(), + let parentProduct = POSVariableParentProduct.init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Test Variable Product", productImageSource: nil, productID: 1) @@ -439,7 +439,7 @@ final class PointOfSaleItemServiceTests: XCTestCase { // Given let parentProductID: Int64 = 123 let parentProduct = POSVariableParentProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: parentProductID), name: "Test Variable Product", productImageSource: nil, productID: parentProductID, From 08732bfa52d66148e15f1ca050cd2eb9d89bd28f Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 16:07:09 +0000 Subject: [PATCH 08/24] Update all test files to use POSItemIdentifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace UUID() with POSItemIdentifier in all test files - Use appropriate underlyingType (.product, .variation, .coupon) - Use meaningful itemID values (productID, variationID, or test IDs) Files updated: - POSOrderServiceTests.swift - GRDBObservableDataSourceTests.swift - POSProductOrVariationResolverTests.swift - POSCouponTests.swift - PointOfSaleItemMapperTests.swift - PointOfSaleCouponServiceTests.swift - CartViewHelperTests.swift - PointOfSaleAggregateModelTests.swift - PointOfSaleItemsControllerTests.swift - PointOfSaleOrderControllerTests.swift - PointOfSaleObservableItemsControllerTests.swift - POSItemActionHandlerFactoryTests.swift - POSItemActionHandlerTests.swift 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../PointOfSaleItemsControllerTests.swift | 8 ++++---- ...OfSaleObservableItemsControllerTests.swift | 14 ++++++------- .../PointOfSaleOrderControllerTests.swift | 16 +++++++-------- .../PointOfSaleAggregateModelTests.swift | 4 ++-- .../POSItemActionHandlerFactoryTests.swift | 4 ++-- .../POSItemActionHandlerTests.swift | 4 ++-- .../ViewHelpers/CartViewHelperTests.swift | 20 +++++++++---------- .../GRDBObservableDataSourceTests.swift | 18 ++++++++--------- .../PointOfSale/POSCouponTests.swift | 8 ++++---- .../POSProductOrVariationResolverTests.swift | 6 +++--- .../PointOfSaleCouponServiceTests.swift | 4 ++-- .../PointOfSaleItemMapperTests.swift | 2 +- .../Tools/POS/POSOrderServiceTests.swift | 18 ++++++++--------- 13 files changed, 63 insertions(+), 63 deletions(-) diff --git a/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleItemsControllerTests.swift b/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleItemsControllerTests.swift index 04981540fbf..bc9e22c6730 100644 --- a/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleItemsControllerTests.swift +++ b/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleItemsControllerTests.swift @@ -236,7 +236,7 @@ final class PointOfSaleItemsControllerTests { analyticsProvider: MockPOSAnalytics() ) - let parentItem = POSItem.variableParentProduct(POSVariableParentProduct(id: UUID(), + let parentItem = POSItem.variableParentProduct(POSVariableParentProduct(id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Fake Parent", productImageSource: nil, productID: 12345)) @@ -268,7 +268,7 @@ final class PointOfSaleItemsControllerTests { analyticsProvider: MockPOSAnalytics() ) - let parentItem = POSItem.variableParentProduct(POSVariableParentProduct(id: UUID(), + let parentItem = POSItem.variableParentProduct(POSVariableParentProduct(id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Fake Parent", productImageSource: nil, productID: 12345)) @@ -496,7 +496,7 @@ final class PointOfSaleItemsControllerTests { analyticsProvider: MockPOSAnalytics() ) - let parentItem = POSItem.variableParentProduct(POSVariableParentProduct(id: UUID(), + let parentItem = POSItem.variableParentProduct(POSVariableParentProduct(id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Parent product", productImageSource: nil, productID: 125)) @@ -522,7 +522,7 @@ final class PointOfSaleItemsControllerTests { analyticsProvider: MockPOSAnalytics() ) - let parentItem = POSItem.variableParentProduct(POSVariableParentProduct(id: UUID(), + let parentItem = POSItem.variableParentProduct(POSVariableParentProduct(id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Parent product", productImageSource: nil, productID: 125)) diff --git a/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleObservableItemsControllerTests.swift b/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleObservableItemsControllerTests.swift index ca4b9b116bc..9b8b0541bcb 100644 --- a/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleObservableItemsControllerTests.swift +++ b/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleObservableItemsControllerTests.swift @@ -118,7 +118,7 @@ final class PointOfSaleObservableItemsControllerTests { let sut = PointOfSaleObservableItemsController(siteID: 123, dataSource: dataSource, catalogSyncCoordinator: coordinator) let parentProduct = POSVariableParentProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Parent", productImageSource: nil, productID: 100, @@ -145,7 +145,7 @@ final class PointOfSaleObservableItemsControllerTests { let sut = PointOfSaleObservableItemsController(siteID: 123, dataSource: dataSource, catalogSyncCoordinator: coordinator) let parentProduct = POSVariableParentProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Parent", productImageSource: nil, productID: 100, @@ -179,7 +179,7 @@ final class PointOfSaleObservableItemsControllerTests { let mockVariations = [makeVariation()] let parentProduct = POSVariableParentProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Parent", productImageSource: nil, productID: 100, @@ -253,10 +253,10 @@ final class PointOfSaleObservableItemsControllerTests { let coordinator = MockPOSCatalogSyncCoordinator() let sut = PointOfSaleObservableItemsController(siteID: 123, dataSource: dataSource, catalogSyncCoordinator: coordinator) - let parent1 = POSVariableParentProduct(id: UUID(), name: "Parent 1", productImageSource: nil, productID: 100, allAttributes: []) + let parent1 = POSVariableParentProduct(id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Parent 1", productImageSource: nil, productID: 100, allAttributes: []) let parentItem1 = POSItem.variableParentProduct(parent1) - let parent2 = POSVariableParentProduct(id: UUID(), name: "Parent 2", productImageSource: nil, productID: 200, allAttributes: []) + let parent2 = POSVariableParentProduct(id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Parent 2", productImageSource: nil, productID: 200, allAttributes: []) let parentItem2 = POSItem.variableParentProduct(parent2) // When: Load parent 1 variations @@ -393,7 +393,7 @@ final class PointOfSaleObservableItemsControllerTests { let sut = PointOfSaleObservableItemsController(siteID: 123, dataSource: dataSource, catalogSyncCoordinator: coordinator) let parentProduct = POSVariableParentProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Parent", productImageSource: nil, productID: 100, @@ -640,7 +640,7 @@ final class PointOfSaleObservableItemsControllerTests { let sut = PointOfSaleObservableItemsController(siteID: siteID, dataSource: dataSource, catalogSyncCoordinator: coordinator) let parentProduct = POSVariableParentProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Parent", productImageSource: nil, productID: 100, diff --git a/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleOrderControllerTests.swift b/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleOrderControllerTests.swift index 5315c959e3a..af796860b67 100644 --- a/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleOrderControllerTests.swift +++ b/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleOrderControllerTests.swift @@ -430,12 +430,12 @@ struct PointOfSaleOrderControllerTests { mockOrderService.orderToReturn = fakeOrder // Initial sync to set up the order - await sut.syncOrder(for: Cart(purchasableItems: [cartItem], coupons: [.init(id: UUID(), code: couponCode, summary: "")]), retryHandler: {}) + await sut.syncOrder(for: Cart(purchasableItems: [cartItem], coupons: [.init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: couponCode, summary: "")]), retryHandler: {}) mockOrderService.syncOrderWasCalled = false // When - sync with same items and coupons - await sut.syncOrder(for: Cart(purchasableItems: [cartItem], coupons: [.init(id: UUID(), code: couponCode, summary: "")]), retryHandler: {}) + await sut.syncOrder(for: Cart(purchasableItems: [cartItem], coupons: [.init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: couponCode, summary: "")]), retryHandler: {}) // Then #expect(mockOrderService.syncOrderWasCalled == false) @@ -455,12 +455,12 @@ struct PointOfSaleOrderControllerTests { mockOrderService.orderToReturn = fakeOrder // Initial sync - await sut.syncOrder(for: Cart(purchasableItems: [cartItem], coupons: [.init(id: UUID(), code: initialCouponCode, summary: "")]), retryHandler: {}) + await sut.syncOrder(for: Cart(purchasableItems: [cartItem], coupons: [.init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: initialCouponCode, summary: "")]), retryHandler: {}) mockOrderService.syncOrderWasCalled = false // When - sync with same items but different coupon - await sut.syncOrder(for: Cart(purchasableItems: [cartItem], coupons: [.init(id: UUID(), code: "DIFFERENT20", summary: "")]), retryHandler: {}) + await sut.syncOrder(for: Cart(purchasableItems: [cartItem], coupons: [.init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "DIFFERENT20", summary: "")]), retryHandler: {}) // Then #expect(mockOrderService.syncOrderWasCalled == true) @@ -480,7 +480,7 @@ struct PointOfSaleOrderControllerTests { mockOrderService.orderToReturn = fakeOrder // Initial sync with coupon - await sut.syncOrder(for: Cart(purchasableItems: [cartItem], coupons: [.init(id: UUID(), code: couponCode, summary: "")]), retryHandler: {}) + await sut.syncOrder(for: Cart(purchasableItems: [cartItem], coupons: [.init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: couponCode, summary: "")]), retryHandler: {}) mockOrderService.syncOrderWasCalled = false @@ -518,7 +518,7 @@ struct PointOfSaleOrderControllerTests { // When await sut.syncOrder(for: Cart(purchasableItems: [makeItem()], - coupons: [.init(id: UUID(), code: "INVALID", summary: "")]), + coupons: [.init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "INVALID", summary: "")]), retryHandler: {}) } @@ -566,7 +566,7 @@ struct PointOfSaleOrderControllerTests { // When await sut.syncOrder(for: Cart(purchasableItems: [makeItem()], - coupons: [.init(id: UUID(), code: "INVALID", summary: "")]), + coupons: [.init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "INVALID", summary: "")]), retryHandler: {}) } @@ -700,7 +700,7 @@ private func makeItem(name: String = "", formattedPrice: String = "", quantity: Int = 1, orderItemsToMatch: [OrderItem] = []) -> Cart.PurchasableItem { - return .init(id: UUID(), + return .init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), item: MockPOSOrderableItem(name: name, formattedPrice: formattedPrice, orderItemsToMatch: orderItemsToMatch), diff --git a/Modules/Tests/PointOfSaleTests/Models/PointOfSaleAggregateModelTests.swift b/Modules/Tests/PointOfSaleTests/Models/PointOfSaleAggregateModelTests.swift index 09d9c1d53c0..870ffb58f35 100644 --- a/Modules/Tests/PointOfSaleTests/Models/PointOfSaleAggregateModelTests.swift +++ b/Modules/Tests/PointOfSaleTests/Models/PointOfSaleAggregateModelTests.swift @@ -996,7 +996,7 @@ struct PointOfSaleAggregateModelTests { private func makePurchasableItem(name: String = "") -> POSItem { return .simpleProduct(POSSimpleProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: name, formattedPrice: "", productID: 1, @@ -1007,7 +1007,7 @@ private func makePurchasableItem(name: String = "") -> POSItem { } private func makeCouponItem(code: String = "") -> POSItem { - return .coupon(.init(id: UUID(), code: code)) + return .coupon(.init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: code)) } private func makeLoadedOrderState(cartTotal: String = "", diff --git a/Modules/Tests/PointOfSaleTests/Presentation/POSItemActionHandlerFactoryTests.swift b/Modules/Tests/PointOfSaleTests/Presentation/POSItemActionHandlerFactoryTests.swift index 393ebff5c81..16bde0c63bd 100644 --- a/Modules/Tests/PointOfSaleTests/Presentation/POSItemActionHandlerFactoryTests.swift +++ b/Modules/Tests/PointOfSaleTests/Presentation/POSItemActionHandlerFactoryTests.swift @@ -158,7 +158,7 @@ struct POSItemActionHandlerFactoryTests { } private func makeProductItem() -> POSItem { - return .simpleProduct(.init(id: UUID(), + return .simpleProduct(.init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Test", formattedPrice: "$1.00", productID: 1, @@ -169,7 +169,7 @@ private func makeProductItem() -> POSItem { } private func makeCouponItem() -> POSItem { - return .coupon(.init(id: UUID(), code: "DISCOUNT!")) + return .coupon(.init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "DISCOUNT!")) } private func makeVariationItem() -> POSItem { diff --git a/Modules/Tests/PointOfSaleTests/Presentation/POSItemActionHandlerTests.swift b/Modules/Tests/PointOfSaleTests/Presentation/POSItemActionHandlerTests.swift index fe46856af1b..3f403a29202 100644 --- a/Modules/Tests/PointOfSaleTests/Presentation/POSItemActionHandlerTests.swift +++ b/Modules/Tests/PointOfSaleTests/Presentation/POSItemActionHandlerTests.swift @@ -83,11 +83,11 @@ struct POSItemActionHandlerTests { } private func makeCouponItem(code: String = "") -> POSItem { - return .coupon(.init(id: UUID(), code: code)) + return .coupon(.init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: code)) } private func makeProductItem() -> POSItem { - return .simpleProduct(.init(id: UUID(), + return .simpleProduct(.init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "some product name", formattedPrice: "$10.00", productID: 123, diff --git a/Modules/Tests/PointOfSaleTests/ViewHelpers/CartViewHelperTests.swift b/Modules/Tests/PointOfSaleTests/ViewHelpers/CartViewHelperTests.swift index 593ae8f1723..72b9001ba01 100644 --- a/Modules/Tests/PointOfSaleTests/ViewHelpers/CartViewHelperTests.swift +++ b/Modules/Tests/PointOfSaleTests/ViewHelpers/CartViewHelperTests.swift @@ -114,7 +114,7 @@ struct CartViewHelperTests { @Test func couponRowState_building_stage_returns_idle() async throws { // Given - let coupon = Cart.CouponItem(id: UUID(), code: "TEST10", summary: "") + let coupon = Cart.CouponItem(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "TEST10", summary: "") let orderState = PointOfSaleOrderState.loaded(PointOfSaleOrderTotals( cartTotal: "$10.00", orderTotal: "$12.00", @@ -129,7 +129,7 @@ struct CartViewHelperTests { @Test func couponRowState_finalizing_and_syncing_returns_validating() async throws { // Given - let coupon = Cart.CouponItem(id: UUID(), code: "TEST10", summary: "") + let coupon = Cart.CouponItem(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "TEST10", summary: "") // When, Then #expect(sut.couponRowState(orderStage: .finalizing, @@ -140,7 +140,7 @@ struct CartViewHelperTests { @Test func couponRowState_finalizing_and_loaded_with_matching_coupon_returns_valid() async throws { // Given let couponCode = "TEST10" - let coupon = Cart.CouponItem(id: UUID(), code: couponCode, summary: "") + let coupon = Cart.CouponItem(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: couponCode, summary: "") let couponTotal = PointOfSaleCouponTotal(code: couponCode, total: "10.00") let orderTotals = PointOfSaleOrderTotals( cartTotal: "$10.00", @@ -157,7 +157,7 @@ struct CartViewHelperTests { @Test func couponRowState_finalizing_and_loaded_with_no_matching_coupon_returns_idle() async throws { // Given - let coupon = Cart.CouponItem(id: UUID(), code: "TEST10", summary: "") + let coupon = Cart.CouponItem(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "TEST10", summary: "") let orderTotals = PointOfSaleOrderTotals( cartTotal: "$10.00", orderTotal: "$12.00", @@ -173,7 +173,7 @@ struct CartViewHelperTests { @Test func couponRowState_finalizing_and_invalid_coupon_error_returns_invalid() async throws { // Given - let coupon = Cart.CouponItem(id: UUID(), code: "TEST10", summary: "") + let coupon = Cart.CouponItem(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "TEST10", summary: "") // When, Then #expect(sut.couponRowState(orderStage: .finalizing, @@ -183,7 +183,7 @@ struct CartViewHelperTests { @Test func couponRowState_finalizing_and_other_error_returns_idle() async throws { // Given - let coupon = Cart.CouponItem(id: UUID(), code: "TEST10", summary: "") + let coupon = Cart.CouponItem(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "TEST10", summary: "") // When, Then #expect(sut.couponRowState(orderStage: .finalizing, @@ -233,7 +233,7 @@ struct CartViewHelperTests { @Test func hasUnresolvedItems_when_cart_has_loading_item_returns_true() async throws { // Given - let loadingItem = Cart.PurchasableItem.loading(id: UUID()) + let loadingItem = Cart.PurchasableItem.loading(id: POSItemIdentifier(underlyingType: .product, itemID: 1)) let cart = Cart(purchasableItems: [loadingItem]) // When, Then @@ -243,7 +243,7 @@ struct CartViewHelperTests { @Test func hasUnresolvedItems_when_cart_has_error_item_returns_true() async throws { // Given let errorItem = Cart.PurchasableItem( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: 1), title: "", subtitle: "", quantity: 1, @@ -257,7 +257,7 @@ struct CartViewHelperTests { @Test func hasUnresolvedItems_when_cart_has_mixed_items_returns_true() async throws { // Given - let loadingItem = Cart.PurchasableItem.loading(id: UUID()) + let loadingItem = Cart.PurchasableItem.loading(id: POSItemIdentifier(underlyingType: .product, itemID: 1)) let loadedItem = makeItem() let cart = Cart(purchasableItems: [loadingItem, loadedItem]) @@ -267,7 +267,7 @@ struct CartViewHelperTests { } private func makeItem() -> Cart.PurchasableItem { - .init(id: UUID(), + .init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), item: MockPOSOrderableItem(name: "Item", formattedPrice: "$1.00"), title: "Item", subtitle: nil, diff --git a/Modules/Tests/YosemiteTests/PointOfSale/GRDBObservableDataSourceTests.swift b/Modules/Tests/YosemiteTests/PointOfSale/GRDBObservableDataSourceTests.swift index b793b773487..af8a20e22bf 100644 --- a/Modules/Tests/YosemiteTests/PointOfSale/GRDBObservableDataSourceTests.swift +++ b/Modules/Tests/YosemiteTests/PointOfSale/GRDBObservableDataSourceTests.swift @@ -125,7 +125,7 @@ struct GRDBObservableDataSourceTests { try await insertTestVariations(parentID: 100, count: 3) let posParent = POSVariableParentProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Parent", productImageSource: nil, productID: 100, @@ -151,7 +151,7 @@ struct GRDBObservableDataSourceTests { try await insertTestVariations(parentID: 100, count: 2) let posParent = POSVariableParentProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Parent", productImageSource: nil, productID: 100, @@ -184,8 +184,8 @@ struct GRDBObservableDataSourceTests { try await insertTestVariations(parentID: 100, count: 2) try await insertTestVariations(parentID: 200, count: 3) - let posParent1 = POSVariableParentProduct(id: UUID(), name: "Parent 1", productImageSource: nil, productID: 100, allAttributes: []) - let posParent2 = POSVariableParentProduct(id: UUID(), name: "Parent 2", productImageSource: nil, productID: 200, allAttributes: []) + let posParent1 = POSVariableParentProduct(id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Parent 1", productImageSource: nil, productID: 100, allAttributes: []) + let posParent2 = POSVariableParentProduct(id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Parent 2", productImageSource: nil, productID: 200, allAttributes: []) // When: Load parent 1 variations await waitForVariationLoad { @@ -267,7 +267,7 @@ struct GRDBObservableDataSourceTests { #expect(sut.variationItems.isEmpty) // When: Load variations - let posParent = POSVariableParentProduct(id: UUID(), name: "Parent", productImageSource: nil, productID: 100, allAttributes: []) + let posParent = POSVariableParentProduct(id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Parent", productImageSource: nil, productID: 100, allAttributes: []) await waitForVariationLoad { sut.loadVariations(for: posParent) } @@ -296,8 +296,8 @@ struct GRDBObservableDataSourceTests { // Insert 3 downloadable variations for parent 2 (should be excluded from count) try await insertDownloadableVariations(parentID: 200, count: 3, startID: 2000) - let posParent1 = POSVariableParentProduct(id: UUID(), name: "Parent 1", productImageSource: nil, productID: 100, allAttributes: []) - let posParent2 = POSVariableParentProduct(id: UUID(), name: "Parent 2", productImageSource: nil, productID: 200, allAttributes: []) + let posParent1 = POSVariableParentProduct(id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Parent 1", productImageSource: nil, productID: 100, allAttributes: []) + let posParent2 = POSVariableParentProduct(id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Parent 2", productImageSource: nil, productID: 200, allAttributes: []) // When: Load parent 1 variations (first page of 5) await waitForVariationLoad { @@ -349,8 +349,8 @@ struct GRDBObservableDataSourceTests { // Parent 2: 8 variations (more than page size of 5) try await insertTestVariations(parentID: 200, count: 8) - let posParent1 = POSVariableParentProduct(id: UUID(), name: "Parent 1", productImageSource: nil, productID: 100, allAttributes: []) - let posParent2 = POSVariableParentProduct(id: UUID(), name: "Parent 2", productImageSource: nil, productID: 200, allAttributes: []) + let posParent1 = POSVariableParentProduct(id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Parent 1", productImageSource: nil, productID: 100, allAttributes: []) + let posParent2 = POSVariableParentProduct(id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Parent 2", productImageSource: nil, productID: 200, allAttributes: []) // When: Load parent 1 variations await waitForVariationLoad { diff --git a/Modules/Tests/YosemiteTests/PointOfSale/POSCouponTests.swift b/Modules/Tests/YosemiteTests/PointOfSale/POSCouponTests.swift index 1d700af32c8..dc291aa774e 100644 --- a/Modules/Tests/YosemiteTests/PointOfSale/POSCouponTests.swift +++ b/Modules/Tests/YosemiteTests/PointOfSale/POSCouponTests.swift @@ -5,7 +5,7 @@ import Testing struct POSCouponTests { @Test func test_isExpired_when_no_expiration_date_then_returns_false() { // Given - let coupon = POSCoupon(id: UUID(), code: "valid-forever") + let coupon = POSCoupon(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "valid-forever") // Then #expect(coupon.isExpired == false) @@ -14,7 +14,7 @@ struct POSCouponTests { @Test func test_isExpired_when_future_date_then_returns_false() { // Given let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: Date()) ?? Date() - let coupon = POSCoupon(id: UUID(), code: "will-expire-in-the-future", dateExpires: tomorrow) + let coupon = POSCoupon(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "will-expire-in-the-future", dateExpires: tomorrow) // Then #expect(coupon.isExpired == false) @@ -23,7 +23,7 @@ struct POSCouponTests { @Test func test_isExpired_when_past_date_then_returns_true() { // Given let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date()) ?? Date() - let coupon = POSCoupon(id: UUID(), code: "expired-yesterday", dateExpires: yesterday) + let coupon = POSCoupon(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "expired-yesterday", dateExpires: yesterday) // Then #expect(coupon.isExpired == true) @@ -32,7 +32,7 @@ struct POSCouponTests { @Test func test_isExpired_when_current_date_then_returns_true() { // Given let now = Date() - let coupon = POSCoupon(id: UUID(), code: "expired-now", dateExpires: now) + let coupon = POSCoupon(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "expired-now", dateExpires: now) // Then #expect(coupon.isExpired == true, "A coupon expiring at the current time should be considered expired") diff --git a/Modules/Tests/YosemiteTests/PointOfSale/POSProductOrVariationResolverTests.swift b/Modules/Tests/YosemiteTests/PointOfSale/POSProductOrVariationResolverTests.swift index f8c1223f848..aed727efa16 100644 --- a/Modules/Tests/YosemiteTests/PointOfSale/POSProductOrVariationResolverTests.swift +++ b/Modules/Tests/YosemiteTests/PointOfSale/POSProductOrVariationResolverTests.swift @@ -29,7 +29,7 @@ struct POSProductOrVariationResolverTests { price: "10.00" ) let expectedItem = POSItem.simpleProduct(POSSimpleProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Simple Product", formattedPrice: "$10.00", productID: 123, @@ -67,14 +67,14 @@ struct POSProductOrVariationResolverTests { productTypeKey: "variable" ) let expectedParentItem = POSItem.variableParentProduct(POSVariableParentProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Parent Product", productImageSource: nil, productID: 123, allAttributes: [] )) let expectedVariationItem = POSItem.variation(POSVariation( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Variation", formattedPrice: "$15.00", price: "15.00", diff --git a/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleCouponServiceTests.swift b/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleCouponServiceTests.swift index 0d79909ab29..b2459c1ce3a 100644 --- a/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleCouponServiceTests.swift +++ b/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleCouponServiceTests.swift @@ -49,7 +49,7 @@ struct PointOfSaleCouponServiceTests { @Test func provideLocalPointOfSaleCoupons_when_enabled_then_calls_strategy() async throws { // Given settingStoreMethods.couponsEnabled = true - let expectedCoupons = [POSItem.coupon(POSCoupon(id: UUID(), code: "test", summary: "test", dateExpires: nil))] + let expectedCoupons = [POSItem.coupon(POSCoupon(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "test", summary: "test", dateExpires: nil))] mockStrategy.fetchLocalCouponsResult = expectedCoupons // When @@ -63,7 +63,7 @@ struct PointOfSaleCouponServiceTests { @Test func providePointOfSaleCoupons_when_enabled_then_calls_strategy() async throws { // Given settingStoreMethods.couponsEnabled = true - let expectedCoupons = PagedItems(items: [POSItem.coupon(POSCoupon(id: UUID(), + let expectedCoupons = PagedItems(items: [POSItem.coupon(POSCoupon(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "test", summary: "test", dateExpires: nil))], diff --git a/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleItemMapperTests.swift b/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleItemMapperTests.swift index 64623553286..77d17cfcf51 100644 --- a/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleItemMapperTests.swift +++ b/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleItemMapperTests.swift @@ -186,7 +186,7 @@ struct PointOfSaleItemMapperTests { private static func createParentProduct() -> POSVariableParentProduct { POSVariableParentProduct( - id: UUID(), + id: POSItemIdentifier(underlyingType: .product, itemID: 1), name: "Parent Product", productImageSource: nil, productID: 125, diff --git a/Modules/Tests/YosemiteTests/Tools/POS/POSOrderServiceTests.swift b/Modules/Tests/YosemiteTests/Tools/POS/POSOrderServiceTests.swift index 5a4b4dd2a20..ee3fcfbfed2 100644 --- a/Modules/Tests/YosemiteTests/Tools/POS/POSOrderServiceTests.swift +++ b/Modules/Tests/YosemiteTests/Tools/POS/POSOrderServiceTests.swift @@ -66,7 +66,7 @@ struct POSOrderServiceTests { // Given let cart = POSCart( items: [makePOSCartItem(productID: 100, quantity: 1)], - coupons: [.init(id: UUID(), code: "SAVE10")] + coupons: [.init(id: POSItemIdentifier(underlyingType: .coupon, itemID: 1), code: "SAVE10")] ) // When @@ -84,9 +84,9 @@ struct POSOrderServiceTests { let cart = POSCart( items: [makePOSCartItem(productID: 100, quantity: 1)], coupons: [ - .init(id: UUID(), code: "SAVE10"), - .init(id: UUID(), code: "FREESHIP"), - .init(id: UUID(), code: "EXTRA5") + .init(id: POSItemIdentifier(underlyingType: .coupon, itemID: 1), code: "SAVE10"), + .init(id: POSItemIdentifier(underlyingType: .coupon, itemID: 2), code: "FREESHIP"), + .init(id: POSItemIdentifier(underlyingType: .coupon, itemID: 3), code: "EXTRA5") ] ) @@ -262,7 +262,7 @@ struct POSOrderServiceTests { let cart = POSCart(items: [ POSCartItem( item: POSVariation( - id: UUID(), + id: POSItemIdentifier(underlyingType: .variation, itemID: 500), name: "Large", formattedPrice: "$20", price: "20", @@ -296,7 +296,7 @@ struct POSOrderServiceTests { let cart = POSCart(items: [ POSCartItem( item: POSVariation( - id: UUID(), + id: POSItemIdentifier(underlyingType: .variation, itemID: 500), name: "Small", formattedPrice: "$15", price: "15", @@ -308,7 +308,7 @@ struct POSOrderServiceTests { ), POSCartItem( item: POSVariation( - id: UUID(), + id: POSItemIdentifier(underlyingType: .variation, itemID: 501), name: "Large", formattedPrice: "$20", price: "20", @@ -374,7 +374,7 @@ struct POSOrderServiceTests { let cart = POSCart(items: [ POSCartItem( item: POSVariation( - id: UUID(), + id: POSItemIdentifier(underlyingType: .variation, itemID: 500), name: "Large", formattedPrice: "$20", price: "20", @@ -514,7 +514,7 @@ private func makePOSCartItem( productID: Int64, quantity: Decimal) -> POSCartItem { return POSCartItem( - item: POSSimpleProduct(id: UUID(), + item: POSSimpleProduct(id: POSItemIdentifier(underlyingType: .product, itemID: productID), name: "", formattedPrice: "", productID: productID, From 4db81293af1df3349550d5a285076601907e3282 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 16:12:58 +0000 Subject: [PATCH 09/24] Add .error case to POSItemIdentifier.UnderlyingType MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of reusing `.loading` for error states in PurchasableItem.posItemIdentifier, we now have a dedicated `.error` case for clearer semantic meaning. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Modules/Sources/PointOfSale/Models/Cart.swift | 2 +- .../PointOfSale/Items/PointOfSaleItemServiceProtocol.swift | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/Sources/PointOfSale/Models/Cart.swift b/Modules/Sources/PointOfSale/Models/Cart.swift index 5d6e6299625..84fed6e797d 100644 --- a/Modules/Sources/PointOfSale/Models/Cart.swift +++ b/Modules/Sources/PointOfSale/Models/Cart.swift @@ -45,7 +45,7 @@ extension Cart { case .loading: return POSItemIdentifier(underlyingType: .loading, itemID: 0) case .error: - return POSItemIdentifier(underlyingType: .loading, itemID: 0) + return POSItemIdentifier(underlyingType: .error, itemID: 0) } } diff --git a/Modules/Sources/Yosemite/PointOfSale/Items/PointOfSaleItemServiceProtocol.swift b/Modules/Sources/Yosemite/PointOfSale/Items/PointOfSaleItemServiceProtocol.swift index 411931f1c64..24a6b743d4a 100644 --- a/Modules/Sources/Yosemite/PointOfSale/Items/PointOfSaleItemServiceProtocol.swift +++ b/Modules/Sources/Yosemite/PointOfSale/Items/PointOfSaleItemServiceProtocol.swift @@ -35,6 +35,7 @@ public struct POSItemIdentifier: Hashable, Sendable { case variation case coupon case loading + case error } } From 7c100496ea15ab4d2dc36d020cadacedcc97bb98 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 16:13:07 +0000 Subject: [PATCH 10/24] Update preview helpers and views to use POSItemIdentifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated all preview code in views to use the new POSItemIdentifier instead of UUID(). This includes: - CartView, CouponRowView, POSCouponCreationSheet - SimpleProductCardView, VariationCardView, CouponCardView - Auto-generated Models+Copiable.generated.swift Also fixed VariationCardView preview to use .variation instead of .product for the underlyingType. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Modules/Sources/PointOfSale/Presentation/CartView.swift | 2 +- .../Sources/PointOfSale/Presentation/CouponRowView.swift | 5 ++++- .../Presentation/Coupons/POSCouponCreationSheet.swift | 4 +++- .../Presentation/Item Selector/CouponCardView.swift | 4 ++-- .../Presentation/Item Selector/SimpleProductCardView.swift | 2 +- .../Presentation/Item Selector/VariationCardView.swift | 6 +++--- .../Yosemite/Model/Copiable/Models+Copiable.generated.swift | 2 +- 7 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Modules/Sources/PointOfSale/Presentation/CartView.swift b/Modules/Sources/PointOfSale/Presentation/CartView.swift index 49ad0e3cf3e..783435ef9bd 100644 --- a/Modules/Sources/PointOfSale/Presentation/CartView.swift +++ b/Modules/Sources/PointOfSale/Presentation/CartView.swift @@ -479,7 +479,7 @@ private extension CartView { #Preview("Cart with one item") { let posModel = POSPreviewHelpers.makePreviewAggregateModel() - posModel.addToCart(.simpleProduct(.init(id: UUID(), + posModel.addToCart(.simpleProduct(.init(id: .init(underlyingType: .product, itemID: 6), name: "Sample Product", formattedPrice: "$10.00", productID: 6, diff --git a/Modules/Sources/PointOfSale/Presentation/CouponRowView.swift b/Modules/Sources/PointOfSale/Presentation/CouponRowView.swift index 28ff4ce62e1..a45b5a7c479 100644 --- a/Modules/Sources/PointOfSale/Presentation/CouponRowView.swift +++ b/Modules/Sources/PointOfSale/Presentation/CouponRowView.swift @@ -109,6 +109,9 @@ private extension CouponRowView { #if DEBUG #Preview(traits: .sizeThatFitsLayout) { - CouponRowView(couponItem: Cart.CouponItem(id: UUID(), code: "10-Discount", summary: "$10 Off · All products"), couponRowState: .idle) {} + CouponRowView(couponItem: Cart.CouponItem(id: UUID(), + posItemIdentifier: .init(underlyingType: .coupon, itemID: 123), + code: "10-Discount", + summary: "$10 Off · All products"), couponRowState: .idle) {} } #endif diff --git a/Modules/Sources/PointOfSale/Presentation/Coupons/POSCouponCreationSheet.swift b/Modules/Sources/PointOfSale/Presentation/Coupons/POSCouponCreationSheet.swift index 42362b9e6ad..edbaeb6c045 100644 --- a/Modules/Sources/PointOfSale/Presentation/Coupons/POSCouponCreationSheet.swift +++ b/Modules/Sources/PointOfSale/Presentation/Coupons/POSCouponCreationSheet.swift @@ -2,6 +2,7 @@ import SwiftUI import class WooFoundation.CurrencySettings import struct Yosemite.Coupon import enum Yosemite.POSItem +import struct Yosemite.POSItemIdentifier import struct Yosemite.POSCoupon extension View { @@ -31,7 +32,8 @@ private struct POSCouponCreationSheetModifier: ViewModifier { discountType: posDiscountType.discountType, showTypeSelection: $showCouponSelectionSheet, onSuccess: { coupon in - addedCouponItem = .coupon(.init(id: UUID(), code: coupon.code, summary: coupon.summary(currencySettings: currencySettings))) + let id = POSItemIdentifier(underlyingType: .coupon, itemID: coupon.couponID) + addedCouponItem = .coupon(.init(id: id, code: coupon.code, summary: coupon.summary(currencySettings: currencySettings))) }, dismissHandler: { selectedType = nil diff --git a/Modules/Sources/PointOfSale/Presentation/Item Selector/CouponCardView.swift b/Modules/Sources/PointOfSale/Presentation/Item Selector/CouponCardView.swift index 8a54e9489ae..1e1d4b0b24a 100644 --- a/Modules/Sources/PointOfSale/Presentation/Item Selector/CouponCardView.swift +++ b/Modules/Sources/PointOfSale/Presentation/Item Selector/CouponCardView.swift @@ -90,7 +90,7 @@ private extension CouponCardView { .font(.caption) .foregroundStyle(.secondary) CouponCardView(coupon: .init( - id: .init(), + id: .init(underlyingType: .coupon, itemID: 1), code: "Coupon-123", summary: "10% off - All Products" )) @@ -101,7 +101,7 @@ private extension CouponCardView { .font(.caption) .foregroundStyle(.secondary) CouponCardView(coupon: .init( - id: .init(), + id: .init(underlyingType: .coupon, itemID: 2), code: "Old-Coupon-123", summary: "10% off - All Products", dateExpires: Calendar.current.date(byAdding: .month, value: -1, to: Date()) diff --git a/Modules/Sources/PointOfSale/Presentation/Item Selector/SimpleProductCardView.swift b/Modules/Sources/PointOfSale/Presentation/Item Selector/SimpleProductCardView.swift index dc0893027b5..80b5fb761f7 100644 --- a/Modules/Sources/PointOfSale/Presentation/Item Selector/SimpleProductCardView.swift +++ b/Modules/Sources/PointOfSale/Presentation/Item Selector/SimpleProductCardView.swift @@ -60,7 +60,7 @@ private extension SimpleProductCardView { #if DEBUG #Preview { - SimpleProductCardView(product: POSSimpleProduct(id: UUID(), + SimpleProductCardView(product: POSSimpleProduct(id: .init(underlyingType: .product, itemID: 123), name: "Product name", formattedPrice: "$3.00", productID: 123, diff --git a/Modules/Sources/PointOfSale/Presentation/Item Selector/VariationCardView.swift b/Modules/Sources/PointOfSale/Presentation/Item Selector/VariationCardView.swift index cef5c63ca69..a331e1a009d 100644 --- a/Modules/Sources/PointOfSale/Presentation/Item Selector/VariationCardView.swift +++ b/Modules/Sources/PointOfSale/Presentation/Item Selector/VariationCardView.swift @@ -48,7 +48,7 @@ private extension VariationCardView { } #Preview("Variation without image") { - let variation = POSVariation(id: .init(), + let variation = POSVariation(id: .init(underlyingType: .variation, itemID: 256), name: "500ml, double shot", formattedPrice: "$5.00", price: "5.00", @@ -59,13 +59,13 @@ private extension VariationCardView { } #Preview("Variation with image") { - let variation = POSVariation(id: .init(), + let variation = POSVariation(id: .init(underlyingType: .variation, itemID: 257), name: "500ml, double shot", formattedPrice: "$5.00", price: "5.00", productImageSource: "https://pd.w.org/2024/12/986762d0d4d4cf17.82435881-scaled.jpeg", productID: 134, - variationID: 256, + variationID: 257, parentProductName: "Coffee") VariationCardView(variation: variation) } diff --git a/Modules/Sources/Yosemite/Model/Copiable/Models+Copiable.generated.swift b/Modules/Sources/Yosemite/Model/Copiable/Models+Copiable.generated.swift index 1979de9a73e..cc683b9310b 100644 --- a/Modules/Sources/Yosemite/Model/Copiable/Models+Copiable.generated.swift +++ b/Modules/Sources/Yosemite/Model/Copiable/Models+Copiable.generated.swift @@ -111,7 +111,7 @@ extension Yosemite.POSOrder { extension Yosemite.POSSimpleProduct { public func copy( - id: CopiableProp = .copy, + id: CopiableProp = .copy, name: CopiableProp = .copy, formattedPrice: CopiableProp = .copy, productImageSource: NullableCopiableProp = .copy, From 0f7018bb946a8b7bffaef6cb06fcb50dc20bb9bf Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 16:25:45 +0000 Subject: [PATCH 11/24] Make identifier copyable and fakable --- Modules/Sources/Fakes/Yosemite.generated.swift | 17 +++++++++++++++++ .../Copiable/Models+Copiable.generated.swift | 16 ++++++++++++++++ .../Items/PointOfSaleItemServiceProtocol.swift | 5 +++-- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/Modules/Sources/Fakes/Yosemite.generated.swift b/Modules/Sources/Fakes/Yosemite.generated.swift index 4b4a8b785b3..dcf5c29cc45 100644 --- a/Modules/Sources/Fakes/Yosemite.generated.swift +++ b/Modules/Sources/Fakes/Yosemite.generated.swift @@ -35,6 +35,23 @@ extension Yosemite.JustInTimeMessageTemplate { .banner } } +extension Yosemite.POSItemIdentifier { + /// Returns a "ready to use" type filled with fake values. + /// + public static func fake() -> Yosemite.POSItemIdentifier { + .init( + underlyingType: .fake(), + itemID: .fake() + ) + } +} +extension Yosemite.POSItemIdentifier.UnderlyingType { + /// Returns a "ready to use" type filled with fake values. + /// + public static func fake() -> Yosemite.POSItemIdentifier.UnderlyingType { + .product + } +} extension Yosemite.POSSimpleProduct { /// Returns a "ready to use" type filled with fake values. /// diff --git a/Modules/Sources/Yosemite/Model/Copiable/Models+Copiable.generated.swift b/Modules/Sources/Yosemite/Model/Copiable/Models+Copiable.generated.swift index cc683b9310b..39f883f2a13 100644 --- a/Modules/Sources/Yosemite/Model/Copiable/Models+Copiable.generated.swift +++ b/Modules/Sources/Yosemite/Model/Copiable/Models+Copiable.generated.swift @@ -5,6 +5,7 @@ import Foundation import Networking import WooFoundation import enum NetworkingCore.OrderStatusEnum +import struct Networking.PagedItems import struct NetworkingCore.Address import struct NetworkingCore.MetaData import struct NetworkingCore.Order @@ -58,6 +59,21 @@ extension Yosemite.JustInTimeMessage { } } +extension Yosemite.POSItemIdentifier { + public func copy( + underlyingType: CopiableProp = .copy, + itemID: CopiableProp = .copy + ) -> Yosemite.POSItemIdentifier { + let underlyingType = underlyingType ?? self.underlyingType + let itemID = itemID ?? self.itemID + + return Yosemite.POSItemIdentifier( + underlyingType: underlyingType, + itemID: itemID + ) + } +} + extension Yosemite.POSOrder { public func copy( id: CopiableProp = .copy, diff --git a/Modules/Sources/Yosemite/PointOfSale/Items/PointOfSaleItemServiceProtocol.swift b/Modules/Sources/Yosemite/PointOfSale/Items/PointOfSaleItemServiceProtocol.swift index 24a6b743d4a..adcf2c1d83f 100644 --- a/Modules/Sources/Yosemite/PointOfSale/Items/PointOfSaleItemServiceProtocol.swift +++ b/Modules/Sources/Yosemite/PointOfSale/Items/PointOfSaleItemServiceProtocol.swift @@ -1,5 +1,6 @@ import Foundation import struct Networking.PagedItems +import Codegen public enum POSItem: Equatable, Identifiable, Hashable { case simpleProduct(POSSimpleProduct) @@ -21,7 +22,7 @@ public enum POSItem: Equatable, Identifiable, Hashable { } } -public struct POSItemIdentifier: Hashable, Sendable { +public struct POSItemIdentifier: Hashable, Sendable, GeneratedCopiable, GeneratedFakeable { public let underlyingType: UnderlyingType public let itemID: Int64 @@ -30,7 +31,7 @@ public struct POSItemIdentifier: Hashable, Sendable { self.itemID = itemID } - public enum UnderlyingType: Sendable { + public enum UnderlyingType: Sendable, GeneratedCopiable, GeneratedFakeable { case product case variation case coupon From 6b34d33aeb502e0c29874f5b239bba5f9fdca4ad Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 16:30:14 +0000 Subject: [PATCH 12/24] Update MockPOSOrderableItem to use POSItemIdentifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed id property from UUID to POSItemIdentifier to match the updated POSOrderableItem protocol. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Tests/PointOfSaleTests/Mocks/MockPOSOrderableItem.swift | 4 ++-- Modules/Tests/YosemiteTests/Mocks/MockPOSOrderableItem.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/Tests/PointOfSaleTests/Mocks/MockPOSOrderableItem.swift b/Modules/Tests/PointOfSaleTests/Mocks/MockPOSOrderableItem.swift index b14560fc077..d8e5b516e2e 100644 --- a/Modules/Tests/PointOfSaleTests/Mocks/MockPOSOrderableItem.swift +++ b/Modules/Tests/PointOfSaleTests/Mocks/MockPOSOrderableItem.swift @@ -3,12 +3,12 @@ import Foundation final class MockPOSOrderableItem: POSOrderableItem, Equatable { var name: String - var id: UUID + var id: POSItemIdentifier var formattedPrice: String var productImageSource: String? init(name: String, - id: UUID = UUID(), + id: POSItemIdentifier = POSItemIdentifier(underlyingType: .product, itemID: 1), formattedPrice: String, productImageSource: String? = nil, orderItemsToMatch: [OrderItem] = [], diff --git a/Modules/Tests/YosemiteTests/Mocks/MockPOSOrderableItem.swift b/Modules/Tests/YosemiteTests/Mocks/MockPOSOrderableItem.swift index b14560fc077..d8e5b516e2e 100644 --- a/Modules/Tests/YosemiteTests/Mocks/MockPOSOrderableItem.swift +++ b/Modules/Tests/YosemiteTests/Mocks/MockPOSOrderableItem.swift @@ -3,12 +3,12 @@ import Foundation final class MockPOSOrderableItem: POSOrderableItem, Equatable { var name: String - var id: UUID + var id: POSItemIdentifier var formattedPrice: String var productImageSource: String? init(name: String, - id: UUID = UUID(), + id: POSItemIdentifier = POSItemIdentifier(underlyingType: .product, itemID: 1), formattedPrice: String, productImageSource: String? = nil, orderItemsToMatch: [OrderItem] = [], From 93810fb17b9af51756c14c787f8cfec77782ea16 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 16:32:21 +0000 Subject: [PATCH 13/24] Update MockPointOfSaleCouponService to use POSItemIdentifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed coupon ID creation from UUID to POSItemIdentifier with sequential itemID values for test data. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Mocks/MockPointOfSaleCouponService.swift | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Modules/Tests/PointOfSaleTests/Mocks/MockPointOfSaleCouponService.swift b/Modules/Tests/PointOfSaleTests/Mocks/MockPointOfSaleCouponService.swift index a24ab972d91..3b3673b7830 100644 --- a/Modules/Tests/PointOfSaleTests/Mocks/MockPointOfSaleCouponService.swift +++ b/Modules/Tests/PointOfSaleTests/Mocks/MockPointOfSaleCouponService.swift @@ -3,6 +3,7 @@ import protocol Yosemite.PointOfSaleItemServiceProtocol import protocol Yosemite.PointOfSaleCouponServiceProtocol import enum Yosemite.POSItem import struct Yosemite.POSCoupon +import struct Yosemite.POSItemIdentifier import struct Yosemite.PagedItems import struct Yosemite.POSVariableParentProduct import enum Yosemite.PointOfSaleCouponServiceError @@ -34,16 +35,16 @@ final class MockPointOfSaleCouponService: PointOfSaleCouponServiceProtocol { } static func makeInitialCoupons() -> [POSItem] { - let coupon1 = POSItem.coupon(POSCoupon(id: UUID(uuidString: ("DC55E3B9-9D83-4C07-82A7-4C300A50E84A")) ?? UUID(), code: "VALID1")) - let coupon2 = POSItem.coupon(POSCoupon(id: UUID(uuidString: ("DC55E3B9-9D83-4C07-82A7-4C300A50E84B")) ?? UUID(), code: "VALID2")) - let coupon3 = POSItem.coupon(POSCoupon(id: UUID(uuidString: ("DC55E3B9-9D83-4C07-82A7-4C300A50E84C")) ?? UUID(), code: "VALID3")) + let coupon1 = POSItem.coupon(POSCoupon(id: POSItemIdentifier(underlyingType: .coupon, itemID: 1), code: "VALID1")) + let coupon2 = POSItem.coupon(POSCoupon(id: POSItemIdentifier(underlyingType: .coupon, itemID: 2), code: "VALID2")) + let coupon3 = POSItem.coupon(POSCoupon(id: POSItemIdentifier(underlyingType: .coupon, itemID: 3), code: "VALID3")) return [coupon1, coupon2, coupon3] } static func makeSecondPageCoupons() -> [POSItem] { - let coupon4 = POSItem.coupon(POSCoupon(id: UUID(uuidString: ("DC55E3B9-9D83-4C07-82A7-4C300A50E84D")) ?? UUID(), code: "VALID4")) - let coupon5 = POSItem.coupon(POSCoupon(id: UUID(uuidString: ("DC55E3B9-9D83-4C07-82A7-4C300A50E84E")) ?? UUID(), code: "VALID5")) - let coupon6 = POSItem.coupon(POSCoupon(id: UUID(uuidString: ("DC55E3B9-9D83-4C07-82A7-4C300A50E84F")) ?? UUID(), code: "VALID6")) + let coupon4 = POSItem.coupon(POSCoupon(id: POSItemIdentifier(underlyingType: .coupon, itemID: 4), code: "VALID4")) + let coupon5 = POSItem.coupon(POSCoupon(id: POSItemIdentifier(underlyingType: .coupon, itemID: 5), code: "VALID5")) + let coupon6 = POSItem.coupon(POSCoupon(id: POSItemIdentifier(underlyingType: .coupon, itemID: 6), code: "VALID6")) return [coupon4, coupon5, coupon6] } From fe6d2ff4f4481aabbb11884701a36ec9b3c23a21 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 16:38:31 +0000 Subject: [PATCH 14/24] Update POSCartItemTests.makeCartItem to use POSItemIdentifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed the id parameter type from UUID to POSItemIdentifier in the makeCartItem helper function. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Modules/Tests/YosemiteTests/Tools/POS/POSCartItemTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Tests/YosemiteTests/Tools/POS/POSCartItemTests.swift b/Modules/Tests/YosemiteTests/Tools/POS/POSCartItemTests.swift index ddefced8512..21594667aca 100644 --- a/Modules/Tests/YosemiteTests/Tools/POS/POSCartItemTests.swift +++ b/Modules/Tests/YosemiteTests/Tools/POS/POSCartItemTests.swift @@ -101,7 +101,7 @@ struct POSCartItemTests { #expect(sut.matches(order: order) == false) } - private func makeCartItem(id: UUID = UUID(), quantity: Decimal, matching: [OrderItem] = [], matcher: ((OrderItem) -> Bool)? = nil) -> POSCartItem { + private func makeCartItem(id: POSItemIdentifier = POSItemIdentifier(underlyingType: .product, itemID: 1), quantity: Decimal, matching: [OrderItem] = [], matcher: ((OrderItem) -> Bool)? = nil) -> POSCartItem { return POSCartItem(item: MockPOSOrderableItem(name: "", id: id, formattedPrice: "", From 6abb8bef1a55a268a18f89e5b8c68b287e1f352c Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 16:43:36 +0000 Subject: [PATCH 15/24] Fix POSItemActionHandlerFactoryTests helper functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated makeVariationItem() to use POSItemIdentifier instead of .init() and fixed makeCouponItem() to use .coupon instead of .product for the underlyingType. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Presentation/POSItemActionHandlerFactoryTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/Tests/PointOfSaleTests/Presentation/POSItemActionHandlerFactoryTests.swift b/Modules/Tests/PointOfSaleTests/Presentation/POSItemActionHandlerFactoryTests.swift index 16bde0c63bd..a988c921664 100644 --- a/Modules/Tests/PointOfSaleTests/Presentation/POSItemActionHandlerFactoryTests.swift +++ b/Modules/Tests/PointOfSaleTests/Presentation/POSItemActionHandlerFactoryTests.swift @@ -169,12 +169,12 @@ private func makeProductItem() -> POSItem { } private func makeCouponItem() -> POSItem { - return .coupon(.init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "DISCOUNT!")) + return .coupon(.init(id: POSItemIdentifier(underlyingType: .coupon, itemID: 1), code: "DISCOUNT!")) } private func makeVariationItem() -> POSItem { return .variation(.init( - id: .init(), + id: POSItemIdentifier(underlyingType: .variation, itemID: 1), name: "Test", formattedPrice: "$2.00", price: "2", From 9e258210c41ff406ac1469dfdc14c0839c4bd489 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 16:46:17 +0000 Subject: [PATCH 16/24] Fix CartViewHelperTests to use correct Cart item constructors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated Cart.CouponItem and Cart.PurchasableItem test instantiations to use UUID for the id parameter and POSItemIdentifier for posItemIdentifier parameter, matching the updated dual-identifier architecture. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../ViewHelpers/CartViewHelperTests.swift | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Modules/Tests/PointOfSaleTests/ViewHelpers/CartViewHelperTests.swift b/Modules/Tests/PointOfSaleTests/ViewHelpers/CartViewHelperTests.swift index 72b9001ba01..f106771c17b 100644 --- a/Modules/Tests/PointOfSaleTests/ViewHelpers/CartViewHelperTests.swift +++ b/Modules/Tests/PointOfSaleTests/ViewHelpers/CartViewHelperTests.swift @@ -1,6 +1,7 @@ import Foundation import Testing @testable import PointOfSale +import struct Yosemite.POSItemIdentifier struct CartViewHelperTests { let sut = CartViewHelper() @@ -114,7 +115,7 @@ struct CartViewHelperTests { @Test func couponRowState_building_stage_returns_idle() async throws { // Given - let coupon = Cart.CouponItem(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "TEST10", summary: "") + let coupon = Cart.CouponItem(id: UUID(), posItemIdentifier: POSItemIdentifier(underlyingType: .coupon, itemID: 1), code: "TEST10", summary: "") let orderState = PointOfSaleOrderState.loaded(PointOfSaleOrderTotals( cartTotal: "$10.00", orderTotal: "$12.00", @@ -129,7 +130,7 @@ struct CartViewHelperTests { @Test func couponRowState_finalizing_and_syncing_returns_validating() async throws { // Given - let coupon = Cart.CouponItem(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "TEST10", summary: "") + let coupon = Cart.CouponItem(id: UUID(), posItemIdentifier: POSItemIdentifier(underlyingType: .coupon, itemID: 1), code: "TEST10", summary: "") // When, Then #expect(sut.couponRowState(orderStage: .finalizing, @@ -140,7 +141,7 @@ struct CartViewHelperTests { @Test func couponRowState_finalizing_and_loaded_with_matching_coupon_returns_valid() async throws { // Given let couponCode = "TEST10" - let coupon = Cart.CouponItem(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: couponCode, summary: "") + let coupon = Cart.CouponItem(id: UUID(), posItemIdentifier: POSItemIdentifier(underlyingType: .coupon, itemID: 1), code: couponCode, summary: "") let couponTotal = PointOfSaleCouponTotal(code: couponCode, total: "10.00") let orderTotals = PointOfSaleOrderTotals( cartTotal: "$10.00", @@ -157,7 +158,7 @@ struct CartViewHelperTests { @Test func couponRowState_finalizing_and_loaded_with_no_matching_coupon_returns_idle() async throws { // Given - let coupon = Cart.CouponItem(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "TEST10", summary: "") + let coupon = Cart.CouponItem(id: UUID(), posItemIdentifier: POSItemIdentifier(underlyingType: .coupon, itemID: 1), code: "TEST10", summary: "") let orderTotals = PointOfSaleOrderTotals( cartTotal: "$10.00", orderTotal: "$12.00", @@ -173,7 +174,7 @@ struct CartViewHelperTests { @Test func couponRowState_finalizing_and_invalid_coupon_error_returns_invalid() async throws { // Given - let coupon = Cart.CouponItem(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "TEST10", summary: "") + let coupon = Cart.CouponItem(id: UUID(), posItemIdentifier: POSItemIdentifier(underlyingType: .coupon, itemID: 1), code: "TEST10", summary: "") // When, Then #expect(sut.couponRowState(orderStage: .finalizing, @@ -183,7 +184,7 @@ struct CartViewHelperTests { @Test func couponRowState_finalizing_and_other_error_returns_idle() async throws { // Given - let coupon = Cart.CouponItem(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "TEST10", summary: "") + let coupon = Cart.CouponItem(id: UUID(), posItemIdentifier: POSItemIdentifier(underlyingType: .coupon, itemID: 1), code: "TEST10", summary: "") // When, Then #expect(sut.couponRowState(orderStage: .finalizing, @@ -233,7 +234,7 @@ struct CartViewHelperTests { @Test func hasUnresolvedItems_when_cart_has_loading_item_returns_true() async throws { // Given - let loadingItem = Cart.PurchasableItem.loading(id: POSItemIdentifier(underlyingType: .product, itemID: 1)) + let loadingItem = Cart.PurchasableItem.loading(id: UUID()) let cart = Cart(purchasableItems: [loadingItem]) // When, Then @@ -243,7 +244,7 @@ struct CartViewHelperTests { @Test func hasUnresolvedItems_when_cart_has_error_item_returns_true() async throws { // Given let errorItem = Cart.PurchasableItem( - id: POSItemIdentifier(underlyingType: .product, itemID: 1), + id: UUID(), title: "", subtitle: "", quantity: 1, @@ -257,7 +258,7 @@ struct CartViewHelperTests { @Test func hasUnresolvedItems_when_cart_has_mixed_items_returns_true() async throws { // Given - let loadingItem = Cart.PurchasableItem.loading(id: POSItemIdentifier(underlyingType: .product, itemID: 1)) + let loadingItem = Cart.PurchasableItem.loading(id: UUID()) let loadedItem = makeItem() let cart = Cart(purchasableItems: [loadingItem, loadedItem]) @@ -267,7 +268,7 @@ struct CartViewHelperTests { } private func makeItem() -> Cart.PurchasableItem { - .init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), + .init(id: UUID(), item: MockPOSOrderableItem(name: "Item", formattedPrice: "$1.00"), title: "Item", subtitle: nil, From 3aa8ceb98849911b4f6e09fa2a87c56d4a8e5ec8 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 16:46:29 +0000 Subject: [PATCH 17/24] Add POSItemIdentifier import to POSItemActionHandlerTests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added missing import for POSItemIdentifier. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Presentation/POSItemActionHandlerTests.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/Tests/PointOfSaleTests/Presentation/POSItemActionHandlerTests.swift b/Modules/Tests/PointOfSaleTests/Presentation/POSItemActionHandlerTests.swift index 3f403a29202..0f3c82de684 100644 --- a/Modules/Tests/PointOfSaleTests/Presentation/POSItemActionHandlerTests.swift +++ b/Modules/Tests/PointOfSaleTests/Presentation/POSItemActionHandlerTests.swift @@ -2,6 +2,7 @@ import Testing import Foundation import enum Yosemite.POSItem import enum Yosemite.POSItemType +import struct Yosemite.POSItemIdentifier import protocol Yosemite.PointOfSaleBarcodeScanServiceProtocol import protocol Yosemite.POSSearchHistoryProviding @testable import PointOfSale From 3741ea42e0db0c42a5daaeaa80f43c8481d779a7 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 16:50:05 +0000 Subject: [PATCH 18/24] Fix TotalsViewHelperTests to use POSItemIdentifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated POSCoupon initialization to use POSItemIdentifier instead of .init() for the id parameter. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../ViewHelpers/TotalsViewHelperTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/Tests/PointOfSaleTests/ViewHelpers/TotalsViewHelperTests.swift b/Modules/Tests/PointOfSaleTests/ViewHelpers/TotalsViewHelperTests.swift index 027524be2c2..6fceff81251 100644 --- a/Modules/Tests/PointOfSaleTests/ViewHelpers/TotalsViewHelperTests.swift +++ b/Modules/Tests/PointOfSaleTests/ViewHelpers/TotalsViewHelperTests.swift @@ -139,7 +139,7 @@ struct TotalsViewHelperTests { @Test func test_shouldShowTotalDiscountField_returns_true_when_cart_has_coupons_order_syncing() { var cart = Cart() - cart.add(.coupon(.init(id: .init(), code: "TEST10", summary: ""))) + cart.add(.coupon(.init(id: POSItemIdentifier(underlyingType: .coupon, itemID: 1), code: "TEST10", summary: ""))) #expect(TotalsViewHelper().shouldShowTotalDiscountField(cart: cart, orderTotals: nil)) } @@ -147,7 +147,7 @@ struct TotalsViewHelperTests { @Test func test_shouldShowTotalDiscountField_returns_true_when_cart_has_coupons_and_orderTotals_with_discount() { var cart = Cart() - cart.add(.coupon(.init(id: .init(), code: "TEST10", summary: ""))) + cart.add(.coupon(.init(id: POSItemIdentifier(underlyingType: .coupon, itemID: 1), code: "TEST10", summary: ""))) let orderTotals = PointOfSaleOrderTotals(cartTotal: "10", orderTotal: "8", taxTotal: "0", @@ -160,7 +160,7 @@ struct TotalsViewHelperTests { @Test func test_shouldShowTotalDiscountField_returns_false_when_cart_has_coupons_and_orderTotals_without_discount() { var cart = Cart() - cart.add(.coupon(.init(id: .init(), code: "TEST10", summary: ""))) + cart.add(.coupon(.init(id: POSItemIdentifier(underlyingType: .coupon, itemID: 1), code: "TEST10", summary: ""))) let orderTotals = PointOfSaleOrderTotals(cartTotal: "10", orderTotal: "10", taxTotal: "0", From 33569f4242baa440d120a394eb53bbf7cb9e51a8 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 16:50:18 +0000 Subject: [PATCH 19/24] Add POSItemIdentifier imports to test files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added missing imports for POSItemIdentifier in test files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Controllers/PointOfSaleOrderControllerTests.swift | 1 + .../Presentation/POSItemActionHandlerFactoryTests.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleOrderControllerTests.swift b/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleOrderControllerTests.swift index af796860b67..e0809bd3e80 100644 --- a/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleOrderControllerTests.swift +++ b/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleOrderControllerTests.swift @@ -13,6 +13,7 @@ import class WooFoundation.CurrencySettings import protocol WooFoundation.Analytics import enum Networking.DotcomError import enum Networking.NetworkError +import struct Yosemite.POSItemIdentifier struct PointOfSaleOrderControllerTests { let mockOrderService = MockPOSOrderService() diff --git a/Modules/Tests/PointOfSaleTests/Presentation/POSItemActionHandlerFactoryTests.swift b/Modules/Tests/PointOfSaleTests/Presentation/POSItemActionHandlerFactoryTests.swift index a988c921664..c5f7a6a811e 100644 --- a/Modules/Tests/PointOfSaleTests/Presentation/POSItemActionHandlerFactoryTests.swift +++ b/Modules/Tests/PointOfSaleTests/Presentation/POSItemActionHandlerFactoryTests.swift @@ -3,6 +3,7 @@ import Foundation @testable import PointOfSale import WooFoundation import enum Yosemite.POSItem +import struct Yosemite.POSItemIdentifier private enum AnalyticsKeys { static let source = "source" From a02954ce224af0f8cb3c9bcd675213634653affa Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 16:53:41 +0000 Subject: [PATCH 20/24] Fix PointOfSaleOrderControllerTests to use dual-identifier architecture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated Cart.CouponItem instantiations to use UUID for id and POSItemIdentifier for posItemIdentifier. Fixed makeItem() helper to use UUID instead of POSItemIdentifier. Changed coupon underlyingType from .product to .coupon. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../PointOfSaleOrderControllerTests.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleOrderControllerTests.swift b/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleOrderControllerTests.swift index e0809bd3e80..7facfe9b47b 100644 --- a/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleOrderControllerTests.swift +++ b/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleOrderControllerTests.swift @@ -431,12 +431,12 @@ struct PointOfSaleOrderControllerTests { mockOrderService.orderToReturn = fakeOrder // Initial sync to set up the order - await sut.syncOrder(for: Cart(purchasableItems: [cartItem], coupons: [.init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: couponCode, summary: "")]), retryHandler: {}) + await sut.syncOrder(for: Cart(purchasableItems: [cartItem], coupons: [.init(id: UUID(), posItemIdentifier: POSItemIdentifier(underlyingType: .coupon, itemID: 1), code: couponCode, summary: "")]), retryHandler: {}) mockOrderService.syncOrderWasCalled = false // When - sync with same items and coupons - await sut.syncOrder(for: Cart(purchasableItems: [cartItem], coupons: [.init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: couponCode, summary: "")]), retryHandler: {}) + await sut.syncOrder(for: Cart(purchasableItems: [cartItem], coupons: [.init(id: UUID(), posItemIdentifier: POSItemIdentifier(underlyingType: .coupon, itemID: 1), code: couponCode, summary: "")]), retryHandler: {}) // Then #expect(mockOrderService.syncOrderWasCalled == false) @@ -456,12 +456,12 @@ struct PointOfSaleOrderControllerTests { mockOrderService.orderToReturn = fakeOrder // Initial sync - await sut.syncOrder(for: Cart(purchasableItems: [cartItem], coupons: [.init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: initialCouponCode, summary: "")]), retryHandler: {}) + await sut.syncOrder(for: Cart(purchasableItems: [cartItem], coupons: [.init(id: UUID(), posItemIdentifier: POSItemIdentifier(underlyingType: .coupon, itemID: 1), code: initialCouponCode, summary: "")]), retryHandler: {}) mockOrderService.syncOrderWasCalled = false // When - sync with same items but different coupon - await sut.syncOrder(for: Cart(purchasableItems: [cartItem], coupons: [.init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "DIFFERENT20", summary: "")]), retryHandler: {}) + await sut.syncOrder(for: Cart(purchasableItems: [cartItem], coupons: [.init(id: UUID(), posItemIdentifier: POSItemIdentifier(underlyingType: .coupon, itemID: 2), code: "DIFFERENT20", summary: "")]), retryHandler: {}) // Then #expect(mockOrderService.syncOrderWasCalled == true) @@ -481,7 +481,7 @@ struct PointOfSaleOrderControllerTests { mockOrderService.orderToReturn = fakeOrder // Initial sync with coupon - await sut.syncOrder(for: Cart(purchasableItems: [cartItem], coupons: [.init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: couponCode, summary: "")]), retryHandler: {}) + await sut.syncOrder(for: Cart(purchasableItems: [cartItem], coupons: [.init(id: UUID(), posItemIdentifier: POSItemIdentifier(underlyingType: .coupon, itemID: 1), code: couponCode, summary: "")]), retryHandler: {}) mockOrderService.syncOrderWasCalled = false @@ -519,7 +519,7 @@ struct PointOfSaleOrderControllerTests { // When await sut.syncOrder(for: Cart(purchasableItems: [makeItem()], - coupons: [.init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "INVALID", summary: "")]), + coupons: [.init(id: UUID(), posItemIdentifier: POSItemIdentifier(underlyingType: .coupon, itemID: 1), code: "INVALID", summary: "")]), retryHandler: {}) } @@ -567,7 +567,7 @@ struct PointOfSaleOrderControllerTests { // When await sut.syncOrder(for: Cart(purchasableItems: [makeItem()], - coupons: [.init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), code: "INVALID", summary: "")]), + coupons: [.init(id: UUID(), posItemIdentifier: POSItemIdentifier(underlyingType: .coupon, itemID: 1), code: "INVALID", summary: "")]), retryHandler: {}) } @@ -701,7 +701,7 @@ private func makeItem(name: String = "", formattedPrice: String = "", quantity: Int = 1, orderItemsToMatch: [OrderItem] = []) -> Cart.PurchasableItem { - return .init(id: POSItemIdentifier(underlyingType: .product, itemID: 1), + return .init(id: UUID(), item: MockPOSOrderableItem(name: name, formattedPrice: formattedPrice, orderItemsToMatch: orderItemsToMatch), From bfd4b2d3ed26f680f3f5aa44404e67d681257681 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 16:58:19 +0000 Subject: [PATCH 21/24] Add missing POSItemIdentifier import to TotalsViewHelperTests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes compilation errors where POSItemIdentifier was not found in scope. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../PointOfSaleTests/ViewHelpers/TotalsViewHelperTests.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/Tests/PointOfSaleTests/ViewHelpers/TotalsViewHelperTests.swift b/Modules/Tests/PointOfSaleTests/ViewHelpers/TotalsViewHelperTests.swift index 6fceff81251..139c8102f28 100644 --- a/Modules/Tests/PointOfSaleTests/ViewHelpers/TotalsViewHelperTests.swift +++ b/Modules/Tests/PointOfSaleTests/ViewHelpers/TotalsViewHelperTests.swift @@ -1,5 +1,6 @@ import Testing @testable import PointOfSale +import struct Yosemite.POSItemIdentifier struct TotalsViewHelperTests { From c5362dd23258fcfe63ec8e9b93a77c11ef2b4598 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 17:00:04 +0000 Subject: [PATCH 22/24] Fix test helper functions in PointOfSaleObservableItemsControllerTests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated makeSimpleProduct and makeVariation helper functions to use POSItemIdentifier instead of UUID, matching the updated type signatures. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../PointOfSaleObservableItemsControllerTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleObservableItemsControllerTests.swift b/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleObservableItemsControllerTests.swift index 9b8b0541bcb..fc39beed888 100644 --- a/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleObservableItemsControllerTests.swift +++ b/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleObservableItemsControllerTests.swift @@ -7,9 +7,9 @@ final class PointOfSaleObservableItemsControllerTests { // MARK: - Test Helpers - private func makeSimpleProduct(id: UUID = UUID(), name: String = "Test Product", productID: Int64 = 1) -> POSItem { + private func makeSimpleProduct(id: POSItemIdentifier? = nil, name: String = "Test Product", productID: Int64 = 1) -> POSItem { .simpleProduct(POSSimpleProduct( - id: id, + id: id ?? POSItemIdentifier(underlyingType: .product, itemID: productID), name: name, formattedPrice: "$2.00", productID: productID, @@ -20,9 +20,9 @@ final class PointOfSaleObservableItemsControllerTests { )) } - private func makeVariation(id: UUID = UUID(), name: String = "Test Variation", variationID: Int64 = 1) -> POSItem { + private func makeVariation(id: POSItemIdentifier? = nil, name: String = "Test Variation", variationID: Int64 = 1) -> POSItem { .variation(POSVariation( - id: id, + id: id ?? POSItemIdentifier(underlyingType: .variation, itemID: variationID), name: name, formattedPrice: "$2.00", price: "2.00", From ca33409899f4c46ccb8451bd27fecfd17c078c74 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 17:01:31 +0000 Subject: [PATCH 23/24] Add missing POSItemIdentifier import to PointOfSaleItemsControllerTests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes compilation errors where POSItemIdentifier was not found in scope. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Controllers/PointOfSaleItemsControllerTests.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleItemsControllerTests.swift b/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleItemsControllerTests.swift index bc9e22c6730..53d2f004475 100644 --- a/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleItemsControllerTests.swift +++ b/Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleItemsControllerTests.swift @@ -3,6 +3,7 @@ import Foundation @testable import PointOfSale import struct Yosemite.POSVariableParentProduct import enum Yosemite.POSItem +import struct Yosemite.POSItemIdentifier import enum Yosemite.PointOfSaleItemServiceError import class Yosemite.PointOfSaleItemFetchStrategyFactory @testable import struct Yosemite.PointOfSaleSearchPurchasableItemFetchStrategy From f076409e7576196d74c0ff8f751cb1cd118e1ccd Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Fri, 28 Nov 2025 17:03:30 +0000 Subject: [PATCH 24/24] Fix import in aggregate model tests --- .../PointOfSaleTests/Models/PointOfSaleAggregateModelTests.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/Tests/PointOfSaleTests/Models/PointOfSaleAggregateModelTests.swift b/Modules/Tests/PointOfSaleTests/Models/PointOfSaleAggregateModelTests.swift index 870ffb58f35..ea4b4a2dc6c 100644 --- a/Modules/Tests/PointOfSaleTests/Models/PointOfSaleAggregateModelTests.swift +++ b/Modules/Tests/PointOfSaleTests/Models/PointOfSaleAggregateModelTests.swift @@ -5,6 +5,7 @@ import protocol WooFoundation.Analytics import protocol Yosemite.PointOfSaleBarcodeScanServiceProtocol import protocol Yosemite.POSOrderableItem import enum Yosemite.POSItem +import struct Yosemite.POSItemIdentifier @testable import struct Yosemite.POSSimpleProduct import struct Yosemite.Order import protocol Yosemite.POSSearchHistoryProviding