2
0
mirror of https://github.com/frappe/books.git synced 2024-11-10 07:40:55 +00:00
books/reports/TrialBalance/TrialBalance.ts
2022-05-26 00:28:54 +05:30

289 lines
6.9 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,
convertAccountRootNodeToAccountList,
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: boolean = false;
loading: boolean = 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) {
this.loading = true;
if (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 rootNode = this.getRootNode(rootType, accountTree)!;
const rootList = convertAccountRootNodeToAccountList(rootNode);
return {
rootType,
rootNode,
rows: this.getReportRowsFromAccountList(rootList),
};
})
.filter((row) => !!row.rootNode);
this.reportData = await this.getReportDataFromRows(rootTypeRows);
this.loading = false;
}
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;
}
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;
}
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.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[];
}
}