Skip to content

Commit 0192577

Browse files
authored
Merge branch 'trunk' into fix/coupon_scrolling
2 parents 3667e1b + 1d87c2e commit 0192577

File tree

9 files changed

+283
-14
lines changed

9 files changed

+283
-14
lines changed

Modules/Sources/Storage/GRDB/Migrations/V001InitialSchema.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ struct V001InitialSchema {
5555

5656
private static func createProductAttributeTable(_ db: Database) throws {
5757
try db.create(table: "productAttribute") { productAttributeTable in
58-
// This table holds local product attributes only. Global attributes belong to a site.
58+
// This table holds both local and global product attributes.
59+
// Local attributes have remoteAttributeID = 0, global attributes have remoteAttributeID > 0
5960
productAttributeTable.autoIncrementedPrimaryKey("id").notNull()
6061
productAttributeTable.column("siteID", .integer).notNull()
6162
productAttributeTable.column("productID", .integer).notNull()
@@ -64,6 +65,7 @@ struct V001InitialSchema {
6465
columns: ["siteID", "id"],
6566
onDelete: .cascade)
6667

68+
productAttributeTable.column("remoteAttributeID", .integer).notNull()
6769
productAttributeTable.column("name", .text).notNull()
6870
productAttributeTable.column("position", .integer).notNull()
6971
productAttributeTable.column("visible", .boolean).notNull()
@@ -119,7 +121,8 @@ struct V001InitialSchema {
119121

120122
private static func createProductVariationAttributeTable(_ db: Database) throws {
121123
try db.create(table: "productVariationAttribute") { productVariationAttributeTable in
122-
// This table holds local variation attributes only. Global attributes belong to a site.
124+
// This table holds both local and global variation attributes.
125+
// Local attributes have remoteAttributeID = 0, global attributes have remoteAttributeID > 0
123126
productVariationAttributeTable.autoIncrementedPrimaryKey("id").notNull()
124127
productVariationAttributeTable.column("siteID", .integer).notNull()
125128
productVariationAttributeTable.column("productVariationID", .integer).notNull()
@@ -128,6 +131,7 @@ struct V001InitialSchema {
128131
columns: ["siteID", "id"],
129132
onDelete: .cascade)
130133

134+
productVariationAttributeTable.column("remoteAttributeID", .integer).notNull()
131135
productVariationAttributeTable.column("name", .text).notNull()
132136
productVariationAttributeTable.column("option", .text).notNull()
133137
}

Modules/Sources/Storage/GRDB/Model/PersistedProductAttribute.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ public struct PersistedProductAttribute: Codable {
66
public private(set) var id: Int64?
77
public let siteID: Int64
88
public let productID: Int64
9+
public let remoteAttributeID: Int64
910
public let name: String
1011
public let position: Int64
1112
public let visible: Bool
@@ -15,6 +16,7 @@ public struct PersistedProductAttribute: Codable {
1516
public init(id: Int64? = nil,
1617
siteID: Int64,
1718
productID: Int64,
19+
remoteAttributeID: Int64,
1820
name: String,
1921
position: Int64,
2022
visible: Bool,
@@ -23,6 +25,7 @@ public struct PersistedProductAttribute: Codable {
2325
self.id = id
2426
self.siteID = siteID
2527
self.productID = productID
28+
self.remoteAttributeID = remoteAttributeID
2629
self.name = name
2730
self.position = position
2831
self.visible = visible
@@ -41,6 +44,7 @@ extension PersistedProductAttribute: FetchableRecord, MutablePersistableRecord {
4144
public static let id = Column(CodingKeys.id)
4245
public static let siteID = Column(CodingKeys.siteID)
4346
public static let productID = Column(CodingKeys.productID)
47+
public static let remoteAttributeID = Column(CodingKeys.remoteAttributeID)
4448
public static let name = Column(CodingKeys.name)
4549
public static let position = Column(CodingKeys.position)
4650
public static let visible = Column(CodingKeys.visible)
@@ -62,6 +66,7 @@ extension PersistedProductAttribute {
6266
case id
6367
case siteID
6468
case productID
69+
case remoteAttributeID
6570
case name
6671
case position
6772
case visible

Modules/Sources/Storage/GRDB/Model/PersistedProductVariationAttribute.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,20 @@ public struct PersistedProductVariationAttribute: Codable {
66
public private(set) var id: Int64?
77
public let siteID: Int64
88
public let productVariationID: Int64
9+
public let remoteAttributeID: Int64
910
public let name: String
1011
public let option: String
1112

1213
public init(id: Int64? = nil,
1314
siteID: Int64,
1415
productVariationID: Int64,
16+
remoteAttributeID: Int64,
1517
name: String,
1618
option: String) {
1719
self.id = id
1820
self.siteID = siteID
1921
self.productVariationID = productVariationID
22+
self.remoteAttributeID = remoteAttributeID
2023
self.name = name
2124
self.option = option
2225
}
@@ -33,6 +36,7 @@ extension PersistedProductVariationAttribute: FetchableRecord, MutablePersistabl
3336
public static let id = Column(CodingKeys.id)
3437
public static let siteID = Column(CodingKeys.siteID)
3538
public static let productVariationID = Column(CodingKeys.productVariationID)
39+
public static let remoteAttributeID = Column(CodingKeys.remoteAttributeID)
3640
public static let name = Column(CodingKeys.name)
3741
public static let option = Column(CodingKeys.option)
3842
}
@@ -51,6 +55,7 @@ extension PersistedProductVariationAttribute {
5155
case id
5256
case siteID
5357
case productVariationID
58+
case remoteAttributeID
5459
case name
5560
case option
5661
}

Modules/Sources/Yosemite/Model/Storage/PersistedProduct+Conversions.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ extension PersistedProductAttribute {
8989
self.init(
9090
siteID: siteID,
9191
productID: productID,
92+
remoteAttributeID: productAttribute.attributeID,
9293
name: productAttribute.name,
9394
position: Int64(productAttribute.position),
9495
visible: productAttribute.visible,
@@ -100,7 +101,7 @@ extension PersistedProductAttribute {
100101
func toProductAttribute(siteID: Int64) -> ProductAttribute {
101102
return ProductAttribute(
102103
siteID: siteID,
103-
attributeID: id ?? 0,
104+
attributeID: remoteAttributeID,
104105
name: name,
105106
position: Int(position),
106107
visible: visible,

Modules/Sources/Yosemite/Model/Storage/PersistedProductVariation+Conversions.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,15 @@ extension PersistedProductVariationAttribute {
8383
self.init(
8484
siteID: siteID,
8585
productVariationID: productVariationID,
86+
remoteAttributeID: productVariationAttribute.id,
8687
name: productVariationAttribute.name,
8788
option: productVariationAttribute.option
8889
)
8990
}
9091

9192
func toProductVariationAttribute() -> ProductVariationAttribute {
9293
return ProductVariationAttribute(
93-
id: id ?? 0,
94+
id: remoteAttributeID,
9495
name: name,
9596
option: option
9697
)

Modules/Tests/StorageTests/GRDB/GRDBManagerTests.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ struct GRDBManagerTests {
193193
let attribute = TestProductAttribute(
194194
siteID: 1,
195195
productID: 100,
196+
remoteAttributeID: 0,
196197
name: "Color",
197198
position: 0,
198199
visible: true,
@@ -247,6 +248,7 @@ struct GRDBManagerTests {
247248
let variationAttribute = TestProductVariationAttribute(
248249
siteID: 1,
249250
productVariationID: 200,
251+
remoteAttributeID: 0,
250252
name: "Color",
251253
option: "Red"
252254
)
@@ -300,6 +302,7 @@ struct GRDBManagerTests {
300302
let productAttribute = TestProductAttribute(
301303
siteID: testSiteId,
302304
productID: 100,
305+
remoteAttributeID: 50,
303306
name: "Color",
304307
position: 0,
305308
visible: true,
@@ -311,6 +314,7 @@ struct GRDBManagerTests {
311314
let variationAttribute = TestProductVariationAttribute(
312315
siteID: testSiteId,
313316
productVariationID: 200,
317+
remoteAttributeID: 60,
314318
name: "Size",
315319
option: "Large"
316320
)
@@ -563,6 +567,7 @@ struct GRDBManagerTests {
563567
let productAttribute = TestProductAttribute(
564568
siteID: siteID,
565569
productID: product.id,
570+
remoteAttributeID: 50,
566571
name: "Color \(i)",
567572
position: i,
568573
visible: true,
@@ -575,6 +580,7 @@ struct GRDBManagerTests {
575580
let variationAttribute = TestProductVariationAttribute(
576581
siteID: siteID,
577582
productVariationID: variation.id,
583+
remoteAttributeID: 60,
578584
name: "Size \(i)",
579585
option: "Large"
580586
)
@@ -744,6 +750,7 @@ extension TestProductVariation: FetchableRecord, PersistableRecord {
744750
struct TestProductAttribute: Codable {
745751
let siteID: Int64
746752
let productID: Int64
753+
let remoteAttributeID: Int64
747754
let name: String
748755
let position: Int
749756
let visible: Bool
@@ -758,6 +765,7 @@ extension TestProductAttribute: FetchableRecord, PersistableRecord {
758765
struct TestProductVariationAttribute: Codable {
759766
let siteID: Int64
760767
let productVariationID: Int64
768+
let remoteAttributeID: Int64
761769
let name: String
762770
let option: String
763771
}

Modules/Tests/YosemiteTests/Storage/PersistedProductTests.swift

Lines changed: 129 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,22 @@ struct PersistedProductTests {
9292
]
9393

9494
let persistedAttributes = [
95-
PersistedProductAttribute(siteID: siteID, productID: productID, name: "Material", position: 0, visible: true, variation: false, options: ["Cotton"]),
96-
PersistedProductAttribute(siteID: siteID, productID: productID, name: "Fit", position: 1, visible: true, variation: true, options: ["Slim", "Regular"])
95+
PersistedProductAttribute(siteID: siteID,
96+
productID: productID,
97+
remoteAttributeID: 501,
98+
name: "Material",
99+
position: 0,
100+
visible: true,
101+
variation: false,
102+
options: ["Cotton"]),
103+
PersistedProductAttribute(siteID: siteID,
104+
productID: productID,
105+
remoteAttributeID: 502,
106+
name: "Fit",
107+
position: 1,
108+
visible: true,
109+
variation: true,
110+
options: ["Slim", "Regular"])
97111
]
98112

99113
// When
@@ -178,6 +192,7 @@ struct PersistedProductTests {
178192
var attribute1 = PersistedProductAttribute(
179193
siteID: 1,
180194
productID: 100,
195+
remoteAttributeID: 501,
181196
name: "Color",
182197
position: 0,
183198
visible: true,
@@ -187,6 +202,7 @@ struct PersistedProductTests {
187202
var attribute2 = PersistedProductAttribute(
188203
siteID: 1,
189204
productID: 100,
205+
remoteAttributeID: 502,
190206
name: "Size",
191207
position: 1,
192208
visible: false,
@@ -312,6 +328,117 @@ struct PersistedProductTests {
312328
#expect(back.options == attribute.options)
313329
}
314330

331+
@Test("PersistedProductAttribute with remoteAttributeID of 0 represents non-global attribute")
332+
func product_attribute_with_zero_remote_id_is_non_global() throws {
333+
// Given a non-global attribute (not shared across products)
334+
let siteID: Int64 = 10
335+
let productID: Int64 = 100
336+
let attribute = ProductAttribute(siteID: siteID,
337+
attributeID: 0,
338+
name: "Custom Color",
339+
position: 0,
340+
visible: true,
341+
variation: true,
342+
options: ["Custom Red", "Custom Blue"])
343+
344+
// When
345+
let persisted = PersistedProductAttribute(from: attribute, siteID: siteID, productID: productID)
346+
347+
// Then the remoteAttributeID should be 0 for non-global attributes
348+
#expect(persisted.remoteAttributeID == 0)
349+
#expect(persisted.name == "Custom Color")
350+
#expect(persisted.options == ["Custom Red", "Custom Blue"])
351+
352+
// When converting back to ProductAttribute
353+
let back = persisted.toProductAttribute(siteID: siteID)
354+
355+
// Then the attributeID should remain 0
356+
#expect(back.attributeID == 0)
357+
#expect(back.name == "Custom Color")
358+
#expect(back.options == ["Custom Red", "Custom Blue"])
359+
}
360+
361+
@Test("Non-global product attribute persists and retrieves correctly with remoteAttributeID 0")
362+
func non_global_product_attribute_persists_correctly() throws {
363+
// Given
364+
let grdbManager = try GRDBManager()
365+
let db = grdbManager.databaseConnection
366+
367+
try db.write { db in
368+
let site = PersistedSite(id: 5)
369+
try site.insert(db)
370+
371+
let product = PersistedProduct(
372+
id: 500,
373+
siteID: 5,
374+
name: "Custom Product",
375+
productTypeKey: "variable",
376+
fullDescription: nil,
377+
shortDescription: nil,
378+
sku: nil,
379+
globalUniqueID: nil,
380+
price: "25.00",
381+
downloadable: false,
382+
parentID: 0,
383+
manageStock: false,
384+
stockQuantity: nil,
385+
stockStatusKey: "instock"
386+
)
387+
try product.insert(db)
388+
389+
// Insert a non-global attribute with remoteAttributeID = 0
390+
var nonGlobalAttribute = PersistedProductAttribute(
391+
siteID: 5,
392+
productID: 500,
393+
remoteAttributeID: 0,
394+
name: "Custom Texture",
395+
position: 0,
396+
visible: true,
397+
variation: true,
398+
options: ["Smooth", "Rough", "Textured"]
399+
)
400+
try nonGlobalAttribute.insert(db)
401+
402+
// Also insert a global attribute for comparison
403+
var globalAttribute = PersistedProductAttribute(
404+
siteID: 5,
405+
productID: 500,
406+
remoteAttributeID: 123,
407+
name: "Standard Size",
408+
position: 1,
409+
visible: true,
410+
variation: false,
411+
options: ["Small", "Medium", "Large"]
412+
)
413+
try globalAttribute.insert(db)
414+
}
415+
416+
// When
417+
let fetchedProduct = try db.read { db in
418+
try PersistedProduct.filter(PersistedProduct.Columns.id == 500).fetchOne(db)
419+
}
420+
421+
let product = try #require(fetchedProduct)
422+
let posProduct = try product.toPOSProduct(db: db)
423+
424+
// Then both attributes should be present
425+
#expect(posProduct.attributes.count == 2)
426+
427+
// Verify non-global attribute
428+
let nonGlobalAttr = posProduct.attributes.first { $0.name == "Custom Texture" }
429+
#expect(nonGlobalAttr != nil)
430+
#expect(nonGlobalAttr?.attributeID == 0)
431+
#expect(nonGlobalAttr?.options == ["Smooth", "Rough", "Textured"])
432+
#expect(nonGlobalAttr?.variation == true)
433+
434+
// Verify global attribute
435+
let globalAttr = posProduct.attributes.first { $0.name == "Standard Size" }
436+
#expect(globalAttr != nil)
437+
#expect(globalAttr?.attributeID == 123)
438+
#expect(globalAttr?.options == ["Small", "Medium", "Large"])
439+
#expect(globalAttr?.variation == false)
440+
}
441+
315442
@Test("PersistedProductImage init(from:) and toProductImage round-trip")
316443
func product_image_round_trip() throws {
317444
// Given

0 commit comments

Comments
 (0)