{"record":{"id":"f48bbee5554aafcc","repo":"actualbudget/actual","slug":"transaction-id-does-not-belong-to-account-acc","errorCode":null,"errorMessage":"Transaction ${id} does not belong to account ${accountId}","messagePattern":"Transaction (.+?) does not belong to account (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/db/index.ts","lineNumber":927,"sourceCode":" *\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 }>(\n      isChild\n        ? `SELECT vt.id, vt.sort_order\n           FROM v_transactions vt\n           WHERE vt.parent_id = ?\n           ORDER BY sort_order DESC, id`","sourceCodeStart":909,"sourceCodeEnd":945,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/db/index.ts#L909-L945","documentation":"moveTransaction also verifies the transaction's account field matches the accountId argument; a mismatch throws 'Transaction <id> does not belong to account <accountId>'. This prevents moving a transaction between positions of an account it does not belong to.","triggerScenarios":"Calling moveTransaction with an accountId different from transaction.account — e.g. passing the target account instead of the source account, or moving within account B a transaction that lives in account A.","commonSituations":"UI drag-and-drop sending the wrong account context; off-by-one after a transaction was transferred between accounts (its account field changed); batch operations iterating accounts with mismatched id variables.","solutions":["Pass the account id the transaction currently belongs to (transaction.account), not the destination account","Re-fetch the transaction to get its current account before calling moveTransaction","If the goal is to change the transaction's account, update the transaction's account field instead of using moveTransaction","Catch the error, re-sync, and retry with the corrected accountId"],"exampleFix":"// before\nawait aqlQuery.moveTransaction(txn.id, targetAccountId, targetId);\n// after\nawait aqlQuery.moveTransaction(txn.id, txn.account, targetId);","handlingStrategy":"validation","validationCode":"const txn = await aqlQuery.getTransaction(txnId);\nif (txn && txn.account !== accountId) {\n  throw new Error(`Transaction ${txnId} belongs to ${txn.account}, not ${accountId}`);\n}","typeGuard":"function belongsToAccount(txn: { account: string } | null, accountId: string): txn is { account: string } {\n  return txn !== null && txn.account === accountId;\n}","tryCatchPattern":"try {\n  await aqlQuery.moveTransaction(txnId, accountId, targetId);\n} catch (e) {\n  if (e.message.includes('does not belong to account')) {\n    const txn = await aqlQuery.getTransaction(txnId);\n    await aqlQuery.moveTransaction(txnId, txn.account, targetId);\n  } else throw e;\n}","preventionTips":["Always pass the transaction's current account (txn.account), not the destination","Re-fetch the transaction before moving in case an account transfer changed ownership","In drag-and-drop UIs, bind the source account, not the drop target","For account changes, update the transaction record instead of using moveTransaction"],"tags":["validation","transactions","ownership","argument-mismatch"],"backgroundTag":"entity-ownership-mismatch","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}