Skip to content

Commit e9d57c1

Browse files
committed
Update variation list when attributes change
1 parent b357920 commit e9d57c1

File tree

3 files changed

+87
-8
lines changed

3 files changed

+87
-8
lines changed

Modules/Sources/Yosemite/PointOfSale/Items/GRDBObservableDataSource.swift

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,47 @@ public final class GRDBObservableDataSource: POSObservableDataSourceProtocol {
160160

161161
private func setupVariationObservation(parentProduct: POSVariableParentProduct) {
162162
let currentPage = currentVariationPage
163+
let parentProductID = parentProduct.productID
164+
165+
struct ObservationResult {
166+
let variations: [POSProductVariation]
167+
let parentProduct: POSVariableParentProduct
168+
}
169+
163170
let observation = ValueObservation
164-
.tracking { [weak self] database -> [POSProductVariation] in
165-
guard let self else { return [] }
171+
.tracking { [weak self] database -> ObservationResult in
172+
guard let self else { return ObservationResult(variations: [], parentProduct: parentProduct) }
173+
174+
// Fetch parent product with updated attributes
175+
struct ParentProductWithAttributes: Decodable, FetchableRecord {
176+
let product: PersistedProduct
177+
let attributes: [PersistedProductAttribute]?
178+
}
179+
180+
let parentWithAttributes = try PersistedProduct
181+
.filter(PersistedProduct.Columns.siteID == self.siteID)
182+
.filter(PersistedProduct.Columns.id == parentProductID)
183+
.including(all: PersistedProduct.attributes)
184+
.asRequest(of: ParentProductWithAttributes.self)
185+
.fetchOne(database)
186+
187+
// Create updated parent product with fresh attributes
188+
let updatedParentProduct: POSVariableParentProduct
189+
if let parentWithAttributes {
190+
let attributes = (parentWithAttributes.attributes ?? []).map {
191+
$0.toProductAttribute(siteID: parentWithAttributes.product.siteID)
192+
}
193+
let product = parentWithAttributes.product
194+
updatedParentProduct = POSVariableParentProduct(
195+
id: parentProduct.id,
196+
name: product.name,
197+
productImageSource: nil, // Image not needed for variation name generation
198+
productID: product.id,
199+
allAttributes: attributes
200+
)
201+
} else {
202+
updatedParentProduct = parentProduct
203+
}
166204

167205
struct VariationWithRelations: Decodable, FetchableRecord {
168206
let persistedProductVariation: PersistedProductVariation
@@ -171,36 +209,42 @@ public final class GRDBObservableDataSource: POSObservableDataSourceProtocol {
171209
}
172210

173211
let variationsWithRelations = try PersistedProductVariation
174-
.posVariationsRequest(siteID: self.siteID, parentProductID: parentProduct.productID)
212+
.posVariationsRequest(siteID: self.siteID, parentProductID: parentProductID)
175213
.limit(self.pageSize * currentPage)
176214
.including(all: PersistedProductVariation.attributes)
177215
.including(optional: PersistedProductVariation.image)
178216
.asRequest(of: VariationWithRelations.self)
179217
.fetchAll(database)
180218

181-
return variationsWithRelations.map { record in
219+
let variations = variationsWithRelations.map { record in
182220
record.persistedProductVariation.toPOSProductVariation(
183221
attributes: (record.attributes ?? []).map { $0.toProductVariationAttribute() },
184222
image: record.image?.toProductImage()
185223
)
186224
}
225+
226+
return ObservationResult(variations: variations, parentProduct: updatedParentProduct)
187227
}
188228

189229
variationObservationCancellable = observation
190230
.publisher(in: grdbManager.databaseConnection)
191231
.receive(on: DispatchQueue.main)
192232
.sink(
193-
receiveCompletion: { [weak self] completion in
233+
receiveCompletion: { [weak self] (completion: Subscribers.Completion<Error>) in
194234
if case .failure(let error) = completion {
195235
self?.variationError = error
196236
self?.isLoadingVariations = false
197237
}
198238
},
199-
receiveValue: { [weak self] observedVariations in
239+
receiveValue: { [weak self] (result: ObservationResult) in
200240
guard let self else { return }
241+
242+
// Update current parent product with fresh attributes
243+
self.currentParentProduct = result.parentProduct
244+
201245
let posItems = itemMapper.mapVariationsToPOSItems(
202-
variations: observedVariations,
203-
parentProduct: parentProduct
246+
variations: result.variations,
247+
parentProduct: result.parentProduct
204248
)
205249
variationItems = posItems
206250
variationError = nil

Modules/Sources/Yosemite/Tools/POS/POSCatalogPersistenceService.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ final class POSCatalogPersistenceService: POSCatalogPersistenceServiceProtocol {
116116
}
117117

118118
for variation in catalog.variationsToPersist {
119+
// Delete attributes for updated variations, the remaining set will be recreated later in the save
120+
try PersistedProductVariationAttribute
121+
.filter(PersistedProductVariationAttribute.Columns.productVariationID == variation.id)
122+
.deleteAll(db)
123+
119124
try variation.save(db)
120125
}
121126

Modules/Tests/YosemiteTests/Tools/POS/POSCatalogPersistenceServiceTests.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,36 @@ struct POSCatalogPersistenceServiceTests {
472472
}
473473
}
474474

475+
@Test func persistIncrementalCatalogData_prevents_duplicate_variation_attributes_on_multiple_syncs() async throws {
476+
// Given - variation with attributes
477+
let parentProduct = POSProduct.fake().copy(siteID: sampleSiteID, productID: 10)
478+
let attribute1 = Yosemite.ProductVariationAttribute.fake().copy(name: "Color", option: "Red")
479+
let attribute2 = Yosemite.ProductVariationAttribute.fake().copy(name: "Size", option: "L")
480+
let variation = POSProductVariation.fake().copy(siteID: sampleSiteID, productID: 10, productVariationID: 1, attributes: [attribute1, attribute2])
481+
try await insertProduct(parentProduct)
482+
try await insertVariation(variation)
483+
484+
// When - perform multiple incremental syncs with the same variation/attributes
485+
let catalog = POSCatalog(products: [parentProduct], variations: [variation], syncDate: .now)
486+
try await sut.persistIncrementalCatalogData(catalog, siteID: sampleSiteID)
487+
try await sut.persistIncrementalCatalogData(catalog, siteID: sampleSiteID)
488+
try await sut.persistIncrementalCatalogData(catalog, siteID: sampleSiteID)
489+
490+
// Then - should have exactly 2 attributes, not duplicates
491+
try await grdbManager.databaseConnection.read { db in
492+
let attributeCount = try PersistedProductVariationAttribute.fetchCount(db)
493+
#expect(attributeCount == 2)
494+
495+
let attributes = try PersistedProductVariationAttribute.fetchAll(db).sorted(by: { $0.name < $1.name })
496+
#expect(attributes[0].name == "Color")
497+
#expect(attributes[0].option == "Red")
498+
#expect(attributes[0].productVariationID == 1)
499+
#expect(attributes[1].name == "Size")
500+
#expect(attributes[1].option == "L")
501+
#expect(attributes[1].productVariationID == 1)
502+
}
503+
}
504+
475505
@Test func persistIncrementalCatalogData_replaces_image_for_updated_variation() async throws {
476506
// Given
477507
let parentProduct = POSProduct.fake().copy(siteID: sampleSiteID, productID: 10)

0 commit comments

Comments
 (0)