Skip to content

fix: correctly migrate AccountingLedgerEntry dates for UTC+ timezones#1521

Draft
the-narwhal wants to merge 1 commit intofrappe:masterfrom
the-narwhal:bug-Profit-and-Loss-report-showing-incorrect-data
Draft

fix: correctly migrate AccountingLedgerEntry dates for UTC+ timezones#1521
the-narwhal wants to merge 1 commit intofrappe:masterfrom
the-narwhal:bug-Profit-and-Loss-report-showing-incorrect-data

Conversation

@the-narwhal
Copy link
Copy Markdown
Contributor

This is a draft PR — feedback, discussion, and testing are very welcome.

I ran into issue #819 and may have traced it down to the root cause. I'm not deeply familiar with all the internals of this issue, so I'd especially appreciate a second set of eyes on the patch logic and the version number chosen for the rerun patch. If you're a UTC+ timezone user who has been affected, testing against a real database with pre-existing entries would be incredibly helpful in confirming the fix works end-to-end.

Fixes #819 — P&L report shows entries in the wrong year for UTC+ timezone users

Root cause

AccountingLedgerEntry.date is a Datetime field serialised by calling Date.prototype.toISOString(). For a UTC+ user (e.g. Malaysia, UTC+8), local midnight on 2023-01-01 is 2022-12-31T16:00:00.000Z in UTC. SQLite stores that literal string, and the P&L filter

WHERE date >= '2023-01-01'

is a plain string comparison: "2022-12-31T16:00:00.000Z" < "2023-01-01" → the entry is silently placed in 2022 instead of 2023.

What was already partially in place

timezoneDateTimeAdjuster (added Feb 2024 in LedgerPosting) correctly forces every new ledger entry to UTC midnight ("2023-01-01T00:00:00.000Z"), whose UTC date string always matches the user's intended date. The fixLedgerDateTime migration patch was meant to backfill existing entries with the same fix — but it contained a critical async bug.

The broken patch

// forEach ignores the Promise returned by async callbacks.
// The outer await only waits for the SELECT, not the UPDATEs.
trx.forEach(async entry => {
  sourceTables.forEach(async table => {
    await knex('AccountingLedgerEntry').update(...)  // never actually awaited
  });
});

No exception was thrown, so the patch was recorded as failed: false in the PatchRun table and will never retry.

Changes

File Change
backend/patches/v0_21_0/fixLedgerDateTime.ts Rewrote with for...of + await — fixes new installs upgrading from old versions
backend/patches/v0_37_0/fixLedgerDateTimeRerun.ts New patch, new name (fixLedgerDateTimeRerun) at v0.37.0 — runs for users who already had the broken patch silently execute
backend/patches/index.ts Registers the new patch
models/Transactional/LedgerPosting.ts Removes orphaned // end ugly timezone fix code comment

Why a new patch name?

The patch runner only re-executes a patch if it previously recorded failed: true. Since the broken patch recorded failed: false, it will never retry under the same name. A new name (fixLedgerDateTimeRerun) at v0.37.0 ensures every user whose DB version is ≤ 0.37.0 — i.e. everyone currently running the app — gets the migration applied on their next launch.

Who is affected

Only UTC+ timezone users (UTC+8, UTC+5:30, etc.) with entries created before the timezoneDateTimeAdjuster was introduced. UTC and UTC− users were never affected because their local midnight falls on the same or a later UTC day, so the stored string already compared correctly.

The fixLedgerDateTime patch (v0.21.2) used forEach with async callbacks,
which fires promises without awaiting them. The outer await resolved
immediately after the SELECT, so no UPDATE statements ever executed.
The patch was silently recorded as succeeded in PatchRun, preventing
any retry.

- Rewrite fixLedgerDateTime using for...of loops with proper await
- Add fixLedgerDateTimeRerun patch (v0.37.0) under a new name so users
  who already had the broken patch recorded as executed get the fix
  applied on their next launch
- Remove orphaned '// end ugly timezone fix code' comment in LedgerPosting
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Profit and Loss report showing incorrect data

1 participant