{"record":{"id":"4f4013bb81cd6151","repo":"actualbudget/actual","slug":"transaction-not-found-id","errorCode":null,"errorMessage":"Transaction not found: ${id}","messagePattern":"Transaction not found: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/db/index.ts","lineNumber":922,"sourceCode":"}\n\n/**\n * Move a transaction to a new position within the same date.\n * Uses the same midpoint/shove algorithm as category reordering.\n *\n * @param id - The ID of the transaction to move\n * @param accountId - The account the transaction belongs to\n * @param targetId - The ID of the transaction to place AFTER, or null to place at top\n */\nexport async function moveTransaction(\n  id: string,\n  accountId: string,\n  targetId: string | null,\n) {\n  await batchMessages(async () => {\n    const transaction = await getTransaction(id);\n    if (!transaction) {\n      throw new Error(`Transaction not found: ${id}`);\n    }\n\n    // Validate that the transaction belongs to the specified account\n    if (transaction.account !== accountId) {\n      throw new Error(\n        `Transaction ${id} does not belong to account ${accountId}`,\n      );\n    }\n\n    // Convert date string (YYYY-MM-DD) to integer format (YYYYMMDD) for SQL query\n    const dateInt = parseInt(transaction.date.replace(/-/g, ''), 10);\n\n    // Get transactions to reorder against.\n    // If this is a child transaction, scope to siblings with the same parent_id.\n    // Otherwise, get all parent transactions for the same date (excluding children).\n    // Query in DESC order to match UI display order.\n    const isChild = transaction.is_child && transaction.parent_id;\n    const transactions = await all<{ id: string; sort_order: number }>(","sourceCodeStart":904,"sourceCodeEnd":940,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/db/index.ts#L904-L940","documentation":"moveTransaction first fetches the transaction by id; if getTransaction returns nothing (missing or tombstoned row), it throws 'Transaction not found: <id>'. This is a precondition check before the move is applied, inside a batchMessages transaction so no partial writes occur.","triggerScenarios":"Calling moveTransaction with an id that never existed, a transaction deleted on this or another device (tombstoned), a stale id from cached client state, or a malformed/truncated id string.","commonSituations":"Multi-device sync where one device deleted the transaction while another tries to move it; replaying queued offline operations referencing old ids; passing a payee or category id by mistake instead of the transaction id.","solutions":["Verify the transaction id exists via getTransactions/getTransaction before moving","Refresh client state (re-sync) so stale ids are replaced with current ones","If the transaction was deleted intentionally, drop the queued move operation instead of retrying","Check that the correct id field is being passed (transaction id, not payee/schedule/category id)"],"exampleFix":"// before\nawait aqlQuery.moveTransaction(txnId, accountId, targetId);\n// after\nconst txn = await aqlQuery.getTransaction(txnId);\nif (txn) {\n  await aqlQuery.moveTransaction(txnId, accountId, targetId);\n}","handlingStrategy":"try-catch","validationCode":"const txn = await aqlQuery.getTransaction(txnId);\nif (!txn) throw new Error(`Cannot move: transaction ${txnId} not found`);","typeGuard":"function isExistingTransaction(txn: unknown): txn is { id: string; account: string; date: string } {\n  return !!txn && typeof (txn as any).id === 'string' && typeof (txn as any).account === 'string';\n}","tryCatchPattern":"try {\n  await aqlQuery.moveTransaction(txnId, accountId, targetId);\n} catch (e) {\n  if (e.message.startsWith('Transaction not found')) {\n    await resync(); // drop stale id from queue\n  } else throw e;\n}","preventionTips":["Re-sync before replaying queued operations that reference transaction ids","Never cache transaction ids across sessions without validation","Verify id source: pass transaction ids, not other entity ids","Treat deleted transactions as terminal — drop, don't retry, the operation"],"tags":["not-found","transactions","stale-data","sync"],"backgroundTag":"entity-not-found","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}