Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions backend/patches/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import testPatch from './testPatch';
import updateSchemas from './updateSchemas';
import setPaymentReferenceType from './setPaymentReferenceType';
import fixLedgerDateTime from './v0_21_0/fixLedgerDateTime';
import fixLedgerDateTimeRerun from './v0_37_0/fixLedgerDateTimeRerun';
import fixItemHSNField from './fixItemHSNField';
import createPaymentMethods from './createPaymentMethods';

Expand Down Expand Up @@ -42,6 +43,11 @@ export default [
version: '0.21.2',
patch: fixLedgerDateTime,
},
{
name: 'fixLedgerDateTimeRerun',
version: '0.37.0',
patch: fixLedgerDateTimeRerun,
},
{ name: 'fixItemHSNField', version: '0.24.0', patch: fixItemHSNField },
{
name: 'createPaymentMethods',
Expand Down
70 changes: 39 additions & 31 deletions backend/patches/v0_21_0/fixLedgerDateTime.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,48 @@
import { DatabaseManager } from '../../database/manager';

/* eslint-disable */
async function execute(dm: DatabaseManager) {
const sourceTables = [
'PurchaseInvoice',
'SalesInvoice',
'JournalEntry',
'Payment',
'StockMovement',
'StockTransfer',
];

const sourceTables = [
"PurchaseInvoice",
"SalesInvoice",
"JournalEntry",
"Payment",
"StockMovement",
"StockTransfer"
];
const entries = (await dm.db!.knex!('AccountingLedgerEntry').select(
'name',
'date',
'referenceName'
)) as Array<{ name: string; date: string; referenceName: string }>;

await dm.db!.knex!('AccountingLedgerEntry')
.select('name', 'date', 'referenceName')
.then((trx: Array<{name: string; date: Date; referenceName: string;}> ) => {
trx.forEach(async entry => {
for (const entry of entries) {
for (const table of sourceTables) {
const resp = (await dm.db!
.knex!.select('name', 'date')
.from(table)
.where({ name: entry.referenceName })) as Array<{
name: string;
date: string;
}>;

sourceTables.forEach(async table => {
await dm.db!.knex!
.select('name','date')
.from(table)
.where({ name: entry['referenceName'] })
.then(async (resp: Array<{name: string; date: Date;}>) => {
if (resp.length !== 0) {
const dateTimeValue = new Date(resp[0]['date']);
await dm.db!.knex!('AccountingLedgerEntry')
.where({ name: entry['name'] })
.update({ date: dateTimeValue.toISOString() });
}
})
});
});
});
if (resp.length !== 0) {
// Source table dates are stored as plain 'YYYY-MM-DD' strings.
// new Date('YYYY-MM-DD') is parsed as UTC midnight per ISO 8601,
// so toISOString() yields 'YYYY-MM-DDT00:00:00.000Z'.
// This ensures the stored datetime always uses UTC midnight so that
// SQLite date comparisons and report range-key extraction both work
// correctly for UTC+ timezone users.
const dateTimeValue = new Date(resp[0].date);
await dm.db!.knex!('AccountingLedgerEntry')
.where({ name: entry.name })
.update({ date: dateTimeValue.toISOString() });

// Date found in this source table; no need to check the rest.
break;
}
}
}
}

export default { execute, beforeMigrate: true };
/* eslint-enable */
63 changes: 63 additions & 0 deletions backend/patches/v0_37_0/fixLedgerDateTimeRerun.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { DatabaseManager } from '../../database/manager';

/**
* Re-runs the fixLedgerDateTime migration for users who already had the
* original v0.21.2 patch recorded as executed. That patch used forEach with
* async callbacks, which fires promises without awaiting them, so it completed
* silently without updating any data.
*
* Root cause: AccountingLedgerEntry.date was stored as local midnight for
* UTC+ timezone users (e.g. UTC+8). Local midnight on 2023-01-01 in UTC+8 is
* 2022-12-31T16:00:00.000Z. SQLite's string comparison then places
* "2022-12-31T16:00:00.000Z" < "2023-01-01", so those entries are filtered
* into the wrong year in P&L and other account reports.
*
* Fix: overwrite every AccountingLedgerEntry.date with the UTC midnight of the
* corresponding source document's date field (stored as a plain YYYY-MM-DD
* string). new Date('YYYY-MM-DD') is parsed as UTC midnight per ISO 8601, so
* toISOString() always yields 'YYYY-MM-DDT00:00:00.000Z', which compares
* correctly against plain date strings and is consistent across all timezones.
*/
async function execute(dm: DatabaseManager) {
const sourceTables = [
'PurchaseInvoice',
'SalesInvoice',
'JournalEntry',
'Payment',
'StockMovement',
'StockTransfer',
];

const entries = (await dm.db!.knex!('AccountingLedgerEntry').select(
'name',
'date',
'referenceName'
)) as Array<{ name: string; date: string; referenceName: string }>;

for (const entry of entries) {
for (const table of sourceTables) {
const resp = (await dm
.db!.knex!.select('name', 'date')
.from(table)
.where({ name: entry.referenceName })) as Array<{
name: string;
date: string;
}>;

if (resp.length !== 0) {
// Source table dates are stored as plain 'YYYY-MM-DD' strings.
// Parsing with new Date() treats them as UTC midnight (ISO 8601),
// and toISOString() produces 'YYYY-MM-DDT00:00:00.000Z'.
const dateTimeValue = new Date(resp[0].date);
await dm.db!.knex!('AccountingLedgerEntry')
.where({ name: entry.name })
.update({ date: dateTimeValue.toISOString() });

// Date found in this source table; no need to check the rest.
break;
}
}
}
}

export default { execute, beforeMigrate: true };
2 changes: 0 additions & 2 deletions models/Transactional/LedgerPosting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,6 @@ export class LedgerPosting {
return map[account];
}

// end ugly timezone fix code

const ledgerEntry = this.fyo.doc.getNewDoc(
ModelNameEnum.AccountingLedgerEntry,
{
Expand Down
Loading