From d65b04de4c099c0ece5bccee343dcef30cc2a34b Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Mon, 16 May 2022 13:19:01 +0530 Subject: [PATCH] incr: complete PNL report --- reports/ProfitAndLoss/ProfitAndLoss.ts | 162 +++++++++++-------------- reports/Report.ts | 2 +- schemas/core/SystemSettings.json | 6 +- src/components/Controls/Base.vue | 4 +- 4 files changed, 78 insertions(+), 96 deletions(-) diff --git a/reports/ProfitAndLoss/ProfitAndLoss.ts b/reports/ProfitAndLoss/ProfitAndLoss.ts index 19a95d73..5a3bf614 100644 --- a/reports/ProfitAndLoss/ProfitAndLoss.ts +++ b/reports/ProfitAndLoss/ProfitAndLoss.ts @@ -26,7 +26,7 @@ import { QueryFilter } from 'utils/db/types'; type DateRange = { fromDate: DateTime; toDate: DateTime }; type ValueMap = Map; type AccountNameValueMapMap = Map; -type BasedOn = 'Fiscal Year' | 'Date Range'; +type BasedOn = 'Fiscal Year' | 'Until Date'; interface Account { name: string; @@ -74,14 +74,20 @@ export class ProfitAndLoss extends LedgerReport { singleColumn: boolean = false; hideGroupBalance: boolean = false; periodicity: Periodicity = 'Monthly'; - basedOn: BasedOn = 'Date Range'; + basedOn: BasedOn = 'Until Date'; _rawData: LedgerEntry[] = []; + _dateRanges?: DateRange[]; accountMap?: Record; + async initialize(): Promise { + await super.initialize(); + await this._setDateRanges(); + } + async setDefaultFilters(): Promise { - if (this.basedOn === 'Date Range' && !this.toDate) { + if (this.basedOn === 'Until Date' && !this.toDate) { this.toDate = DateTime.now().toISODate(); } @@ -89,6 +95,12 @@ export class ProfitAndLoss extends LedgerReport { this.fromYear = DateTime.now().year; this.toYear = this.fromYear + 1; } + + await this._setDateRanges(); + } + + async _setDateRanges() { + this._dateRanges = await this._getDateRanges(); } async setReportData(filter?: string) { @@ -147,8 +159,8 @@ export class ProfitAndLoss extends LedgerReport { const totalValueMap = new Map(); for (const key of totalIncome.valueMap!.keys()) { - const income = totalIncome.valueMap!.get(key)!; - const expense = totalExpense.valueMap!.get(key)!; + const income = totalIncome.valueMap!.get(key) ?? 0; + const expense = totalExpense.valueMap!.get(key) ?? 0; totalValueMap.set(key, income - expense); } @@ -158,20 +170,10 @@ export class ProfitAndLoss extends LedgerReport { level: 0, } as AccountListNode; - const dateKeys = this.getSortedDateKeys(totalValueMap); - const totalIncomeRow = this.getRowFromAccountListNode( - totalIncome, - dateKeys - ); - const totalExpenseRow = this.getRowFromAccountListNode( - totalExpense, - dateKeys - ); + const totalIncomeRow = this.getRowFromAccountListNode(totalIncome); + const totalExpenseRow = this.getRowFromAccountListNode(totalExpense); - const totalProfitRow = this.getRowFromAccountListNode( - totalProfit, - dateKeys - ); + const totalProfitRow = this.getRowFromAccountListNode(totalProfit); totalProfitRow.cells.forEach((c) => { c.bold = true; if (typeof c.rawValue !== 'number') { @@ -219,29 +221,9 @@ export class ProfitAndLoss extends LedgerReport { name: string ): Promise { const leafNodes = getListOfLeafNodes(accountTree) as AccountTreeNode[]; - let keys: DateRange[] | undefined = undefined; - - /** - * Keys need to be from the nodes cause they are ref keys. - */ - for (const node of leafNodes) { - const drs = [...(node?.valueMap?.keys() ?? [])]; - if (!drs || !drs.length) { - continue; - } - - keys = drs; - if (keys && keys.length) { - break; - } - } - - if (!keys || !keys.length) { - keys = await this._getDateRanges(); - } const totalMap = leafNodes.reduce((acc, node) => { - for (const key of keys!) { + for (const key of this._dateRanges!) { const bal = acc.get(key) ?? 0; const val = node.valueMap?.get(key) ?? 0; acc.set(key, bal + val); @@ -254,20 +236,12 @@ export class ProfitAndLoss extends LedgerReport { } getPnlRowsFromAccountList(accountList: AccountList): ReportData { - const dateKeys = this.getSortedDateKeys(accountList[0].valueMap!); - return accountList.map((al) => { - return this.getRowFromAccountListNode(al, dateKeys); + return this.getRowFromAccountListNode(al); }); } - getSortedDateKeys(valueMap: ValueMap) { - return [...valueMap.keys()].sort( - (a, b) => b.toDate.toMillis() - a.toDate.toMillis() - ); - } - - getRowFromAccountListNode(al: AccountListNode, dateKeys: DateRange[]) { + getRowFromAccountListNode(al: AccountListNode) { const nameCell = { value: al.name, rawValue: al.name, @@ -278,7 +252,7 @@ export class ProfitAndLoss extends LedgerReport { indent: al.level ?? 0, } as ReportCell; - const balanceCells = dateKeys.map((k) => { + const balanceCells = this._dateRanges!.map((k) => { const rawValue = al.valueMap?.get(k) ?? 0; let value = this.fyo.format(rawValue, 'Currency'); if (this.hideGroupBalance && al.isGroup) { @@ -302,19 +276,20 @@ export class ProfitAndLoss extends LedgerReport { } as ReportRow; } - // async getTotalProfitNode() - async _getGroupedByDateRanges( map: GroupedMap ): Promise { - const dateRanges = await this._getDateRanges(); const accountValueMap: AccountNameValueMapMap = new Map(); const accountMap = await this._getAccountMap(); 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, dateRanges); + const key = this._getRangeMapKey(entry); if (key === null) { continue; } @@ -367,15 +342,12 @@ export class ProfitAndLoss extends LedgerReport { return this.accountMap; } - _getRangeMapKey( - entry: LedgerEntry, - dateRanges: DateRange[] - ): DateRange | null { + _getRangeMapKey(entry: LedgerEntry): DateRange | null { const entryDate = DateTime.fromISO( entry.date!.toISOString().split('T')[0] ).toMillis(); - for (const dr of dateRanges) { + for (const dr of this._dateRanges!) { const toDate = dr.toDate.toMillis(); const fromDate = dr.fromDate.toMillis(); @@ -419,14 +391,14 @@ export class ProfitAndLoss extends LedgerReport { }); } - return dateRanges; + return dateRanges.sort((b, a) => b.toDate.toMillis() - a.toDate.toMillis()); } async _getFromAndToDates() { let toDate: string; let fromDate: string; - if (this.basedOn === 'Date Range') { + if (this.basedOn === 'Until Date') { toDate = this.toDate!; const months = monthsMap[this.periodicity] * Math.max(this.count ?? 1, 1); fromDate = DateTime.fromISO(toDate).minus({ months }).toISODate(); @@ -465,7 +437,7 @@ export class ProfitAndLoss extends LedgerReport { fieldtype: 'Select', options: [ { label: t`Fiscal Year`, value: 'Fiscal Year' }, - { label: t`Date Range`, value: 'Date Range' }, + { label: t`Until Date`, value: 'Until Date' }, ], label: t`Based On`, fieldname: 'basedOn', @@ -486,22 +458,24 @@ export class ProfitAndLoss extends LedgerReport { let dateFilters = [ { - fieldtype: 'Date', - fieldname: 'fromYear', - placeholder: t`From Date`, - label: t`From Date`, - required: true, - }, - { - fieldtype: 'Date', + fieldtype: 'Int', fieldname: 'toYear', placeholder: t`To Year`, label: t`To Year`, + minvalue: 2000, + required: true, + }, + { + fieldtype: 'Int', + fieldname: 'fromYear', + placeholder: t`From Year`, + label: t`From Year`, + minvalue: 2000, required: true, }, ] as Field[]; - if (this.basedOn === 'Date Range') { + if (this.basedOn === 'Until Date') { dateFilters = [ { fieldtype: 'Date', @@ -537,7 +511,7 @@ export class ProfitAndLoss extends LedgerReport { ].flat(); } - async getColumns(): Promise { + getColumns(): ColumnField[] { const columns = [ { label: t`Account`, @@ -548,19 +522,18 @@ export class ProfitAndLoss extends LedgerReport { }, ] as ColumnField[]; - const dateRanges = await this._getDateRanges(); - const dateColumns = dateRanges - .sort((a, b) => b.toDate.toMillis() - a.toDate.toMillis()) - .map( - (d) => - ({ - label: this.fyo.format(d.toDate.toJSDate(), 'Date'), - fieldtype: 'Data', - fieldname: 'toDate', - align: 'right', - width: ACC_BAL_WIDTH, - } as ColumnField) - ); + const dateColumns = this._dateRanges!.sort( + (a, b) => b.toDate.toMillis() - a.toDate.toMillis() + ).map( + (d) => + ({ + label: this.fyo.format(d.toDate.toJSDate(), 'Date'), + fieldtype: 'Data', + fieldname: 'toDate', + align: 'right', + width: ACC_BAL_WIDTH, + } as ColumnField) + ); return [columns, dateColumns].flat(); } @@ -590,12 +563,13 @@ async function getFiscalEndpoints(toYear: number, fromYear: number) { const fromDate = [ fromYear, fys.toISOString().split('T')[0].split('-').slice(1), - ].join('-'); + ] + .flat() + .join('-'); - const toDate = [ - toYear, - fye.toISOString().split('T')[0].split('-').slice(1), - ].join('-'); + const toDate = [toYear, fye.toISOString().split('T')[0].split('-').slice(1)] + .flat() + .join('-'); return { fromDate, toDate }; } @@ -710,6 +684,10 @@ function convertAccountTreeToAccountList( const accountList: AccountList = []; for (const rootNode of Object.values(accountTree)) { + if (!rootNode) { + continue; + } + pushToAccountList(rootNode, accountList, 0); } @@ -741,6 +719,10 @@ function pushToAccountList( function getListOfLeafNodes(tree: Tree): TreeNode[] { const nonGroupChildren: TreeNode[] = []; for (const node of Object.values(tree)) { + if (!node) { + continue; + } + const groupChildren = node.children ?? []; while (groupChildren.length) { diff --git a/reports/Report.ts b/reports/Report.ts index 05085b87..60a3de6d 100644 --- a/reports/Report.ts +++ b/reports/Report.ts @@ -63,9 +63,9 @@ export abstract class Report extends Observable { this[key] = value; } + await this.setDefaultFilters(); this.filters = await this.getFilters(); this.columns = await this.getColumns(); - await this.setDefaultFilters(); await this.setReportData(key); } diff --git a/schemas/core/SystemSettings.json b/schemas/core/SystemSettings.json index 8a111be4..9931c053 100644 --- a/schemas/core/SystemSettings.json +++ b/schemas/core/SystemSettings.json @@ -57,15 +57,15 @@ "fieldtype": "Int", "default": 2, "required": true, - "minValue": 0, - "maxValue": 9, + "minvalue": 0, + "maxvalue": 9, "description": "Sets how many digits are shown after the decimal point." }, { "fieldname": "internalPrecision", "label": "Internal Precision", "fieldtype": "Int", - "minValue": 0, + "minvalue": 0, "default": 11, "description": "Sets the internal precision used for monetary calculations. Above 6 should be sufficient for most currencies." }, diff --git a/src/components/Controls/Base.vue b/src/components/Controls/Base.vue index ee5300cf..1c95b03c 100644 --- a/src/components/Controls/Base.vue +++ b/src/components/Controls/Base.vue @@ -11,8 +11,8 @@ :value="value" :placeholder="inputPlaceholder" :readonly="isReadOnly" - :max="df.maxValue" - :min="df.minValue" + :max="df.maxvalue" + :min="df.minvalue" @blur="(e) => triggerChange(e.target.value)" @focus="(e) => $emit('focus', e)" @input="(e) => $emit('input', e)"