2
0
mirror of https://github.com/frappe/books.git synced 2024-11-08 14:50:56 +00:00

incr: add the TrialBalance report

This commit is contained in:
18alantom 2022-05-16 18:31:58 +05:30
parent 06b5ac4f90
commit 63c2eace33
8 changed files with 342 additions and 47 deletions

View File

@ -31,8 +31,8 @@ import { fyo } from 'src/initFyo';
import { getMapFromList } from 'utils';
import { QueryFilter } from 'utils/db/types';
const ACC_NAME_WIDTH = 2;
const ACC_BAL_WIDTH = 1;
export const ACC_NAME_WIDTH = 2;
export const ACC_BAL_WIDTH = 1;
export abstract class AccountReport extends LedgerReport {
toDate?: string;
@ -40,7 +40,7 @@ export abstract class AccountReport extends LedgerReport {
fromYear?: number;
toYear?: number;
consolidateColumns: boolean = false;
hideGroupBalance: boolean = false;
hideGroupAmounts: boolean = false;
periodicity: Periodicity = 'Monthly';
basedOn: BasedOn = 'Until Date';
@ -48,12 +48,6 @@ export abstract class AccountReport extends LedgerReport {
_dateRanges?: DateRange[];
accountMap?: Record<string, Account>;
abstract get rootTypes(): AccountRootType[];
async initialize(): Promise<void> {
await super.initialize();
await this._setDateRanges();
}
async setDefaultFilters(): Promise<void> {
if (this.basedOn === 'Until Date' && !this.toDate) {
@ -105,9 +99,9 @@ export abstract class AccountReport extends LedgerReport {
const totalMap = leafNodes.reduce((acc, node) => {
for (const key of this._dateRanges!) {
const bal = acc.get(key) ?? 0;
const val = node.valueMap?.get(key) ?? 0;
acc.set(key, bal + val);
const bal = acc.get(key)?.balance ?? 0;
const val = node.valueMap?.get(key)?.balance ?? 0;
acc.set(key, { balance: bal + val });
}
return acc;
@ -134,9 +128,9 @@ export abstract class AccountReport extends LedgerReport {
} as ReportCell;
const balanceCells = this._dateRanges!.map((k) => {
const rawValue = al.valueMap?.get(k) ?? 0;
const rawValue = al.valueMap?.get(k)?.balance ?? 0;
let value = this.fyo.format(rawValue, 'Currency');
if (this.hideGroupBalance && al.isGroup) {
if (this.hideGroupAmounts && al.isGroup) {
value = '';
}
@ -175,14 +169,14 @@ export abstract class AccountReport extends LedgerReport {
continue;
}
const totalBalance = valueMap.get(key!) ?? 0;
const totalBalance = valueMap.get(key!)?.balance ?? 0;
const balance = (entry.debit ?? 0) - (entry.credit ?? 0);
const rootType = accountMap[entry.account].rootType;
if (isCredit(rootType)) {
valueMap.set(key!, totalBalance - balance);
valueMap.set(key!, { balance: totalBalance - balance });
} else {
valueMap.set(key!, totalBalance + balance);
valueMap.set(key!, { balance: totalBalance + balance });
}
}
accountValueMap.set(account, valueMap);
@ -232,7 +226,7 @@ export abstract class AccountReport extends LedgerReport {
const toDate = dr.toDate.toMillis();
const fromDate = dr.fromDate.toMillis();
if (entryDate <= toDate && entryDate > fromDate) {
if (fromDate < entryDate && entryDate <= toDate) {
return dr;
}
}
@ -386,8 +380,8 @@ export abstract class AccountReport extends LedgerReport {
} as Field,
{
fieldtype: 'Check',
label: t`Hide Group Balance`,
fieldname: 'hideGroupBalance',
label: t`Hide Group Amounts`,
fieldname: 'hideGroupAmounts',
} as Field,
].flat();
}
@ -426,7 +420,7 @@ export abstract class AccountReport extends LedgerReport {
metaFilters: string[] = ['basedOn'];
}
async function getFiscalEndpoints(toYear: number, fromYear: number) {
export async function getFiscalEndpoints(toYear: number, fromYear: number) {
const fys = (await fyo.getValue(
ModelNameEnum.AccountingSettings,
'fiscalYearStart'
@ -503,8 +497,14 @@ function updateParentAccountWithChildValues(
parentAccount.valueMap ??= new Map();
for (const key of valueMap.keys()) {
const value = parentAccount.valueMap!.get(key) ?? 0;
parentAccount.valueMap!.set(key, value + valueMap.get(key)!);
const value = parentAccount.valueMap!.get(key);
const childValue = valueMap.get(key);
const map: Record<string, number> = {};
for (const key of Object.keys(childValue!)) {
map[key] = (value?.[key] ?? 0) + (childValue?.[key] ?? 0);
}
parentAccount.valueMap!.set(key, map);
}
return parentAccount.parentAccount!;

View File

@ -7,15 +7,9 @@ import {
AccountReport,
convertAccountRootNodeToAccountList,
} from 'reports/AccountReport';
import { AccountTreeNode, ReportData } from 'reports/types';
import { ReportData, RootTypeRow } from 'reports/types';
import { getMapFromList } from 'utils';
type RootTypeRow = {
rootType: AccountRootType;
rootNode: AccountTreeNode;
rows: ReportData;
};
export class BalanceSheet extends AccountReport {
static title = t`Balance Sheet`;
static reportName = 'balance-sheet';
@ -29,7 +23,7 @@ export class BalanceSheet extends AccountReport {
}
async setReportData(filter?: string) {
if (filter !== 'hideGroupBalance') {
if (filter !== 'hideGroupAmounts') {
await this._setRawData();
}

View File

@ -7,7 +7,12 @@ import {
AccountReport,
convertAccountRootNodeToAccountList,
} from 'reports/AccountReport';
import { AccountListNode, AccountTreeNode, ReportData } from 'reports/types';
import {
AccountListNode,
AccountTreeNode,
ReportData,
ValueMap,
} from 'reports/types';
export class ProfitAndLoss extends AccountReport {
static title = t`Profit And Loss`;
@ -18,7 +23,7 @@ export class ProfitAndLoss extends AccountReport {
}
async setReportData(filter?: string) {
if (filter !== 'hideGroupBalance') {
if (filter !== 'hideGroupAmounts') {
await this._setRawData();
}
@ -78,11 +83,11 @@ export class ProfitAndLoss extends AccountReport {
t`Total Expense (Debit)`
);
const totalValueMap = new Map();
const totalValueMap: ValueMap = new Map();
for (const key of totalIncome.valueMap!.keys()) {
const income = totalIncome.valueMap!.get(key) ?? 0;
const expense = totalExpense.valueMap!.get(key) ?? 0;
totalValueMap.set(key, income - expense);
const income = totalIncome.valueMap!.get(key)?.balance ?? 0;
const expense = totalExpense.valueMap!.get(key)?.balance ?? 0;
totalValueMap.set(key, { balance: income - expense });
}
const totalProfit = {
@ -117,7 +122,6 @@ export class ProfitAndLoss extends AccountReport {
expenseRows,
totalExpenseRow,
emptyRow,
emptyRow,
totalProfitRow,
].flat() as ReportData;
}

View File

@ -0,0 +1,291 @@
import { t } from 'fyo';
import { Action } from 'fyo/model/types';
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[];
}
getActions(): Action[] {
return [];
}
}

View File

@ -1,5 +1,11 @@
import { BalanceSheet } from './BalanceSheet/BalanceSheet';
import { GeneralLedger } from './GeneralLedger/GeneralLedger';
import { ProfitAndLoss } from './ProfitAndLoss/ProfitAndLoss';
import { TrialBalance } from './TrialBalance/TrialBalance';
export const reports = { GeneralLedger, ProfitAndLoss, BalanceSheet };
export const reports = {
GeneralLedger,
ProfitAndLoss,
BalanceSheet,
TrialBalance,
};

View File

@ -72,7 +72,7 @@ export interface LedgerEntry {
export type GroupedMap = Map<string, LedgerEntry[]>;
export type DateRange = { fromDate: DateTime; toDate: DateTime };
export type ValueMap = Map<DateRange, number>;
export type ValueMap = Map<DateRange, Record<string, number>>;
export interface Account {
name: string;
@ -97,7 +97,6 @@ export interface AccountListNode extends Account {
export type AccountNameValueMapMap = Map<string, ValueMap>;
export type BasedOn = 'Fiscal Year' | 'Until Date';
export interface TreeNode {
name: string;
children?: TreeNode[];
@ -105,4 +104,8 @@ export interface TreeNode {
export type Tree = Record<string, TreeNode>;
export type RootTypeRow = {
rootType: AccountRootType;
rootNode: AccountTreeNode;
rows: ReportData;
};

View File

@ -1,7 +1,6 @@
import { ipcRenderer } from 'electron';
import { ConfigKeys } from 'fyo/core/types';
import { DateTime } from 'luxon';
import { ProfitAndLoss } from 'reports/ProfitAndLoss/ProfitAndLoss';
import { IPC_ACTIONS } from 'utils/messages';
import { App as VueApp, createApp } from 'vue';
import App from './App.vue';
@ -107,7 +106,5 @@ function setOnWindow() {
window.fyo = fyo;
// @ts-ignore
window.DateTime = DateTime;
// @ts-ignore
window.pnl = new ProfitAndLoss(fyo);
}
}

View File

@ -153,12 +153,12 @@ function getCompleteSidebar(): SidebarConfig {
name: 'balance-sheet',
route: '/report/BalanceSheet',
},
/*
{
label: t`Trial Balance`,
name: 'trial-balance',
route: '/report/trial-balance',
route: '/report/TrialBalance',
},
/*
{
label: t`GSTR1`,
name: 'gstr1',