From d04b1561cdc3947d289b5f29a5f08899468cc462 Mon Sep 17 00:00:00 2001 From: Mildred Ki'Lya Date: Fri, 1 Dec 2023 19:38:09 +0100 Subject: [PATCH 1/2] fix(reports): correct date calculation in AccountReport fyo.getValue(*, 'fiscalYear{Start,End}') returns a javascript Date object that encodes the filcal year as local time (01-01 and 12-31 at midnight). This date must not be converted to UTC else east timezones will return the day before (12-31 and 12-30). Use locale time values instead. When computing the monthly/quaterly/half yearly time periods, correct the dates in the particular case where the last day of the month is selected as reference date (yields series 12-31 11-30 10-31 09-30 ...) instead of (12-31 11-30 10-30 09-30 ...). --- reports/AccountReport.ts | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/reports/AccountReport.ts b/reports/AccountReport.ts index 2f0bba60..093bdeee 100644 --- a/reports/AccountReport.ts +++ b/reports/AccountReport.ts @@ -236,6 +236,17 @@ export abstract class AccountReport extends LedgerReport { return null; } + // Fix arythmetic on dates when adding or substracting months. If the + // reference date was the last day in month, ensure that the resulting date is + // also the last day. + _fixMonthsJump(refDate: DateTime, date: DateTime): DateTime { + if (refDate.day == refDate.daysInMonth && date.day != date.daysInMonth) { + return date.set({day: date.daysInMonth}) + } else { + return date + } + } + async _getDateRanges(): Promise { const endpoints = await this._getFromAndToDates(); const fromDate = DateTime.fromISO(endpoints.fromDate); @@ -252,7 +263,7 @@ export abstract class AccountReport extends LedgerReport { const months: number = monthsMap[this.periodicity]; const dateRanges: DateRange[] = [ - { toDate, fromDate: toDate.minus({ months }) }, + { toDate, fromDate: this._fixMonthsJump(toDate, toDate.minus({ months })) }, ]; let count = this.count ?? 1; @@ -264,7 +275,7 @@ export abstract class AccountReport extends LedgerReport { const lastRange = dateRanges.at(-1)!; dateRanges.push({ toDate: lastRange.fromDate, - fromDate: lastRange.fromDate.minus({ months }), + fromDate: this._fixMonthsJump(toDate, lastRange.fromDate.minus({ months })), }); } @@ -445,14 +456,15 @@ export async function getFiscalEndpoints( const fromDate = [ fromYear, - fys.toISOString().split('T')[0].split('-').slice(1), - ] - .flat() - .join('-'); + (fys.getMonth() + 1).toString().padStart(2, '0'), + fys.getDate().toString().padStart(2, '0') + ].join('-'); - const toDate = [toYear, fye.toISOString().split('T')[0].split('-').slice(1)] - .flat() - .join('-'); + const toDate = [ + toYear, + (fye.getMonth() + 1).toString().padStart(2, '0'), + fye.getDate().toString().padStart(2, '0') + ].join('-'); return { fromDate, toDate }; } From dd2830530b34fa2c9fdd09278475e9d3c99d0db6 Mon Sep 17 00:00:00 2001 From: Mildred Ki'Lya Date: Fri, 1 Dec 2023 20:42:14 +0100 Subject: [PATCH 2/2] fix(reports): Allow multiple root account to have the same type In French accounting, multiple root accounts can be of the same type, ensure that reports take all accounts and not only the first one. --- reports/AccountReport.ts | 41 ++++++++++------- reports/BalanceSheet/BalanceSheet.ts | 14 +++--- reports/ProfitAndLoss/ProfitAndLoss.ts | 61 ++++++++++++++++---------- reports/TrialBalance/TrialBalance.ts | 10 ++--- reports/types.ts | 2 +- 5 files changed, 78 insertions(+), 50 deletions(-) diff --git a/reports/AccountReport.ts b/reports/AccountReport.ts index 093bdeee..ad4e0591 100644 --- a/reports/AccountReport.ts +++ b/reports/AccountReport.ts @@ -64,12 +64,12 @@ export abstract class AccountReport extends LedgerReport { this._dateRanges = await this._getDateRanges(); } - getRootNode( + getRootNodes( rootType: AccountRootType, accountTree: AccountTree - ): AccountTreeNode | undefined { + ): AccountTreeNode[] | undefined { const rootNodeList = Object.values(accountTree); - return rootNodeList.find((n) => n.rootType === rootType); + return rootNodeList.filter((n) => n.rootType === rootType); } getEmptyRow(): ReportRow { @@ -88,8 +88,11 @@ export abstract class AccountReport extends LedgerReport { }; } - getTotalNode(rootNode: AccountTreeNode, name: string): AccountListNode { - const accountTree = { [rootNode.name]: rootNode }; + getTotalNode(rootNodes: AccountTreeNode[], name: string): AccountListNode { + const accountTree: Tree = {}; + for (const rootNode of rootNodes) { + accountTree[rootNode.name] = rootNode; + } const leafNodes = getListOfLeafNodes(accountTree) as AccountTreeNode[]; const totalMap = leafNodes.reduce((acc, node) => { @@ -241,9 +244,9 @@ export abstract class AccountReport extends LedgerReport { // also the last day. _fixMonthsJump(refDate: DateTime, date: DateTime): DateTime { if (refDate.day == refDate.daysInMonth && date.day != date.daysInMonth) { - return date.set({day: date.daysInMonth}) + return date.set({ day: date.daysInMonth }); } else { - return date + return date; } } @@ -263,7 +266,10 @@ export abstract class AccountReport extends LedgerReport { const months: number = monthsMap[this.periodicity]; const dateRanges: DateRange[] = [ - { toDate, fromDate: this._fixMonthsJump(toDate, toDate.minus({ months })) }, + { + toDate, + fromDate: this._fixMonthsJump(toDate, toDate.minus({ months })), + }, ]; let count = this.count ?? 1; @@ -275,7 +281,10 @@ export abstract class AccountReport extends LedgerReport { const lastRange = dateRanges.at(-1)!; dateRanges.push({ toDate: lastRange.fromDate, - fromDate: this._fixMonthsJump(toDate, lastRange.fromDate.minus({ months })), + fromDate: this._fixMonthsJump( + toDate, + lastRange.fromDate.minus({ months }) + ), }); } @@ -457,13 +466,13 @@ export async function getFiscalEndpoints( const fromDate = [ fromYear, (fys.getMonth() + 1).toString().padStart(2, '0'), - fys.getDate().toString().padStart(2, '0') + fys.getDate().toString().padStart(2, '0'), ].join('-'); const toDate = [ toYear, (fye.getMonth() + 1).toString().padStart(2, '0'), - fye.getDate().toString().padStart(2, '0') + fye.getDate().toString().padStart(2, '0'), ].join('-'); return { fromDate, toDate }; @@ -585,15 +594,17 @@ function getPrunedChildren(children: AccountTreeNode[]): AccountTreeNode[] { }); } -export function convertAccountRootNodeToAccountList( - rootNode: AccountTreeNode +export function convertAccountRootNodesToAccountList( + rootNodes: AccountTreeNode[] ): AccountList { - if (!rootNode) { + if (!rootNodes || rootNodes.length == 0) { return []; } const accountList: AccountList = []; - pushToAccountList(rootNode, accountList, 0); + for (const rootNode of rootNodes) { + pushToAccountList(rootNode, accountList, 0); + } return accountList; } diff --git a/reports/BalanceSheet/BalanceSheet.ts b/reports/BalanceSheet/BalanceSheet.ts index b4650b0c..7f404dfd 100644 --- a/reports/BalanceSheet/BalanceSheet.ts +++ b/reports/BalanceSheet/BalanceSheet.ts @@ -5,7 +5,7 @@ import { } from 'models/baseModels/Account/types'; import { AccountReport, - convertAccountRootNodeToAccountList, + convertAccountRootNodesToAccountList, } from 'reports/AccountReport'; import { ReportData, RootTypeRow } from 'reports/types'; import { getMapFromList } from 'utils'; @@ -44,15 +44,15 @@ export class BalanceSheet extends AccountReport { const rootTypeRows: RootTypeRow[] = this.rootTypes .map((rootType) => { - const rootNode = this.getRootNode(rootType, accountTree)!; - const rootList = convertAccountRootNodeToAccountList(rootNode); + const rootNodes = this.getRootNodes(rootType, accountTree)!; + const rootList = convertAccountRootNodesToAccountList(rootNodes); return { rootType, - rootNode, + rootNodes, rows: this.getReportRowsFromAccountList(rootList), }; }) - .filter((row) => !!row.rootNode); + .filter((row) => !!row.rootNodes.length); this.reportData = this.getReportDataFromRows( getMapFromList(rootTypeRows, 'rootType') @@ -88,8 +88,8 @@ export class BalanceSheet extends AccountReport { reportData.push(...row.rows); - if (row.rootNode) { - const totalNode = this.getTotalNode(row.rootNode, totalName); + if (row.rootNodes.length) { + const totalNode = this.getTotalNode(row.rootNodes, totalName); const totalRow = this.getRowFromAccountListNode(totalNode); reportData.push(totalRow); } diff --git a/reports/ProfitAndLoss/ProfitAndLoss.ts b/reports/ProfitAndLoss/ProfitAndLoss.ts index 859a3e42..cf6edbc4 100644 --- a/reports/ProfitAndLoss/ProfitAndLoss.ts +++ b/reports/ProfitAndLoss/ProfitAndLoss.ts @@ -5,7 +5,7 @@ import { } from 'models/baseModels/Account/types'; import { AccountReport, - convertAccountRootNodeToAccountList, + convertAccountRootNodesToAccountList, } from 'reports/AccountReport'; import { AccountListNode, @@ -45,28 +45,28 @@ export class ProfitAndLoss extends AccountReport { /** * Income Rows */ - const incomeRoot = this.getRootNode( + const incomeRoots = this.getRootNodes( AccountRootTypeEnum.Income, accountTree )!; - const incomeList = convertAccountRootNodeToAccountList(incomeRoot); + const incomeList = convertAccountRootNodesToAccountList(incomeRoots); const incomeRows = this.getReportRowsFromAccountList(incomeList); /** * Expense Rows */ - const expenseRoot = this.getRootNode( + const expenseRoots = this.getRootNodes( AccountRootTypeEnum.Expense, accountTree )!; - const expenseList = convertAccountRootNodeToAccountList(expenseRoot); + const expenseList = convertAccountRootNodesToAccountList(expenseRoots); const expenseRows = this.getReportRowsFromAccountList(expenseList); this.reportData = this.getReportDataFromRows( incomeRows, expenseRows, - incomeRoot, - expenseRoot + incomeRoots, + expenseRoots ); this.loading = false; } @@ -74,43 +74,57 @@ export class ProfitAndLoss extends AccountReport { getReportDataFromRows( incomeRows: ReportData, expenseRows: ReportData, - incomeRoot: AccountTreeNode | undefined, - expenseRoot: AccountTreeNode | undefined + incomeRoots: AccountTreeNode[] | undefined, + expenseRoots: AccountTreeNode[] | undefined ): ReportData { - if (incomeRoot && !expenseRoot) { + if ( + incomeRoots && + incomeRoots.length && + !expenseRoots && + !expenseRoots.length + ) { return this.getIncomeOrExpenseRows( - incomeRoot, + incomeRoots, incomeRows, t`Total Income (Credit)` ); } - if (expenseRoot && !incomeRoot) { + if ( + expenseRoots && + expenseRoots.length && + (!incomeRoots || !incomeRoots.length) + ) { return this.getIncomeOrExpenseRows( - expenseRoot, + expenseRoots, expenseRows, t`Total Income (Credit)` ); } - if (!incomeRoot || !expenseRoot) { + if ( + !incomeRoots || + !incomeRoots.length || + !expenseRoots || + !expenseRoots.length + ) { return []; } return this.getIncomeAndExpenseRows( incomeRows, expenseRows, - incomeRoot, - expenseRoot + incomeRoots, + expenseRoots ); } getIncomeOrExpenseRows( - root: AccountTreeNode, + roots: AccountTreeNode[], rows: ReportData, totalRowName: string ): ReportData { - const total = this.getTotalNode(root, totalRowName); + const total = this.getTotalNode(roots, totalRowName); const totalRow = this.getRowFromAccountListNode(total); return [rows, totalRow].flat(); @@ -119,14 +133,17 @@ export class ProfitAndLoss extends AccountReport { getIncomeAndExpenseRows( incomeRows: ReportData, expenseRows: ReportData, - incomeRoot: AccountTreeNode, - expenseRoot: AccountTreeNode + incomeRoots: AccountTreeNode[], + expenseRoots: AccountTreeNode[] ) { - const totalIncome = this.getTotalNode(incomeRoot, t`Total Income (Credit)`); + const totalIncome = this.getTotalNode( + incomeRoots, + t`Total Income (Credit)` + ); const totalIncomeRow = this.getRowFromAccountListNode(totalIncome); const totalExpense = this.getTotalNode( - expenseRoot, + expenseRoots, t`Total Expense (Debit)` ); const totalExpenseRow = this.getRowFromAccountListNode(totalExpense); diff --git a/reports/TrialBalance/TrialBalance.ts b/reports/TrialBalance/TrialBalance.ts index edc3047c..24e98cc5 100644 --- a/reports/TrialBalance/TrialBalance.ts +++ b/reports/TrialBalance/TrialBalance.ts @@ -9,7 +9,7 @@ import { AccountReport, ACC_BAL_WIDTH, ACC_NAME_WIDTH, - convertAccountRootNodeToAccountList, + convertAccountRootNodesToAccountList, getFiscalEndpoints, } from 'reports/AccountReport'; import { @@ -65,15 +65,15 @@ export class TrialBalance extends AccountReport { const rootTypeRows: RootTypeRow[] = this.rootTypes .map((rootType) => { - const rootNode = this.getRootNode(rootType, accountTree)!; - const rootList = convertAccountRootNodeToAccountList(rootNode); + const rootNodes = this.getRootNodes(rootType, accountTree)!; + const rootList = convertAccountRootNodesToAccountList(rootNodes); return { rootType, - rootNode, + rootNodes, rows: this.getReportRowsFromAccountList(rootList), }; }) - .filter((row) => !!row.rootNode); + .filter((row) => !!(row.rootNodes && row.rootNodes.length)); this.reportData = await this.getReportDataFromRows(rootTypeRows); this.loading = false; diff --git a/reports/types.ts b/reports/types.ts index 8e010b44..9da97b3e 100644 --- a/reports/types.ts +++ b/reports/types.ts @@ -107,6 +107,6 @@ export type Tree = Record; export type RootTypeRow = { rootType: AccountRootType; - rootNode: AccountTreeNode; + rootNodes: AccountTreeNode[]; rows: ReportData; }; \ No newline at end of file