From f293a37697b13d1a51d9bfe63cf1dd84a6253ee1 Mon Sep 17 00:00:00 2001 From: RafaelKayumov Date: Fri, 17 Oct 2025 16:30:27 +0300 Subject: [PATCH 1/9] Add attendance status property to readonly booking --- .../Networking/Model/Bookings/Booking.swift | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Modules/Sources/Networking/Model/Bookings/Booking.swift b/Modules/Sources/Networking/Model/Bookings/Booking.swift index 21a6d331e47..f194e1a2fa7 100644 --- a/Modules/Sources/Networking/Model/Bookings/Booking.swift +++ b/Modules/Sources/Networking/Model/Bookings/Booking.swift @@ -20,6 +20,7 @@ public struct Booking: Codable, GeneratedCopiable, Hashable, GeneratedFakeable { public let resourceID: Int64 public let startDate: Date public let statusKey: String + public let attendanceStatusKey: String public let localTimezone: String public let currency: String public let orderInfo: BookingOrderInfo? @@ -28,6 +29,10 @@ public struct Booking: Codable, GeneratedCopiable, Hashable, GeneratedFakeable { return BookingStatus(rawValue: statusKey) ?? .unknown } + public var attendanceStatus: BookingAttendanceStatus { + return BookingAttendanceStatus(rawValue: attendanceStatusKey) ?? .unknown + } + /// Booking struct initializer. /// public init(siteID: Int64, @@ -46,6 +51,7 @@ public struct Booking: Codable, GeneratedCopiable, Hashable, GeneratedFakeable { resourceID: Int64, startDate: Date, statusKey: String, + attendanceStatusKey: String, localTimezone: String, currency: String, orderInfo: BookingOrderInfo?) { @@ -65,6 +71,7 @@ public struct Booking: Codable, GeneratedCopiable, Hashable, GeneratedFakeable { self.resourceID = resourceID self.startDate = startDate self.statusKey = statusKey + self.attendanceStatusKey = attendanceStatusKey self.localTimezone = localTimezone self.currency = currency self.orderInfo = orderInfo @@ -99,6 +106,7 @@ public struct Booking: Codable, GeneratedCopiable, Hashable, GeneratedFakeable { let resourceID = try container.decode(Int64.self, forKey: .resourceID) let startDate = Date(timeIntervalSince1970: try container.decode(Double.self, forKey: .startDate)) let statusKey = try container.decode(String.self, forKey: .statusKey) + let attendanceStatusKey = try container.decode(String.self, forKey: .attendanceStatusKey) let localTimezone = try container.decode(String.self, forKey: .localTimezone) let currency = try container.decode(String.self, forKey: .currency) let orderInfo: BookingOrderInfo? = nil // to be prefilled when synced @@ -142,6 +150,7 @@ public struct Booking: Codable, GeneratedCopiable, Hashable, GeneratedFakeable { try container.encode(resourceID, forKey: .resourceID) try container.encode(startDate, forKey: .startDate) try container.encode(statusKey, forKey: .statusKey) + try container.encode(attendanceStatusKey, forKey: .attendanceStatusKey) try container.encode(localTimezone, forKey: .localTimezone) } } @@ -171,6 +180,7 @@ private extension Booking { case resourceID = "resource_id" case startDate = "start" case statusKey = "status" + case attendanceStatusKey = "attendance_status" case localTimezone = "local_timezone" case currency } @@ -185,7 +195,6 @@ enum BookingDecodingError: Error { // MARK: - Supporting Types // -// periphery: ignore /// Represents a Booking Status. public enum BookingStatus: String, CaseIterable { case complete @@ -196,3 +205,11 @@ public enum BookingStatus: String, CaseIterable { case confirmed case unknown } + +public enum BookingAttendanceStatus: String, CaseIterable { + case booked + case checkedIn = "checked_in" + case cancelled + case noShow = "no_show" + case unknown +} From 84ca06a823ba43206f07b67987fc5f947f75c51c Mon Sep 17 00:00:00 2001 From: RafaelKayumov Date: Fri, 17 Oct 2025 16:34:01 +0300 Subject: [PATCH 2/9] Add model version 129 for the new attendanceStatusKey in Booking --- .../Networking/Model/Bookings/Booking.swift | 1 + .../.xccurrentversion | 2 +- .../Model 129.xcdatamodel/contents | 1176 +++++++++++++++++ 3 files changed, 1178 insertions(+), 1 deletion(-) create mode 100644 Modules/Sources/Storage/Resources/WooCommerce.xcdatamodeld/Model 129.xcdatamodel/contents diff --git a/Modules/Sources/Networking/Model/Bookings/Booking.swift b/Modules/Sources/Networking/Model/Bookings/Booking.swift index f194e1a2fa7..5d50c101145 100644 --- a/Modules/Sources/Networking/Model/Bookings/Booking.swift +++ b/Modules/Sources/Networking/Model/Bookings/Booking.swift @@ -127,6 +127,7 @@ public struct Booking: Codable, GeneratedCopiable, Hashable, GeneratedFakeable { resourceID: resourceID, startDate: startDate, statusKey: statusKey, + attendanceStatusKey: attendanceStatusKey, localTimezone: localTimezone, currency: currency, orderInfo: orderInfo) diff --git a/Modules/Sources/Storage/Resources/WooCommerce.xcdatamodeld/.xccurrentversion b/Modules/Sources/Storage/Resources/WooCommerce.xcdatamodeld/.xccurrentversion index e0df1183b72..051471ed320 100644 --- a/Modules/Sources/Storage/Resources/WooCommerce.xcdatamodeld/.xccurrentversion +++ b/Modules/Sources/Storage/Resources/WooCommerce.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - Model 128.xcdatamodel + Model 129.xcdatamodel diff --git a/Modules/Sources/Storage/Resources/WooCommerce.xcdatamodeld/Model 129.xcdatamodel/contents b/Modules/Sources/Storage/Resources/WooCommerce.xcdatamodeld/Model 129.xcdatamodel/contents new file mode 100644 index 00000000000..d030970308a --- /dev/null +++ b/Modules/Sources/Storage/Resources/WooCommerce.xcdatamodeld/Model 129.xcdatamodel/contents @@ -0,0 +1,1176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 21ec58e6b6b8969ef16818c4a45a410789c4a5b0 Mon Sep 17 00:00:00 2001 From: RafaelKayumov Date: Fri, 17 Oct 2025 16:41:13 +0300 Subject: [PATCH 3/9] Update Booking Core Data model --- .../Storage/Model/Booking/Booking+CoreDataProperties.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/Sources/Storage/Model/Booking/Booking+CoreDataProperties.swift b/Modules/Sources/Storage/Model/Booking/Booking+CoreDataProperties.swift index f4176800f76..23e8e8eb9b7 100644 --- a/Modules/Sources/Storage/Model/Booking/Booking+CoreDataProperties.swift +++ b/Modules/Sources/Storage/Model/Booking/Booking+CoreDataProperties.swift @@ -19,6 +19,7 @@ extension Booking { @NSManaged public var googleCalendarEventID: String? @NSManaged public var orderItemID: Int64 @NSManaged public var statusKey: String? + @NSManaged public var attendanceStatusKey: String? @NSManaged public var localTimezone: String? @NSManaged public var currency: String? @NSManaged public var orderInfo: BookingOrderInfo? From 2bd2e0ab9e650b2dfac46340cec7c072d25ed6ad Mon Sep 17 00:00:00 2001 From: RafaelKayumov Date: Fri, 17 Oct 2025 16:41:23 +0300 Subject: [PATCH 4/9] Add migration description and tests --- Modules/Sources/Storage/Model/MIGRATIONS.md | 4 +++ .../CoreData/MigrationTests.swift | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/Modules/Sources/Storage/Model/MIGRATIONS.md b/Modules/Sources/Storage/Model/MIGRATIONS.md index 58b314d4d5c..dd85daf3e41 100644 --- a/Modules/Sources/Storage/Model/MIGRATIONS.md +++ b/Modules/Sources/Storage/Model/MIGRATIONS.md @@ -2,6 +2,10 @@ This file documents changes in the WCiOS Storage data model. Please explain any changes to the data model as well as any custom migrations. +## Model 129 (Release X.X.X.X) +- @rafaelkayumov 2025-10-17 + - Added `attendanceStatusKey` attribute to `Booking` entity. + ## Model 128 (Release 23.5.0.0) - @itsmeichigo 2025-10-14 - Added `BookingOrderInfo` entity. diff --git a/Modules/Tests/StorageTests/CoreData/MigrationTests.swift b/Modules/Tests/StorageTests/CoreData/MigrationTests.swift index 515828fc98f..4d6ce35f540 100644 --- a/Modules/Tests/StorageTests/CoreData/MigrationTests.swift +++ b/Modules/Tests/StorageTests/CoreData/MigrationTests.swift @@ -2281,6 +2281,37 @@ final class MigrationTests: XCTestCase { let insertedResource = try XCTUnwrap(targetContext.first(entityName: "BookingResource")) XCTAssertEqual(insertedResource, resource) } + + func test_migrating_from_128_to_129_adds_new_attendanceStatusKey_attribute_to_booking() throws { + // Given + let sourceContainer = try startPersistentContainer("Model 128") + let sourceContext = sourceContainer.viewContext + + let booking = insertBooking(to: sourceContext) + try sourceContext.save() + + XCTAssertNil(booking.entity.attributesByName["attendanceStatusKey"], "Precondition. Attribute does not exist.") + + // When + let targetContainer = try migrate(sourceContainer, to: "Model 129") + + // Then + let targetContext = targetContainer.viewContext + let migratedBooking = try XCTUnwrap(targetContext.first(entityName: "Booking")) + + // `attendanceStatusKey` should be present in `migratedBooking` + XCTAssertNotNil(migratedBooking.entity.attributesByName["attendanceStatusKey"]) + + // `attendanceStatusKey` value should default as nil in model 129 + let value = migratedBooking.value(forKey: "attendanceStatusKey") as? String + XCTAssertNil(value) + + // `attendanceStatusKey` must be settable + migratedBooking.setValue("checked_in", forKey: "attendanceStatusKey") + try targetContext.save() + let updatedValue = migratedBooking.value(forKey: "attendanceStatusKey") as? String + XCTAssertEqual(updatedValue, "checked_in") + } } // MARK: - Persistent Store Setup and Migrations From 2c87690b6b3bd5f155150e2aed0e473bad89ab24 Mon Sep 17 00:00:00 2001 From: RafaelKayumov Date: Mon, 20 Oct 2025 13:33:24 +0300 Subject: [PATCH 5/9] Add missing arguments --- Modules/Sources/Fakes/Networking.generated.swift | 1 + .../Networking/Model/Copiable/Models+Copiable.generated.swift | 3 +++ .../Yosemite/Model/Booking/Booking+ReadOnlyConvertible.swift | 1 + .../Bookings/Booking Details/BookingDetailsView.swift | 1 + 4 files changed, 6 insertions(+) diff --git a/Modules/Sources/Fakes/Networking.generated.swift b/Modules/Sources/Fakes/Networking.generated.swift index 221bf6d81a5..931e388a8d9 100644 --- a/Modules/Sources/Fakes/Networking.generated.swift +++ b/Modules/Sources/Fakes/Networking.generated.swift @@ -338,6 +338,7 @@ extension Networking.Booking { resourceID: .fake(), startDate: .fake(), statusKey: .fake(), + attendanceStatusKey: .fake(), localTimezone: .fake(), currency: .fake(), orderInfo: .fake() diff --git a/Modules/Sources/Networking/Model/Copiable/Models+Copiable.generated.swift b/Modules/Sources/Networking/Model/Copiable/Models+Copiable.generated.swift index 91f7c131fe2..ff51dabe27f 100644 --- a/Modules/Sources/Networking/Model/Copiable/Models+Copiable.generated.swift +++ b/Modules/Sources/Networking/Model/Copiable/Models+Copiable.generated.swift @@ -446,6 +446,7 @@ extension Networking.Booking { resourceID: CopiableProp = .copy, startDate: CopiableProp = .copy, statusKey: CopiableProp = .copy, + attendanceStatusKey: CopiableProp = .copy, localTimezone: CopiableProp = .copy, currency: CopiableProp = .copy, orderInfo: NullableCopiableProp = .copy @@ -466,6 +467,7 @@ extension Networking.Booking { let resourceID = resourceID ?? self.resourceID let startDate = startDate ?? self.startDate let statusKey = statusKey ?? self.statusKey + let attendanceStatusKey = attendanceStatusKey ?? self.attendanceStatusKey let localTimezone = localTimezone ?? self.localTimezone let currency = currency ?? self.currency let orderInfo = orderInfo ?? self.orderInfo @@ -487,6 +489,7 @@ extension Networking.Booking { resourceID: resourceID, startDate: startDate, statusKey: statusKey, + attendanceStatusKey: attendanceStatusKey, localTimezone: localTimezone, currency: currency, orderInfo: orderInfo diff --git a/Modules/Sources/Yosemite/Model/Booking/Booking+ReadOnlyConvertible.swift b/Modules/Sources/Yosemite/Model/Booking/Booking+ReadOnlyConvertible.swift index 7e8b5385375..1f5bcf46a49 100644 --- a/Modules/Sources/Yosemite/Model/Booking/Booking+ReadOnlyConvertible.swift +++ b/Modules/Sources/Yosemite/Model/Booking/Booking+ReadOnlyConvertible.swift @@ -47,6 +47,7 @@ extension Storage.Booking: ReadOnlyConvertible { resourceID: resourceID, startDate: startDate ?? Date(), statusKey: statusKey ?? "", + attendanceStatusKey: attendanceStatusKey ?? "", localTimezone: localTimezone ?? "", currency: currency ?? "USD", orderInfo: orderInfo?.toReadOnly()) diff --git a/WooCommerce/Classes/ViewRelated/Bookings/Booking Details/BookingDetailsView.swift b/WooCommerce/Classes/ViewRelated/Bookings/Booking Details/BookingDetailsView.swift index ebfd276fec3..6b985811f6a 100644 --- a/WooCommerce/Classes/ViewRelated/Bookings/Booking Details/BookingDetailsView.swift +++ b/WooCommerce/Classes/ViewRelated/Bookings/Booking Details/BookingDetailsView.swift @@ -335,6 +335,7 @@ struct BookingDetailsView_Previews: PreviewProvider { resourceID: 113, startDate: now, statusKey: "paid", + attendanceStatusKey: "booked", localTimezone: "America/New_York", currency: "USD", orderInfo: nil From 8979c247baab129bc60668891596ff354d1c75b1 Mon Sep 17 00:00:00 2001 From: RafaelKayumov Date: Mon, 20 Oct 2025 13:33:35 +0300 Subject: [PATCH 6/9] Update migration test --- Modules/Tests/StorageTests/CoreData/MigrationTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/Tests/StorageTests/CoreData/MigrationTests.swift b/Modules/Tests/StorageTests/CoreData/MigrationTests.swift index 4d6ce35f540..a634d620938 100644 --- a/Modules/Tests/StorageTests/CoreData/MigrationTests.swift +++ b/Modules/Tests/StorageTests/CoreData/MigrationTests.swift @@ -2302,9 +2302,9 @@ final class MigrationTests: XCTestCase { // `attendanceStatusKey` should be present in `migratedBooking` XCTAssertNotNil(migratedBooking.entity.attributesByName["attendanceStatusKey"]) - // `attendanceStatusKey` value should default as nil in model 129 + // `attendanceStatusKey` value should default as "" in model 129 let value = migratedBooking.value(forKey: "attendanceStatusKey") as? String - XCTAssertNil(value) + XCTAssertEqual(value, "") // `attendanceStatusKey` must be settable migratedBooking.setValue("checked_in", forKey: "attendanceStatusKey") From 227a360d53ba4dbe598c44533692f776d605f426 Mon Sep 17 00:00:00 2001 From: RafaelKayumov Date: Mon, 20 Oct 2025 13:49:54 +0300 Subject: [PATCH 7/9] Mark unused attendance status --- Modules/Sources/Networking/Model/Bookings/Booking.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/Sources/Networking/Model/Bookings/Booking.swift b/Modules/Sources/Networking/Model/Bookings/Booking.swift index 5d50c101145..cabcfb2c115 100644 --- a/Modules/Sources/Networking/Model/Bookings/Booking.swift +++ b/Modules/Sources/Networking/Model/Bookings/Booking.swift @@ -29,6 +29,7 @@ public struct Booking: Codable, GeneratedCopiable, Hashable, GeneratedFakeable { return BookingStatus(rawValue: statusKey) ?? .unknown } + /// periphery: ignore - will be used in UI in upcoming PRs public var attendanceStatus: BookingAttendanceStatus { return BookingAttendanceStatus(rawValue: attendanceStatusKey) ?? .unknown } @@ -207,6 +208,7 @@ public enum BookingStatus: String, CaseIterable { case unknown } +/// periphery: ignore - will be used in UI in upcoming PRs public enum BookingAttendanceStatus: String, CaseIterable { case booked case checkedIn = "checked_in" From 93cbfbfc567de6758bbe8a9d85291b11639b6b42 Mon Sep 17 00:00:00 2001 From: RafaelKayumov Date: Mon, 20 Oct 2025 15:20:47 +0300 Subject: [PATCH 8/9] Make attendance_status decoding optional --- Modules/Sources/Networking/Model/Bookings/Booking.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Sources/Networking/Model/Bookings/Booking.swift b/Modules/Sources/Networking/Model/Bookings/Booking.swift index cabcfb2c115..92dd37d14d6 100644 --- a/Modules/Sources/Networking/Model/Bookings/Booking.swift +++ b/Modules/Sources/Networking/Model/Bookings/Booking.swift @@ -107,7 +107,7 @@ public struct Booking: Codable, GeneratedCopiable, Hashable, GeneratedFakeable { let resourceID = try container.decode(Int64.self, forKey: .resourceID) let startDate = Date(timeIntervalSince1970: try container.decode(Double.self, forKey: .startDate)) let statusKey = try container.decode(String.self, forKey: .statusKey) - let attendanceStatusKey = try container.decode(String.self, forKey: .attendanceStatusKey) + let attendanceStatusKey = container.failsafeDecodeIfPresent(String.self, forKey: .attendanceStatusKey) ?? "" let localTimezone = try container.decode(String.self, forKey: .localTimezone) let currency = try container.decode(String.self, forKey: .currency) let orderInfo: BookingOrderInfo? = nil // to be prefilled when synced From 92b1598e0d4f7babd40493ab771960ffc810632c Mon Sep 17 00:00:00 2001 From: RafaelKayumov Date: Mon, 20 Oct 2025 16:28:36 +0300 Subject: [PATCH 9/9] Update attendance status keys --- Modules/Sources/Networking/Model/Bookings/Booking.swift | 4 ++-- .../Yosemite/Model/Booking/Booking+ReadOnlyConvertible.swift | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Modules/Sources/Networking/Model/Bookings/Booking.swift b/Modules/Sources/Networking/Model/Bookings/Booking.swift index 92dd37d14d6..4cdb6ff4bb8 100644 --- a/Modules/Sources/Networking/Model/Bookings/Booking.swift +++ b/Modules/Sources/Networking/Model/Bookings/Booking.swift @@ -211,8 +211,8 @@ public enum BookingStatus: String, CaseIterable { /// periphery: ignore - will be used in UI in upcoming PRs public enum BookingAttendanceStatus: String, CaseIterable { case booked - case checkedIn = "checked_in" + case checkedIn = "checked-in" case cancelled - case noShow = "no_show" + case noShow = "no-show" case unknown } diff --git a/Modules/Sources/Yosemite/Model/Booking/Booking+ReadOnlyConvertible.swift b/Modules/Sources/Yosemite/Model/Booking/Booking+ReadOnlyConvertible.swift index 1f5bcf46a49..55a62cb3a6f 100644 --- a/Modules/Sources/Yosemite/Model/Booking/Booking+ReadOnlyConvertible.swift +++ b/Modules/Sources/Yosemite/Model/Booking/Booking+ReadOnlyConvertible.swift @@ -24,6 +24,7 @@ extension Storage.Booking: ReadOnlyConvertible { resourceID = booking.resourceID startDate = booking.startDate statusKey = booking.statusKey + attendanceStatusKey = booking.attendanceStatusKey localTimezone = booking.localTimezone currency = booking.currency }