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:
parent
06b5ac4f90
commit
63c2eace33
@ -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!;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
291
reports/TrialBalance/TrialBalance.ts
Normal file
291
reports/TrialBalance/TrialBalance.ts
Normal 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 [];
|
||||
}
|
||||
}
|
@ -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,
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user