mirror of
https://github.com/frappe/books.git
synced 2024-11-09 23:30:56 +00:00
incr: add tree making functions to PNL
This commit is contained in:
parent
bf01fa0327
commit
6c350f0974
@ -1,7 +1,11 @@
|
|||||||
import { Fyo, t } from 'fyo';
|
import { t } from 'fyo';
|
||||||
import { Action } from 'fyo/model/types';
|
import { Action } from 'fyo/model/types';
|
||||||
|
import { cloneDeep } from 'lodash';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { AccountRootType } from 'models/baseModels/Account/types';
|
import {
|
||||||
|
AccountRootType,
|
||||||
|
AccountRootTypeEnum,
|
||||||
|
} from 'models/baseModels/Account/types';
|
||||||
import { isCredit } from 'models/helpers';
|
import { isCredit } from 'models/helpers';
|
||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
import { LedgerReport } from 'reports/LedgerReport';
|
import { LedgerReport } from 'reports/LedgerReport';
|
||||||
@ -20,7 +24,25 @@ type DateRange = { fromDate: DateTime; toDate: DateTime };
|
|||||||
type ValueMap = Map<DateRange, number>;
|
type ValueMap = Map<DateRange, number>;
|
||||||
type AccountNameValueMapMap = Map<string, ValueMap>;
|
type AccountNameValueMapMap = Map<string, ValueMap>;
|
||||||
type BasedOn = 'Fiscal Year' | 'Date Range';
|
type BasedOn = 'Fiscal Year' | 'Date Range';
|
||||||
type Account = { name: string; rootType: AccountRootType; isGroup: boolean };
|
|
||||||
|
interface Account {
|
||||||
|
name: string;
|
||||||
|
rootType: AccountRootType;
|
||||||
|
isGroup: boolean;
|
||||||
|
parentAccount: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AccountTreeNode extends Account {
|
||||||
|
children?: AccountTreeNode[];
|
||||||
|
valueMap?: ValueMap;
|
||||||
|
prune?: boolean;
|
||||||
|
}
|
||||||
|
type AccountTree = Record<string, AccountTreeNode>;
|
||||||
|
|
||||||
|
const PNL_ROOT_TYPES: AccountRootType[] = [
|
||||||
|
AccountRootTypeEnum.Income,
|
||||||
|
AccountRootTypeEnum.Expense,
|
||||||
|
];
|
||||||
|
|
||||||
export class ProfitAndLoss extends LedgerReport {
|
export class ProfitAndLoss extends LedgerReport {
|
||||||
static title = t`Profit And Loss`;
|
static title = t`Profit And Loss`;
|
||||||
@ -32,25 +54,21 @@ export class ProfitAndLoss extends LedgerReport {
|
|||||||
toYear?: number;
|
toYear?: number;
|
||||||
singleColumn: boolean = false;
|
singleColumn: boolean = false;
|
||||||
periodicity: Periodicity = 'Monthly';
|
periodicity: Periodicity = 'Monthly';
|
||||||
basedOn: BasedOn = 'Fiscal Year';
|
basedOn: BasedOn = 'Date Range';
|
||||||
|
|
||||||
_rawData: LedgerEntry[] = [];
|
_rawData: LedgerEntry[] = [];
|
||||||
|
|
||||||
accountMap?: Record<string, Account>;
|
accountMap?: Record<string, Account>;
|
||||||
|
|
||||||
constructor(fyo: Fyo) {
|
|
||||||
super(fyo);
|
|
||||||
}
|
|
||||||
|
|
||||||
async setDefaultFilters(): Promise<void> {
|
async setDefaultFilters(): Promise<void> {
|
||||||
if (this.basedOn === 'Date Range' && !this.toDate) {
|
if (this.basedOn === 'Date Range' && !this.toDate) {
|
||||||
this.toDate = DateTime.now().toISODate();
|
this.toDate = DateTime.now().toISODate();
|
||||||
this.count = 1;
|
this.count = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.basedOn === 'Fiscal Year' && !this.toYear) {
|
if (this.basedOn === 'Fiscal Year' && !this.toYear) {
|
||||||
this.toYear = DateTime.now().year;
|
this.fromYear = DateTime.now().year;
|
||||||
this.fromYear = this.toYear - 1;
|
this.toYear = this.fromYear + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,12 +84,30 @@ export class ProfitAndLoss extends LedgerReport {
|
|||||||
|
|
||||||
const map = this._getGroupedMap(sort, 'account');
|
const map = this._getGroupedMap(sort, 'account');
|
||||||
const rangeGroupedMap = await this._getGroupedByDateRanges(map);
|
const rangeGroupedMap = await this._getGroupedByDateRanges(map);
|
||||||
|
const accountTree = await this._getAccountTree(rangeGroupedMap);
|
||||||
|
|
||||||
|
for (const name of Object.keys(accountTree)) {
|
||||||
|
const { rootType } = accountTree[name];
|
||||||
|
if (PNL_ROOT_TYPES.includes(rootType)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete accountTree[name];
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* TODO: Get account tree from accountMap
|
|
||||||
* TODO: Create Grid from rangeGroupedMap and tree
|
* TODO: Create Grid from rangeGroupedMap and tree
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async temp() {
|
||||||
|
await this.setDefaultFilters();
|
||||||
|
await this._setRawData();
|
||||||
|
const map = this._getGroupedMap(false, 'account');
|
||||||
|
const rangeGroupedMap = await this._getGroupedByDateRanges(map);
|
||||||
|
const accountTree = await this._getAccountTree(rangeGroupedMap);
|
||||||
|
return accountTree;
|
||||||
|
}
|
||||||
|
|
||||||
async _getGroupedByDateRanges(
|
async _getGroupedByDateRanges(
|
||||||
map: GroupedMap
|
map: GroupedMap
|
||||||
): Promise<AccountNameValueMapMap> {
|
): Promise<AccountNameValueMapMap> {
|
||||||
@ -83,7 +119,7 @@ export class ProfitAndLoss extends LedgerReport {
|
|||||||
const valueMap: ValueMap = new Map();
|
const valueMap: ValueMap = new Map();
|
||||||
for (const entry of map.get(account)!) {
|
for (const entry of map.get(account)!) {
|
||||||
const key = this._getRangeMapKey(entry, dateRanges);
|
const key = this._getRangeMapKey(entry, dateRanges);
|
||||||
if (valueMap === null) {
|
if (key === null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,6 +139,18 @@ export class ProfitAndLoss extends LedgerReport {
|
|||||||
return accountValueMap;
|
return accountValueMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _getAccountTree(rangeGroupedMap: AccountNameValueMapMap) {
|
||||||
|
const accountTree = cloneDeep(await this._getAccountMap()) as AccountTree;
|
||||||
|
|
||||||
|
setPruneFlagOnAccountTreeNodes(accountTree);
|
||||||
|
setValueMapOnAccountTreeNodes(accountTree, rangeGroupedMap);
|
||||||
|
setChildrenOnAccountTreeNodes(accountTree);
|
||||||
|
deleteNonRootAccountTreeNodes(accountTree);
|
||||||
|
pruneAccountTree(accountTree);
|
||||||
|
|
||||||
|
return accountTree;
|
||||||
|
}
|
||||||
|
|
||||||
async _getAccountMap() {
|
async _getAccountMap() {
|
||||||
if (this.accountMap) {
|
if (this.accountMap) {
|
||||||
return this.accountMap;
|
return this.accountMap;
|
||||||
@ -110,12 +158,13 @@ export class ProfitAndLoss extends LedgerReport {
|
|||||||
|
|
||||||
const accountList: Account[] = (
|
const accountList: Account[] = (
|
||||||
await this.fyo.db.getAllRaw('Account', {
|
await this.fyo.db.getAllRaw('Account', {
|
||||||
fields: ['name', 'rootType', 'isGroup'],
|
fields: ['name', 'rootType', 'isGroup', 'parentAccount'],
|
||||||
})
|
})
|
||||||
).map((rv) => ({
|
).map((rv) => ({
|
||||||
name: rv.name as string,
|
name: rv.name as string,
|
||||||
rootType: rv.rootType as AccountRootType,
|
rootType: rv.rootType as AccountRootType,
|
||||||
isGroup: Boolean(rv.isGroup),
|
isGroup: Boolean(rv.isGroup),
|
||||||
|
parentAccount: rv.parentAccount as string | null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.accountMap = getMapFromList(accountList, 'name');
|
this.accountMap = getMapFromList(accountList, 'name');
|
||||||
@ -126,12 +175,15 @@ export class ProfitAndLoss extends LedgerReport {
|
|||||||
entry: LedgerEntry,
|
entry: LedgerEntry,
|
||||||
dateRanges: DateRange[]
|
dateRanges: DateRange[]
|
||||||
): DateRange | null {
|
): DateRange | null {
|
||||||
const entryDate = +DateTime.fromISO(
|
const entryDate = DateTime.fromISO(
|
||||||
entry.date!.toISOString().split('T')[0]
|
entry.date!.toISOString().split('T')[0]
|
||||||
);
|
).toMillis();
|
||||||
|
|
||||||
for (const dr of dateRanges) {
|
for (const dr of dateRanges) {
|
||||||
if (entryDate <= +dr.toDate && entryDate > +dr.fromDate) {
|
const toDate = dr.toDate.toMillis();
|
||||||
|
const fromDate = dr.fromDate.toMillis();
|
||||||
|
|
||||||
|
if (entryDate <= toDate && entryDate > fromDate) {
|
||||||
return dr;
|
return dr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,7 +283,7 @@ export class ProfitAndLoss extends LedgerReport {
|
|||||||
{ label: t`Fiscal Year`, value: 'Fiscal Year' },
|
{ label: t`Fiscal Year`, value: 'Fiscal Year' },
|
||||||
{ label: t`Date Range`, value: 'Date Range' },
|
{ label: t`Date Range`, value: 'Date Range' },
|
||||||
],
|
],
|
||||||
default: 'Fiscal Year',
|
default: 'Date Range',
|
||||||
label: t`Based On`,
|
label: t`Based On`,
|
||||||
fieldname: 'basedOn',
|
fieldname: 'basedOn',
|
||||||
},
|
},
|
||||||
@ -332,3 +384,112 @@ const monthsMap: Record<Periodicity, number> = {
|
|||||||
'Half Yearly': 6,
|
'Half Yearly': 6,
|
||||||
Yearly: 12,
|
Yearly: 12,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function setPruneFlagOnAccountTreeNodes(accountTree: AccountTree) {
|
||||||
|
for (const account of Object.values(accountTree)) {
|
||||||
|
account.prune = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setValueMapOnAccountTreeNodes(
|
||||||
|
accountTree: AccountTree,
|
||||||
|
rangeGroupedMap: AccountNameValueMapMap
|
||||||
|
) {
|
||||||
|
for (const name of rangeGroupedMap.keys()) {
|
||||||
|
const valueMap = rangeGroupedMap.get(name)!;
|
||||||
|
accountTree[name].valueMap = valueMap;
|
||||||
|
accountTree[name].prune = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the update the parent account values recursively
|
||||||
|
* also prevent pruning of the parent accounts.
|
||||||
|
*/
|
||||||
|
let parentAccountName: string | null = accountTree[name].parentAccount;
|
||||||
|
let parentValueMap = valueMap;
|
||||||
|
|
||||||
|
while (parentAccountName !== null) {
|
||||||
|
const update = updateParentAccountWithChildValues(
|
||||||
|
accountTree,
|
||||||
|
parentAccountName,
|
||||||
|
parentValueMap
|
||||||
|
);
|
||||||
|
|
||||||
|
parentAccountName = update.parentAccountName;
|
||||||
|
parentValueMap = update.parentValueMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateParentAccountWithChildValues(
|
||||||
|
accountTree: AccountTree,
|
||||||
|
parentAccountName: string,
|
||||||
|
parentValueMap: ValueMap
|
||||||
|
): {
|
||||||
|
parentAccountName: string | null;
|
||||||
|
parentValueMap: ValueMap;
|
||||||
|
} {
|
||||||
|
const parentAccount = accountTree[parentAccountName];
|
||||||
|
parentAccount.prune = false;
|
||||||
|
parentAccount.valueMap ??= new Map();
|
||||||
|
|
||||||
|
for (const key of parentValueMap.keys()) {
|
||||||
|
const value = parentAccount.valueMap!.get(key) ?? 0;
|
||||||
|
parentAccount.valueMap!.set(key, value + parentValueMap.get(key)!);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
parentAccountName: parentAccount.parentAccount,
|
||||||
|
parentValueMap: parentAccount.valueMap!,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function setChildrenOnAccountTreeNodes(accountTree: AccountTree) {
|
||||||
|
const parentNodes: Set<string> = new Set();
|
||||||
|
|
||||||
|
for (const name of Object.keys(accountTree)) {
|
||||||
|
const ac = accountTree[name];
|
||||||
|
if (!ac.parentAccount) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
accountTree[ac.parentAccount].children ??= [];
|
||||||
|
accountTree[ac.parentAccount].children!.push(ac!);
|
||||||
|
|
||||||
|
parentNodes.add(ac.parentAccount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteNonRootAccountTreeNodes(accountTree: AccountTree) {
|
||||||
|
for (const name of Object.keys(accountTree)) {
|
||||||
|
const ac = accountTree[name];
|
||||||
|
if (!ac.parentAccount) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete accountTree[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pruneAccountTree(accountTree: AccountTree) {
|
||||||
|
for (const root of Object.keys(accountTree)) {
|
||||||
|
if (accountTree[root].prune) {
|
||||||
|
delete accountTree[root];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const root of Object.keys(accountTree)) {
|
||||||
|
accountTree[root].children = getPrunedChildren(accountTree[root].children!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPrunedChildren(children: AccountTreeNode[]): AccountTreeNode[] {
|
||||||
|
return children.filter((child) => {
|
||||||
|
if (child.children) {
|
||||||
|
child.children = getPrunedChildren(child.children);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !child.prune;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertAccountTreeToAccountList() {}
|
||||||
|
Loading…
Reference in New Issue
Block a user