DEV Community

Cover image for Deleting a Transaction Should Be Surgical — Here's How to Do It Right
DEVANSHU PATIL
DEVANSHU PATIL

Posted on

Deleting a Transaction Should Be Surgical — Here's How to Do It Right

Deleting a Transaction Should Be Surgical — Here's How to Do It Right

In software development, "delete" sounds simple: remove the row from the database and move on.

In a relational personal finance application, however, deleting a transaction is a major architectural operation. A single transaction record often has tentacles reaching into multiple subsystems:

  • It altered account balances that must now be refunded.
  • It may have been an inter-account transfer connecting two completely separate bank entities.
  • It may have had a receipt photo stored in private app storage.
  • It may have been a companion record generated by a loan repayment or a split expense.

If you simply execute DELETE FROM transactions WHERE id = ?, you leave behind accounting scars: orphaned files consuming device storage, desynchronized loan balances, and corrupted account totals.

Here is how FinLedger designs safe, zero-leak transaction deletion across all connected subsystems.

The Web of Dependencies

Consider what happens when a user deletes a single transfer transaction of 15,000 rupees sent from checking to a savings account six months ago.

flowchart TD
    subgraph Dependencies["The Dependency Web of a Single Transaction"]
        direction TB
        Tx["Transaction Record (ID: 502)"]
        Source["Source Account (Checking: Deducted 15,000)"]
        Dest["Destination Account (Savings: Credited 15,000)"]
        Receipt["Receipt Attachment (File: receipt_502.jpg on Disk)"]
        Lending["Companion Lending Record (Debt Repayment Sync)"]

        Tx --- Source
        Tx --- Dest
        Tx --- Receipt
        Tx --- Lending
    end

If the deletion logic only removes the row from the transactions table:

  1. The checking account balance remains permanently under-reported by 15,000 rupees.
  2. The savings account balance remains permanently over-reported by 15,000 rupees.
  3. The receipt photo image file receipt_502.jpg remains abandoned in /data/user/0/com.finance.app/files/receipts/, silently consuming device disk space forever.
  4. If this transaction was linked to a loan repayment, the friend's outstanding debt is now permanently desynchronized from the actual ledger.

The Atomic Deletion Sequence

To eliminate orphaned data and balance drift, FinLedger executes transaction deletion through a unified multi-step workflow in TransactionRepository:

flowchart LR
    subgraph DeletePipeline["Unified Deletion Pipeline"]
        direction TB
        Fetch["1. Fetch Entity & Linked Attachments"] --> Postings["2. Calculate Inverted Postings"]
        Postings --> Recalc["3. Atomic SQLite Balance Reversal"]
        Recalc --> Sync["4. Sync Companion Subsystems (Lending/Split)"]
        Sync --> Disk["5. Clean Up Physical Files on Disk"]
    end

Step 1: Pre-Deletion Inspection

Before touching the database, the repository queries the target transaction entity along with its associated records:

  • Identifies the source account and destination account (if a transfer).
  • Retrieves all linked TransactionAttachmentEntity rows containing local file URIs and remote Google Drive file IDs.

Step 2: Inverting Postings and Recalculating Balances

The engine passes the transaction to Posting.kt to determine its exact historical balance impact, negating all deltas:

  • If deleting an Expense: the source account is credited back the transaction amount.
  • If deleting an Income: the destination account is debited the transaction amount.
  • If deleting a Transfer: the source account is credited, and the destination account is debited.
  • If deleting a Credit Card Purchase: the card's liability balance is reduced.

Both accounts are updated inside an atomic SQLite transaction block (AppDatabase.withTransaction).

Step 3: Two-Way Subsystem Synchronization

If the deleted transaction was connected to a specialized feature, companion entities must be updated to preserve relational truth:

  • Lending & Debt Tracker: If deleting a repayment transaction (repay_tx_<id>), syncLendingStateOnTransactionDelete immediately re-opens the loan receivable, increments the borrower's outstanding debt, and moves the person back from "Past Records" to "Active Records".
  • Split & Group Expenses: If deleting an expense split across a group, the group's net settlement balances are dynamically recalculated to reflect that the shared bill no longer exists.

Step 4: Physical Attachment Cleanup

Once the database transaction commits successfully, the repository initiates filesystem cleanup:

  • The local JPEG or PNG image files stored in the private app sandbox are deleted via standard filesystem APIs (File.delete()).
  • If Google Drive sync is enabled, the remote file deletion queue notes the deleted ID so the cloud snapshot removes the receipt on the next sync cycle.

Handling the Storage vs. Database ACID Window

During FinLedger's formal architectural audits (documented in FINLEDGER_FULL_APPLICATION_AUDIT.md), our review identified a subtle edge case common to all mobile apps:

SQLite operations are ACID-compliant, but local filesystem operations are not part of the database transaction. If the app process dies precisely between the SQLite commit and the physical disk file deletion, an orphaned image file could theoretically remain on disk.

To harden this boundary:

  • Attachment entities track their local file status explicitly.
  • The AttachmentRepository includes a background startup hygiene sweep that compares physical files in the receipts directory against active database attachment records, quietly pruning any orphan files whose companion database rows no longer exist.

Key Takeaway

Deleting a financial transaction is a reconciliation event, not a simple database row drop.

By reversing postings across all counter-party accounts, synchronizing companion debt models, and cleaning up filesystem attachments within disciplined architectural boundaries, FinLedger guarantees that deleting a mistake leaves zero accounting scars.


FinLedger is a privacy-first, offline-first personal finance application for Android that models bank accounts, credit cards, and cash without double-counting.

Explore FinLedger on Google Play: https://play.google.com/store/apps/details?id=com.devanshufinance.app

Top comments (0)