2
0
mirror of https://github.com/frappe/books.git synced 2024-11-14 17:34:04 +00:00
books/reports/TrialBalance/TrialBalance.ts
Mildred Ki'Lya dd2830530b 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.
2023-12-05 21:38:25 +01:00

292 lines
7.1 KiB
TypeScript

import { t } from 'fyo';
import { ValueError } from 'fyo/utils/errors';
import { DateTime } from 'luxon';
import {
AccountRootType,
AccountRootTypeEnum,
} from 'models/baseModels/Account/types';
import {
AccountReport,
ACC_BAL_WIDTH,
ACC_NAME_WIDTH,
convertAccountRootNodesToAccountList,
getFiscalEndpoints,
} from 'reports/AccountReport';
import {
Account,
AccountListNode,
AccountNameValueMapMap,
ColumnField,
DateRange,
GroupedMap,
LedgerEntry,
ReportCell,
ReportData,
ReportRow,
RootTypeRow,
ValueMap,
} from 'reports/types';
import { Field } from 'schemas/types';
import { QueryFilter } from 'utils/db/types';
export class TrialBalance extends AccountReport {
static title = t`Trial Balance`;
static reportName = 'trial-balance';
fromDate?: string;
toDate?: string;
hideGroupAmounts = false;
loading = false;
_rawData: LedgerEntry[] = [];
_dateRanges?: DateRange[];
accountMap?: Record<string, Account>;
get rootTypes(): AccountRootType[] {
return [
AccountRootTypeEnum.Asset,
AccountRootTypeEnum.Liability,
AccountRootTypeEnum.Income,
AccountRootTypeEnum.Expense,
AccountRootTypeEnum.Equity,
];
}
async setReportData(filter?: string, force?: boolean) {
this.loading = true;
if (force || filter !== 'hideGroupAmounts') {
await this._setRawData();
}
const map = this._getGroupedMap(true, 'account');
const rangeGroupedMap = await this._getGroupedByDateRanges(map);
const accountTree = await this._getAccountTree(rangeGroupedMap);
const rootTypeRows: RootTypeRow[] = this.rootTypes
.map((rootType) => {
const rootNodes = this.getRootNodes(rootType, accountTree)!;
const rootList = convertAccountRootNodesToAccountList(rootNodes);
return {
rootType,
rootNodes,
rows: this.getReportRowsFromAccountList(rootList),
};
})
.filter((row) => !!(row.rootNodes && row.rootNodes.length));
this.reportData = await this.getReportDataFromRows(rootTypeRows);
this.loading = false;
}
// eslint-disable-next-line @typescript-eslint/require-await
async getReportDataFromRows(
rootTypeRows: RootTypeRow[]
): Promise<ReportData> {
const reportData = rootTypeRows.reduce((reportData, r) => {
reportData.push(...r.rows);
reportData.push(this.getEmptyRow());
return reportData;
}, [] as ReportData);
reportData.pop();
return reportData;
}
// eslint-disable-next-line @typescript-eslint/require-await
async _getGroupedByDateRanges(
map: GroupedMap
): Promise<AccountNameValueMapMap> {
const accountValueMap: AccountNameValueMapMap = new Map();
for (const account of map.keys()) {
const valueMap: ValueMap = new Map();
/**
* Set Balance for every DateRange key
*/
for (const entry of map.get(account)!) {
const key = this._getRangeMapKey(entry);
if (key === null) {
throw new ValueError(
`invalid entry in trial balance ${entry.date?.toISOString() ?? ''}`
);
}
const map = valueMap.get(key);
const totalCredit = map?.credit ?? 0;
const totalDebit = map?.debit ?? 0;
valueMap.set(key, {
credit: totalCredit + (entry.credit ?? 0),
debit: totalDebit + (entry.debit ?? 0),
});
}
accountValueMap.set(account, valueMap);
}
return accountValueMap;
}
async _getDateRanges(): Promise<DateRange[]> {
if (!this.toDate || !this.fromDate) {
await this.setDefaultFilters();
}
const toDate = DateTime.fromISO(this.toDate!);
const fromDate = DateTime.fromISO(this.fromDate!);
return [
{
fromDate: DateTime.fromISO('0001-01-01'),
toDate: fromDate,
},
{ fromDate, toDate },
{
fromDate: toDate,
toDate: DateTime.fromISO('9999-12-31'),
},
];
}
getRowFromAccountListNode(al: AccountListNode) {
const nameCell = {
value: al.name,
rawValue: al.name,
align: 'left',
width: ACC_NAME_WIDTH,
bold: !al.level,
indent: al.level ?? 0,
} as ReportCell;
const balanceCells = this._dateRanges!.map((k) => {
const map = al.valueMap?.get(k);
const hide = this.hideGroupAmounts && al.isGroup;
return [
{
rawValue: map?.debit ?? 0,
value: hide ? '' : this.fyo.format(map?.debit ?? 0, 'Currency'),
align: 'right',
width: ACC_BAL_WIDTH,
},
{
rawValue: map?.credit ?? 0,
value: hide ? '' : this.fyo.format(map?.credit ?? 0, 'Currency'),
align: 'right',
width: ACC_BAL_WIDTH,
} as ReportCell,
];
});
return {
cells: [nameCell, balanceCells].flat(2),
level: al.level,
isGroup: !!al.isGroup,
folded: false,
foldedBelow: false,
} as ReportRow;
}
// eslint-disable-next-line @typescript-eslint/require-await
async _getQueryFilters(): Promise<QueryFilter> {
const filters: QueryFilter = {};
filters.reverted = false;
return filters;
}
async setDefaultFilters(): Promise<void> {
if (!this.toDate || !this.fromDate) {
const { year } = DateTime.now();
const endpoints = await getFiscalEndpoints(year + 1, year, this.fyo);
this.fromDate = endpoints.fromDate;
this.toDate = DateTime.fromISO(endpoints.toDate)
.minus({ days: 1 })
.toISODate();
}
await this._setDateRanges();
}
getFilters(): Field[] {
return [
{
fieldtype: 'Date',
fieldname: 'fromDate',
placeholder: t`From Date`,
label: t`From Date`,
required: true,
},
{
fieldtype: 'Date',
fieldname: 'toDate',
placeholder: t`To Date`,
label: t`To Date`,
required: true,
},
{
fieldtype: 'Check',
label: t`Hide Group Amounts`,
fieldname: 'hideGroupAmounts',
} as Field,
] as Field[];
}
getColumns(): ColumnField[] {
return [
{
label: t`Account`,
fieldtype: 'Link',
fieldname: 'account',
align: 'left',
width: ACC_NAME_WIDTH,
},
{
label: t`Opening (Dr)`,
fieldtype: 'Data',
fieldname: 'openingDebit',
align: 'right',
width: ACC_BAL_WIDTH,
},
{
label: t`Opening (Cr)`,
fieldtype: 'Data',
fieldname: 'openingCredit',
align: 'right',
width: ACC_BAL_WIDTH,
},
{
label: t`Debit`,
fieldtype: 'Data',
fieldname: 'debit',
align: 'right',
width: ACC_BAL_WIDTH,
},
{
label: t`Credit`,
fieldtype: 'Data',
fieldname: 'credit',
align: 'right',
width: ACC_BAL_WIDTH,
},
{
label: t`Closing (Dr)`,
fieldtype: 'Data',
fieldname: 'closingDebit',
align: 'right',
width: ACC_BAL_WIDTH,
},
{
label: t`Closing (Cr)`,
fieldtype: 'Data',
fieldname: 'closingCredit',
align: 'right',
width: ACC_BAL_WIDTH,
},
] as ColumnField[];
}
}