Skip to content

Commit 6accd13

Browse files
vitnehasilclaude
andcommitted
iOS: fix phantom lastExecutedAt when BGTask expires before purchase
claimPlanForExecution no longer sets lastExecutedAt — only advances nextExecutionAt to prevent double-purchase. lastExecutedAt is now set exclusively by saveTransactionAndAdvance after a transaction is actually persisted. This prevents the UI showing a "last executed" time with no corresponding transaction when a BGTask is killed mid-execution. Also saves a failed transaction when credentials are missing instead of silently returning, so the user sees what went wrong. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4e8cf27 commit 6accd13

2 files changed

Lines changed: 17 additions & 2 deletions

File tree

accbot-ios/AccBot/Data/Local/Database/Dao/DcaPlanDao.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,17 +142,19 @@ final class DcaPlanDao {
142142
/// Atomically claim a plan for execution by advancing nextExecutionAt,
143143
/// but only if it is still in the past (or null). Returns true if claimed,
144144
/// false if another task already claimed it.
145+
/// Note: lastExecutedAt is NOT set here — it is only set after a transaction
146+
/// is actually saved, so the UI never shows a "last executed" time without
147+
/// a corresponding transaction.
145148
func claimPlanForExecution(id: Int64, now: Date, nextExecutionAt: Date) throws -> Bool {
146149
try dbPool.write { db in
147150
try db.execute(
148151
sql: """
149152
UPDATE dca_plans
150-
SET nextExecutionAt = ?, lastExecutedAt = ?
153+
SET nextExecutionAt = ?
151154
WHERE id = ? AND (nextExecutionAt IS NULL OR nextExecutionAt <= ?)
152155
""",
153156
arguments: [
154157
nextExecutionAt.timeIntervalSince1970,
155-
now.timeIntervalSince1970,
156158
id,
157159
now.timeIntervalSince1970,
158160
]

accbot-ios/AccBot/Service/DcaExecutionEngine.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,19 @@ final class DcaExecutionEngine {
123123
let isSandbox = userPreferences.isSandboxMode()
124124
guard let credentials = credentialsStore.get(for: plan.exchange, isSandbox: isSandbox) else {
125125
logger.error("No credentials for \(plan.exchange.displayName) (sandbox=\(isSandbox))")
126+
let failedTx = Transaction(
127+
planId: plan.id,
128+
exchange: plan.exchange,
129+
crypto: plan.crypto,
130+
fiat: plan.fiat,
131+
fiatAmount: plan.amount,
132+
cryptoAmount: 0,
133+
price: 0,
134+
fee: 0,
135+
status: .failed,
136+
errorMessage: "No API credentials configured (sandbox=\(isSandbox))"
137+
)
138+
saveTransactionAndAdvance(failedTx, plan: plan, now: now)
126139
return
127140
}
128141

0 commit comments

Comments
 (0)