# Correct Transactions

Learn how to replace a posted transaction under the same transaction ID with a correction.


A ledger never edits a posting. When a posted transaction is wrong — the amount, the accounts, the effective date, or even the tran code — the fix is to void it and post it again. A **correction** does both in one call, and keeps the transaction's ID.

To correct a transaction, post it again with a `correction`. Give each correction its own `correctionId`, and optionally a `description` and `metadata` that say why:

```graphql
mutation CorrectTransaction($accountAliciaId: UUID!, $accountBobbyId: UUID!) {
  postTransaction(
    input: {
      transactionId: "6b5e47b6-60d2-49bf-8210-0e4c3dd3ec68"
      tranCode: "BOOK_TRANSFER"
      params: {
        crAccount: $accountAliciaId
        drAccount: $accountBobbyId
        amount: "1.50"
        currency: "USD"
        effective: "2022-09-08"
      }
      properties: {
        correction: {
          correctionId: "22046f82-dd5f-40b1-b0dc-2cc2e3afff23"
          description: "Wrong amount"
          metadata: { ticket: "OPS-1234" }
        }
      }
    }
  ) {
    transactionId
    version
    correction {
      correctionId
      description
    }
  }
}
```

## What a correction does

| The transaction with this ID                | Result                                                                 |
|---------------------------------------------|------------------------------------------------------------------------|
| Does not exist                              | It is posted. Version 1 points to the correction.                      |
| Exists                                      | The transaction is corrected in place, even when the new values are the same. |
| Is voided, or is itself a void              | `INVALID_STATE_TRANSITION`.                                            |

A correction writes two new versions of the transaction, under the same `transactionId`. Both point to the correction:

1. A **void**. It is a copy of the version being corrected, and its entries void that version's entries.
2. The **corrected** transaction, with the new tran code, params and entries.

A transaction posted at version 1 and then corrected is at version 3. The versions before the correction stay in `history`.

On every version, `correction` is the first correction that wrote that version or a later one, and `correction.transaction` is the version that correction wrote. So versions 1 to 3 above all resolve to the correction, and to version 3. An `updateTransaction` after the last correction resolves to that correction until another correction is made; then it resolves to the new one. `correction` is `null` on a transaction that was never corrected.

## Retries

A `correctionId` can be used once. A repeat of it fails with `ALREADY_EXISTS`, whatever its values.

With `idempotent: true`, a repeat returns the version the correction wrote, and writes nothing, when it asks for the same correction: the same `transactionId`, `description`, `metadata` and `voidEffective`, and the same transaction. Otherwise it fails with `BAD_REQUEST`. "The same transaction" means the same tran code, journal, effective date, correlation ID, external ID, description, metadata and groups, and the same entries: account, currency, amount, direction, layer, entry type, description and metadata. Amounts compare by value, so `10` and `10.00` are the same.

The tran code runs again on a repeat, so a compared field must come out the same each time. A field that a tran code computes from the clock or from `uuid.New()` differs on each run, and its repeats fail with `BAD_REQUEST`. Pass such values in as params, so a repeat sends the same ones.

The repeat compares with the version the correction wrote, not the current version. So a late retry of a correction does not undo a later correction.

An `idempotent: true` post without a `correction` compares with the current version, as it does on any transaction. After a correction, that is the corrected transaction. A late retry of the original post fails with `BAD_REQUEST`, because the correction changed the transaction. A post of the corrected values returns the current version and writes nothing.

So to make a post and its corrections safe to retry, in any order, post the transaction with a `correction` too, and set `idempotent: true` on each post. Each retry then finds its own `correctionId` and returns the version it wrote.

A `correctionId` only stops a correction from applying twice. It does not order corrections that were never applied. If correction `c1` did not commit, and is sent again after correction `c2` committed, `c1` is not a repeat: it corrects the transaction `c2` left, and becomes the current version. To keep the corrections of one transaction in order, send each one after the one before it has committed, or send them in order in one `PostTransactions` request.

Read a correction with `correction(id:)`. List the corrections of a transaction with `corrections(index: { name: TRANSACTION_ID }, where: { transactionId: { eq: ... } })`, or with a custom index on `Correction` (`createIndex(input: { on: Correction, ... })`) and `index: { name: CUSTOM }`.

## Entries after a correction

Every entry stays under the transaction's ID, numbered by `sequence`:

