Fix launch crash: rename SyncRecord.entity to entityName
CoreData treats `entity` as the managed object's entity, so the predicate
`entity == "setting"` became a fetch of an entity named "setting" and the
app aborted at launch ("setting" is not a valid entity name). The model is
new in the unreleased contract v2 change, so no migration is needed.
Verified on the watchOS 27.2 simulator: launch, login to a local
BalanceServer v2, total balance 57 800 ₽ with a USD account converted at
the synced rate and a cross-currency transfer, per-account balances
39 800 ₽ and $200.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d0b023b1ba
commit
94177dbb0b
4 files changed
+12
-10
No files matched your search
@@ -20,7 +20,7 @@ extension EnvironmentValues {
|
|||||||
struct CurrencyContextProvider<Content: View>: View {
|
struct CurrencyContextProvider<Content: View>: View {
|
||||||
@Query private var accounts: [FinanceAccount]
|
@Query private var accounts: [FinanceAccount]
|
||||||
@Query private var rates: [ExchangeRate]
|
@Query private var rates: [ExchangeRate]
|
||||||
@Query(filter: #Predicate<SyncRecord> { $0.entity == "setting" && $0.recordID == "baseCurrency" })
|
@Query(filter: #Predicate<SyncRecord> { $0.entityName == "setting" && $0.recordID == "baseCurrency" })
|
||||||
private var baseSetting: [SyncRecord]
|
private var baseSetting: [SyncRecord]
|
||||||
@AppStorage("currencyCode") private var currencyCode = SupportedCurrency.RUB.rawValue
|
@AppStorage("currencyCode") private var currencyCode = SupportedCurrency.RUB.rawValue
|
||||||
@ViewBuilder let content: Content
|
@ViewBuilder let content: Content
|
||||||
|
|||||||
@@ -489,7 +489,7 @@ struct WatchSettingsView: View {
|
|||||||
@StateObject private var account = ServerAccountStore.shared
|
@StateObject private var account = ServerAccountStore.shared
|
||||||
@AppStorage("currencyCode") private var currencyCode = SupportedCurrency.RUB.rawValue
|
@AppStorage("currencyCode") private var currencyCode = SupportedCurrency.RUB.rawValue
|
||||||
@AppStorage("appTheme") private var appTheme = AppTheme.system.rawValue
|
@AppStorage("appTheme") private var appTheme = AppTheme.system.rawValue
|
||||||
@Query(filter: #Predicate<SyncRecord> { $0.entity == "setting" && $0.recordID == "baseCurrency" })
|
@Query(filter: #Predicate<SyncRecord> { $0.entityName == "setting" && $0.recordID == "baseCurrency" })
|
||||||
private var baseSetting: [SyncRecord]
|
private var baseSetting: [SyncRecord]
|
||||||
|
|
||||||
private var currencyCodes: [String] {
|
private var currencyCodes: [String] {
|
||||||
@@ -508,7 +508,7 @@ struct WatchSettingsView: View {
|
|||||||
record.payload = payload
|
record.payload = payload
|
||||||
record.syncUpdatedAt = .now
|
record.syncUpdatedAt = .now
|
||||||
} else {
|
} else {
|
||||||
modelContext.insert(SyncRecord(entity: "setting", recordID: SyncRecord.baseCurrencyKey, payload: payload, syncUpdatedAt: .now))
|
modelContext.insert(SyncRecord(entityName: "setting", recordID: SyncRecord.baseCurrencyKey, payload: payload, syncUpdatedAt: .now))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,13 +70,15 @@ final class ExchangeRate {
|
|||||||
/// `attachment`, `setting`. Payload хранится целиком, поэтому все поля сохраняются.
|
/// `attachment`, `setting`. Payload хранится целиком, поэтому все поля сохраняются.
|
||||||
@Model
|
@Model
|
||||||
final class SyncRecord {
|
final class SyncRecord {
|
||||||
var entity: String = ""
|
/// Не `entity`: так называется встроенное свойство CoreData, и предикат
|
||||||
|
/// по нему ломает SQL-запрос SwiftData.
|
||||||
|
var entityName: String = ""
|
||||||
var recordID: String = ""
|
var recordID: String = ""
|
||||||
var payload: Data = Data()
|
var payload: Data = Data()
|
||||||
var syncUpdatedAt: Date = Date.now
|
var syncUpdatedAt: Date = Date.now
|
||||||
|
|
||||||
init(entity: String, recordID: String, payload: Data, syncUpdatedAt: Date) {
|
init(entityName: String, recordID: String, payload: Data, syncUpdatedAt: Date) {
|
||||||
self.entity = entity
|
self.entityName = entityName
|
||||||
self.recordID = recordID
|
self.recordID = recordID
|
||||||
self.payload = payload
|
self.payload = payload
|
||||||
self.syncUpdatedAt = syncUpdatedAt
|
self.syncUpdatedAt = syncUpdatedAt
|
||||||
|
|||||||
@@ -498,9 +498,9 @@ final class ServerAccountStore: ObservableObject {
|
|||||||
private func pushSettings(context: ModelContext, after threshold: Date, through cutoff: Date, cursor: Int64) async throws {
|
private func pushSettings(context: ModelContext, after threshold: Date, through cutoff: Date, cursor: Int64) async throws {
|
||||||
let lowerBound = threshold, upperBound = cutoff
|
let lowerBound = threshold, upperBound = cutoff
|
||||||
let items = try context.fetch(FetchDescriptor<SyncRecord>(predicate: #Predicate { item in
|
let items = try context.fetch(FetchDescriptor<SyncRecord>(predicate: #Predicate { item in
|
||||||
item.entity == "setting" && item.syncUpdatedAt >= lowerBound && item.syncUpdatedAt <= upperBound
|
item.entityName == "setting" && item.syncUpdatedAt >= lowerBound && item.syncUpdatedAt <= upperBound
|
||||||
}))
|
}))
|
||||||
try await sendPush(items.map { SyncChange(entity:$0.entity, id:$0.recordID, deleted:false, updatedAt:$0.syncUpdatedAt, payload:$0.payload) }, cursor:cursor)
|
try await sendPush(items.map { SyncChange(entity:$0.entityName, id:$0.recordID, deleted:false, updatedAt:$0.syncUpdatedAt, payload:$0.payload) }, cursor:cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func pushTombstones(through cutoff: Date, cursor: Int64) async throws {
|
private func pushTombstones(through cutoff: Date, cursor: Int64) async throws {
|
||||||
@@ -660,12 +660,12 @@ final class ServerAccountStore: ObservableObject {
|
|||||||
/// Регулярные операции часы не исполняют (§4).
|
/// Регулярные операции часы не исполняют (§4).
|
||||||
private func applyRecord(_ change: SyncChange, context: ModelContext) throws {
|
private func applyRecord(_ change: SyncChange, context: ModelContext) throws {
|
||||||
let entity = change.entity, recordID = change.id
|
let entity = change.entity, recordID = change.id
|
||||||
let existing = try context.fetch(FetchDescriptor<SyncRecord>(predicate:#Predicate { $0.entity == entity && $0.recordID == recordID })).first
|
let existing = try context.fetch(FetchDescriptor<SyncRecord>(predicate:#Predicate { $0.entityName == entity && $0.recordID == recordID })).first
|
||||||
if let existing, existing.syncUpdatedAt > change.updatedAt { return }
|
if let existing, existing.syncUpdatedAt > change.updatedAt { return }
|
||||||
if change.deleted { if let existing { context.delete(existing) }; return }
|
if change.deleted { if let existing { context.delete(existing) }; return }
|
||||||
guard let data = change.payload else { return }
|
guard let data = change.payload else { return }
|
||||||
if let existing { existing.payload = data; existing.syncUpdatedAt = change.updatedAt }
|
if let existing { existing.payload = data; existing.syncUpdatedAt = change.updatedAt }
|
||||||
else { context.insert(SyncRecord(entity:entity, recordID:recordID, payload:data, syncUpdatedAt:change.updatedAt)) }
|
else { context.insert(SyncRecord(entityName:entity, recordID:recordID, payload:data, syncUpdatedAt:change.updatedAt)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private func transaction(id: UUID, context: ModelContext) throws -> FinanceTransaction? {
|
private func transaction(id: UUID, context: ModelContext) throws -> FinanceTransaction? {
|
||||||
|
|||||||
Reference in new issue
Block a user