mirror of
https://github.com/frappe/books.git
synced 2024-11-14 09:24:04 +00:00
287 lines
6.8 KiB
TypeScript
287 lines
6.8 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;
|
||
|
|
||
|
_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) {
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
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,
|
||
|
italics: al.isGroup,
|
||
|
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[];
|
||
|
}
|
||
|
}
|