| Sequence | Entries                     | `transactionVersion` |
|----------|-----------------------------|----------------------|
| 0–1      | The original entries        | 1                    |
| 2–3      | The entries voiding them    | 2                    |
| 4–5      | The corrected entries       | 3                    |

- `Transaction.entries` returns the entries of that version only. On the current transaction those are the corrected entries; on a `history` node they are that version's entries.
- `Transaction.entries(all: true)` returns every entry under the ID.
- `Entry.transaction` resolves to the version the entry belongs to. For the original entries that is version 1, not the corrected transaction. So `Entry.transaction.correction` of a void or corrected entry is the correction that wrote it, and of an original entry the correction that replaced it.

The original entries are marked `voidedBy` their void, and the void entries have `voidOf` set, as `voidTransaction` does.

## Balances and calculations

The void and the corrected entries post together. Calculations evaluate each entry against the version it belongs to, so the void entries land in the same dimensions as the entries they void. A calculation by `context.vars.transaction.metadata.memo`, for example, moves the amount from the old memo's bucket to the new one.

`context.vars.transaction` is the **current** version of the transaction an entry belongs to. If `updateTransaction` changed the metadata after the post, a calculation attached later and backfilled sees the updated metadata. A correction's void copies the current version, as `voidTransaction` does.

## The void's effective date

By default the void has the effective date of the version it voids. Set `voidEffective` to date it later, for example in the current period when the original's period is closed. It must not be before the original effective date. A correction that posts a new transaction has no void and ignores it.

```graphql
properties: {
  correction: { correctionId: "5b0f3c2e-8d1a-4e7b-9c6f-2a4d8e1b3c70", voidEffective: "2022-09-30" }
}
```

## Corrections in a `PostTransactions` request

The gRPC `PostTransactions` API posts a list of posts and voids in one request, all or nothing. A correction in a request does what it does alone, in the request's order, and it shares the request's balance writes with the posts around it. These cases differ from the same operations sent one call at a time:

| Case                                                              | One call at a time                                                                          | One `PostTransactions` request                                      |
|-------------------------------------------------------------------|---------------------------------------------------------------------------------------------|---------------------------------------------------------------------|
| Two posts with the same `correctionId`                            | The second is a repeat: `ALREADY_EXISTS`, or with `idempotent: true` the version the first wrote. | `BAD_REQUEST`.                                                      |
| A post of a transaction ID, then a correction of it               | The correction corrects the posted transaction.                                             | The same. The correction builds on the post before it in the request. |
| A correction, then a post of the same ID without a `correction`   | The post fails with `ALREADY_EXISTS`, or with `idempotent: true` compares with the corrected transaction. | `BAD_REQUEST`. Every later post of an ID in a request must be a correction. |
| A void of the transaction, then a correction of it                | The correction fails with `INVALID_STATE_TRANSITION`.                                      | `BAD_REQUEST`. A void must come after every post of its transaction. |
| An `exception` or `asVoidTransaction` post, then a correction of the same ID | The correction fails with `INVALID_STATE_TRANSITION`: the transaction is voided, or is a void. | `BAD_REQUEST`.                                                      |

A `dependsOn` of a transaction that the request posts is met from its first post on. Velocity controls check a post and each correction of it as if they were sent one at a time, in the request's order; see [Velocity Controls](https://www.twisp.com/docs/reference/ledger/velocity-controls.md).

## Limits

- A correction cannot be combined with `exception` or `asVoidTransaction`.
- System tran codes, and tran codes that start a workflow, cannot be used in a correction, as either the old or the new tran code. Their workflows expect a posting not to change.
- A voided transaction cannot be corrected. A corrected transaction can be voided; the void covers the corrected entries only.
- Two different corrections of one transaction that run at the same time do not both apply: one commits, and the other fails with a retryable error. A retry corrects the transaction the first one left, so the last commit wins.
- A correction is not checked against the version it replaces. A late correction that was never applied applies on top of later ones (see [Retries](https://www.twisp.com/docs/tutorials/advanced/correct-transactions.md#retries)).
- A correction reads and writes the `Correction` table. A client whose policies list tables one by one needs access to it to post or read corrections.
- A correction writes row versions and entries like any post. Webhook consumers receive `transaction.updated` for both new versions and `entry.inserted` for the new entries. The `correctionId` on the versions tells a correction from an `updateTransaction`.
