2
0
mirror of https://github.com/frappe/books.git synced 2024-11-14 17:34:04 +00:00
books/reports/ProfitAndLoss/ProfitAndLoss.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

191 lines
4.5 KiB
TypeScript

import { t } from 'fyo';
import {
AccountRootType,
AccountRootTypeEnum,
} from 'models/baseModels/Account/types';
import {
AccountReport,
convertAccountRootNodesToAccountList,
} from 'reports/AccountReport';
import {
AccountListNode,
AccountTreeNode,
ReportData,
ValueMap,
} from 'reports/types';
export class ProfitAndLoss extends AccountReport {
static title = t`Profit And Loss`;
static reportName = 'profit-and-loss';
loading = false;
get rootTypes(): AccountRootType[] {
return [AccountRootTypeEnum.Income, AccountRootTypeEnum.Expense];
}
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);
for (const name of Object.keys(accountTree)) {
const { rootType } = accountTree[name];
if (this.rootTypes.includes(rootType)) {
continue;
}
delete accountTree[name];
}
/**
* Income Rows
*/
const incomeRoots = this.getRootNodes(
AccountRootTypeEnum.Income,
accountTree
)!;
const incomeList = convertAccountRootNodesToAccountList(incomeRoots);
const incomeRows = this.getReportRowsFromAccountList(incomeList);
/**
* Expense Rows
*/
const expenseRoots = this.getRootNodes(
AccountRootTypeEnum.Expense,
accountTree
)!;
const expenseList = convertAccountRootNodesToAccountList(expenseRoots);
const expenseRows = this.getReportRowsFromAccountList(expenseList);
this.reportData = this.getReportDataFromRows(
incomeRows,
expenseRows,
incomeRoots,
expenseRoots
);
this.loading = false;
}
getReportDataFromRows(
incomeRows: ReportData,
expenseRows: ReportData,
incomeRoots: AccountTreeNode[] | undefined,
expenseRoots: AccountTreeNode[] | undefined
): ReportData {
if (
incomeRoots &&
incomeRoots.length &&
!expenseRoots &&
!expenseRoots.length
) {
return this.getIncomeOrExpenseRows(
incomeRoots,
incomeRows,
t`Total Income (Credit)`
);
}
if (
expenseRoots &&
expenseRoots.length &&
(!incomeRoots || !incomeRoots.length)
) {
return this.getIncomeOrExpenseRows(
expenseRoots,
expenseRows,
t`Total Income (Credit)`
);
}
if (
!incomeRoots ||
!incomeRoots.length ||
!expenseRoots ||
!expenseRoots.length
) {
return [];
}
return this.getIncomeAndExpenseRows(
incomeRows,
expenseRows,
incomeRoots,
expenseRoots
);
}
getIncomeOrExpenseRows(
roots: AccountTreeNode[],
rows: ReportData,
totalRowName: string
): ReportData {
const total = this.getTotalNode(roots, totalRowName);
const totalRow = this.getRowFromAccountListNode(total);
return [rows, totalRow].flat();
}
getIncomeAndExpenseRows(
incomeRows: ReportData,
expenseRows: ReportData,
incomeRoots: AccountTreeNode[],
expenseRoots: AccountTreeNode[]
) {
const totalIncome = this.getTotalNode(
incomeRoots,
t`Total Income (Credit)`
);
const totalIncomeRow = this.getRowFromAccountListNode(totalIncome);
const totalExpense = this.getTotalNode(
expenseRoots,
t`Total Expense (Debit)`
);
const totalExpenseRow = this.getRowFromAccountListNode(totalExpense);
const totalValueMap: ValueMap = new Map();
for (const key of totalIncome.valueMap!.keys()) {
const income = totalIncome.valueMap!.get(key)?.balance ?? 0;
const expense = totalExpense.valueMap!.get(key)?.balance ?? 0;
totalValueMap.set(key, { balance: income - expense });
}
const totalProfit = {
name: t`Total Profit`,
valueMap: totalValueMap,
level: 0,
} as AccountListNode;
const totalProfitRow = this.getRowFromAccountListNode(totalProfit);
totalProfitRow.cells.forEach((c) => {
c.bold = true;
if (typeof c.rawValue !== 'number') {
return;
}
if (c.rawValue > 0) {
c.color = 'green';
} else if (c.rawValue < 0) {
c.color = 'red';
}
});
const emptyRow = this.getEmptyRow();
return [
incomeRows,
totalIncomeRow,
emptyRow,
expenseRows,
totalExpenseRow,
emptyRow,
totalProfitRow,
].flat() as ReportData;
}
}