2
0
mirror of https://github.com/frappe/books.git synced 2025-02-02 12:08:27 +00:00

incr: get PNL to display

- needs a few more fixes
This commit is contained in:
18alantom 2022-05-16 00:18:57 +05:30
parent 6c350f0974
commit ad22005cc2
7 changed files with 168 additions and 96 deletions

View File

@ -1,6 +1,7 @@
import { Fyo, t } from 'fyo'; import { Fyo, t } from 'fyo';
import { Action } from 'fyo/model/types'; import { Action } from 'fyo/model/types';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { ModelNameEnum } from 'models/types';
import { LedgerReport } from 'reports/LedgerReport'; import { LedgerReport } from 'reports/LedgerReport';
import { import {
ColumnField, ColumnField,
@ -12,12 +13,21 @@ import {
import { Field, FieldTypeEnum } from 'schemas/types'; import { Field, FieldTypeEnum } from 'schemas/types';
import { QueryFilter } from 'utils/db/types'; import { QueryFilter } from 'utils/db/types';
type ReferenceType =
| ModelNameEnum.SalesInvoice
| ModelNameEnum.PurchaseInvoice
| ModelNameEnum.Payment
| ModelNameEnum.JournalEntry
| 'All';
export class GeneralLedger extends LedgerReport { export class GeneralLedger extends LedgerReport {
static title = t`General Ledger`; static title = t`General Ledger`;
static reportName = 'general-ledger'; static reportName = 'general-ledger';
ascending!: boolean; ascending: boolean = false;
groupBy!: 'none' | 'party' | 'account' | 'referenceName'; reverted: boolean = false;
referenceType: ReferenceType = 'All';
groupBy: 'none' | 'party' | 'account' | 'referenceName' = 'none';
_rawData: LedgerEntry[] = []; _rawData: LedgerEntry[] = [];
constructor(fyo: Fyo) { constructor(fyo: Fyo) {
@ -263,7 +273,6 @@ export class GeneralLedger extends LedgerReport {
label: t`Ref Type`, label: t`Ref Type`,
fieldname: 'referenceType', fieldname: 'referenceType',
placeholder: t`Ref Type`, placeholder: t`Ref Type`,
default: 'All',
}, },
{ {
fieldtype: 'DynamicLink', fieldtype: 'DynamicLink',
@ -301,7 +310,6 @@ export class GeneralLedger extends LedgerReport {
}, },
{ {
fieldtype: 'Select', fieldtype: 'Select',
default: 'none',
label: t`Group By`, label: t`Group By`,
fieldname: 'groupBy', fieldname: 'groupBy',
options: [ options: [
@ -313,13 +321,11 @@ export class GeneralLedger extends LedgerReport {
}, },
{ {
fieldtype: 'Check', fieldtype: 'Check',
default: false,
label: t`Include Cancelled`, label: t`Include Cancelled`,
fieldname: 'reverted', fieldname: 'reverted',
}, },
{ {
fieldtype: 'Check', fieldtype: 'Check',
default: false,
label: t`Ascending Order`, label: t`Ascending Order`,
fieldname: 'ascending', fieldname: 'ascending',
}, },

View File

@ -4,7 +4,7 @@ import { cloneDeep } from 'lodash';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { import {
AccountRootType, AccountRootType,
AccountRootTypeEnum, AccountRootTypeEnum
} from 'models/baseModels/Account/types'; } 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';
@ -14,6 +14,9 @@ import {
GroupedMap, GroupedMap,
LedgerEntry, LedgerEntry,
Periodicity, Periodicity,
ReportCell,
ReportData,
ReportRow
} from 'reports/types'; } from 'reports/types';
import { Field } from 'schemas/types'; import { Field } from 'schemas/types';
import { fyo } from 'src/initFyo'; import { fyo } from 'src/initFyo';
@ -32,24 +35,33 @@ interface Account {
parentAccount: string | null; parentAccount: string | null;
} }
type AccountTree = Record<string, AccountTreeNode>;
interface AccountTreeNode extends Account { interface AccountTreeNode extends Account {
children?: AccountTreeNode[]; children?: AccountTreeNode[];
valueMap?: ValueMap; valueMap?: ValueMap;
prune?: boolean; prune?: boolean;
} }
type AccountTree = Record<string, AccountTreeNode>;
type AccountList = AccountListNode[];
interface AccountListNode extends Account {
valueMap?: ValueMap;
level?: number;
}
const PNL_ROOT_TYPES: AccountRootType[] = [ const PNL_ROOT_TYPES: AccountRootType[] = [
AccountRootTypeEnum.Income, AccountRootTypeEnum.Income,
AccountRootTypeEnum.Expense, AccountRootTypeEnum.Expense,
]; ];
const ACC_NAME_WIDTH = 1.5;
const ACC_BAL_WIDTH = 1;
export class ProfitAndLoss extends LedgerReport { export class ProfitAndLoss extends LedgerReport {
static title = t`Profit And Loss`; static title = t`Profit And Loss`;
static reportName = 'profit-and-loss'; static reportName = 'profit-and-loss';
toDate?: string; toDate?: string;
count?: number; count: number = 3;
fromYear?: number; fromYear?: number;
toYear?: number; toYear?: number;
singleColumn: boolean = false; singleColumn: boolean = false;
@ -63,7 +75,6 @@ export class ProfitAndLoss extends LedgerReport {
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 = 3;
} }
if (this.basedOn === 'Fiscal Year' && !this.toYear) { if (this.basedOn === 'Fiscal Year' && !this.toYear) {
@ -73,16 +84,8 @@ export class ProfitAndLoss extends LedgerReport {
} }
async setReportData(filter?: string) { async setReportData(filter?: string) {
let sort = true; await this._setRawData();
if ( const map = this._getGroupedMap(true, 'account');
this._rawData.length === 0 &&
!['periodicity', 'singleColumn'].includes(filter!)
) {
await this._setRawData();
sort = false;
}
const map = this._getGroupedMap(sort, 'account');
const rangeGroupedMap = await this._getGroupedByDateRanges(map); const rangeGroupedMap = await this._getGroupedByDateRanges(map);
const accountTree = await this._getAccountTree(rangeGroupedMap); const accountTree = await this._getAccountTree(rangeGroupedMap);
@ -94,18 +97,28 @@ export class ProfitAndLoss extends LedgerReport {
delete accountTree[name]; delete accountTree[name];
} }
/**
* TODO: Create Grid from rangeGroupedMap and tree const accountList = convertAccountTreeToAccountList(accountTree);
*/ this.reportData = this.getReportDataFromAccountList(accountList);
} }
async temp() { getReportDataFromAccountList(accountList: AccountList): ReportData {
await this.setDefaultFilters(); const dateKeys = [...accountList[0].valueMap!.keys()].sort(
await this._setRawData(); (a, b) => b.toDate.toMillis() - a.toDate.toMillis()
const map = this._getGroupedMap(false, 'account'); );
const rangeGroupedMap = await this._getGroupedByDateRanges(map);
const accountTree = await this._getAccountTree(rangeGroupedMap); return accountList.map((al) => {
return accountTree; const nameCell = { value: al.name, align: 'left', width: ACC_NAME_WIDTH };
const balanceCells = dateKeys.map(
(k) =>
({
value: this.fyo.format(al.valueMap?.get(k)!, 'Currency'),
align: 'right',
width: ACC_BAL_WIDTH,
} as ReportCell)
);
return [nameCell, balanceCells].flat() as ReportRow;
});
} }
async _getGroupedByDateRanges( async _getGroupedByDateRanges(
@ -256,7 +269,7 @@ export class ProfitAndLoss extends LedgerReport {
return filters; return filters;
} }
getFilters() { getFilters(): Field[] {
const periodNameMap: Record<Periodicity, string> = { const periodNameMap: Record<Periodicity, string> = {
Monthly: t`Months`, Monthly: t`Months`,
Quarterly: t`Quarters`, Quarterly: t`Quarters`,
@ -265,6 +278,15 @@ export class ProfitAndLoss extends LedgerReport {
}; };
const filters = [ const filters = [
{
fieldtype: 'Select',
options: [
{ label: t`Fiscal Year`, value: 'Fiscal Year' },
{ label: t`Date Range`, value: 'Date Range' },
],
label: t`Based On`,
fieldname: 'basedOn',
},
{ {
fieldtype: 'Select', fieldtype: 'Select',
options: [ options: [
@ -273,31 +295,31 @@ export class ProfitAndLoss extends LedgerReport {
{ label: t`Half Yearly`, value: 'Half Yearly' }, { label: t`Half Yearly`, value: 'Half Yearly' },
{ label: t`Yearly`, value: 'Yearly' }, { label: t`Yearly`, value: 'Yearly' },
], ],
default: 'Monthly',
label: t`Periodicity`, label: t`Periodicity`,
fieldname: 'periodicity', fieldname: 'periodicity',
}, },
,
] as Field[];
let dateFilters = [
{ {
fieldtype: 'Select', fieldtype: 'Date',
options: [ fieldname: 'fromYear',
{ label: t`Fiscal Year`, value: 'Fiscal Year' }, placeholder: t`From Date`,
{ label: t`Date Range`, value: 'Date Range' }, label: t`From Date`,
], required: true,
default: 'Date Range',
label: t`Based On`,
fieldname: 'basedOn',
}, },
{ {
fieldtype: 'Check', fieldtype: 'Date',
default: false, fieldname: 'toYear',
label: t`Single Column`, placeholder: t`To Year`,
fieldname: 'singleColumn', label: t`To Year`,
required: true,
}, },
] as Field[]; ] as Field[];
if (this.basedOn === 'Date Range') { if (this.basedOn === 'Date Range') {
return [ dateFilters = [
...filters,
{ {
fieldtype: 'Date', fieldtype: 'Date',
fieldname: 'toDate', fieldname: 'toDate',
@ -308,6 +330,7 @@ export class ProfitAndLoss extends LedgerReport {
{ {
fieldtype: 'Int', fieldtype: 'Int',
fieldname: 'count', fieldname: 'count',
minvalue: 1,
placeholder: t`Number of ${periodNameMap[this.periodicity]}`, placeholder: t`Number of ${periodNameMap[this.periodicity]}`,
label: t`Number of ${periodNameMap[this.periodicity]}`, label: t`Number of ${periodNameMap[this.periodicity]}`,
required: true, required: true,
@ -315,32 +338,43 @@ export class ProfitAndLoss extends LedgerReport {
] as Field[]; ] as Field[];
} }
const thisYear = DateTime.local().year;
return [ return [
...filters, filters,
dateFilters,
{ {
fieldtype: 'Date', fieldtype: 'Check',
fieldname: 'fromYear', label: t`Single Column`,
placeholder: t`From Date`, fieldname: 'singleColumn',
label: t`From Date`, } as Field,
default: thisYear - 1, ].flat();
required: true,
},
{
fieldtype: 'Date',
fieldname: 'toYear',
placeholder: t`To Year`,
label: t`To Year`,
default: thisYear,
required: true,
},
] as Field[];
} }
getColumns(): ColumnField[] { async getColumns(): Promise<ColumnField[]> {
const columns = [] as ColumnField[]; const columns = [
{
label: t`Account`,
fieldtype: 'Link',
fieldname: 'account',
align: 'left',
width: ACC_NAME_WIDTH,
},
] as ColumnField[];
return columns; 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)
);
return [columns, dateColumns].flat();
} }
getActions(): Action[] { getActions(): Action[] {
@ -492,4 +526,36 @@ function getPrunedChildren(children: AccountTreeNode[]): AccountTreeNode[] {
}); });
} }
function convertAccountTreeToAccountList() {} function convertAccountTreeToAccountList(
accountTree: AccountTree
): AccountList {
const accountList: AccountList = [];
for (const rootNode of Object.values(accountTree)) {
pushToAccountList(rootNode, accountList, 0);
}
return accountList;
}
function pushToAccountList(
accountTreeNode: AccountTreeNode,
accountList: AccountList,
level: number
) {
accountList.push({
name: accountTreeNode.name,
rootType: accountTreeNode.rootType,
isGroup: accountTreeNode.isGroup,
parentAccount: accountTreeNode.parentAccount,
valueMap: accountTreeNode.valueMap,
level,
});
const children = accountTreeNode.children ?? [];
const childLevel = level + 1;
for (const childNode of children) {
pushToAccountList(childNode, accountList, childLevel);
}
}

View File

@ -10,17 +10,25 @@ export abstract class Report extends Observable<RawValue> {
static reportName: string; static reportName: string;
fyo: Fyo; fyo: Fyo;
columns: ColumnField[]; columns: ColumnField[] = [];
filters: Field[]; filters: Field[] = [];
reportData: ReportData; reportData: ReportData;
constructor(fyo: Fyo) { constructor(fyo: Fyo) {
super(); super();
this.fyo = fyo; this.fyo = fyo;
this.reportData = []; this.reportData = [];
this.filters = this.getFilters(); }
this.columns = this.getColumns();
this.initializeFilters(); async initialize() {
/**
* Not in constructor cause possibly async.
*/
await this.setDefaultFilters();
this.filters = await this.getFilters();
this.columns = await this.getColumns();
await this.setReportData();
} }
get filterMap() { get filterMap() {
@ -54,30 +62,19 @@ export abstract class Report extends Observable<RawValue> {
this[key] = value; this[key] = value;
} }
this.filters = this.getFilters(); this.filters = await this.getFilters();
this.columns = this.getColumns(); this.columns = await this.getColumns();
await this.setDefaultFilters(); await this.setDefaultFilters();
await this.setReportData(key); await this.setReportData(key);
} }
initializeFilters() {
for (const field of this.filters) {
if (!field.default) {
this[field.fieldname] = undefined;
continue;
}
this[field.fieldname] = field.default;
}
}
/** /**
* Should first check if filter value is set * Should first check if filter value is set
* and update only if it is not set. * and update only if it is not set.
*/ */
async setDefaultFilters() {} async setDefaultFilters() {}
abstract getActions(): Action[]; abstract getActions(): Action[];
abstract getFilters(): Field[]; abstract getFilters(): Field[] | Promise<Field[]>;
abstract getColumns(): ColumnField[]; abstract getColumns(): ColumnField[] | Promise<ColumnField[]>;
abstract setReportData(filter?: string): Promise<void>; abstract setReportData(filter?: string): Promise<void>;
} }

View File

@ -1,2 +1,4 @@
import { GeneralLedger } from './GeneralLedger/GeneralLedger'; import { GeneralLedger } from './GeneralLedger/GeneralLedger';
export const reports = { GeneralLedger }; import { ProfitAndLoss } from './ProfitAndLoss/ProfitAndLoss';
export const reports = { GeneralLedger, ProfitAndLoss };

View File

@ -39,6 +39,7 @@
</template> </template>
<script> <script>
import { computed } from '@vue/reactivity'; import { computed } from '@vue/reactivity';
import { t } from 'fyo';
import { reports } from 'reports'; import { reports } from 'reports';
import FormControl from 'src/components/Controls/FormControl.vue'; import FormControl from 'src/components/Controls/FormControl.vue';
import PageHeader from 'src/components/PageHeader.vue'; import PageHeader from 'src/components/PageHeader.vue';
@ -62,9 +63,6 @@ export default defineComponent({
}; };
}, },
components: { PageHeader, FormControl, ListReport }, components: { PageHeader, FormControl, ListReport },
async mounted() {
await this.setReportData();
},
async activated() { async activated() {
await this.setReportData(); await this.setReportData();
if (fyo.store.isDevelopment) { if (fyo.store.isDevelopment) {
@ -79,15 +77,15 @@ export default defineComponent({
methods: { methods: {
async setReportData() { async setReportData() {
const Report = reports[this.reportName]; const Report = reports[this.reportName];
if (this.report === null) { if (this.report === null) {
this.report = new Report(fyo); this.report = new Report(fyo);
await this.report.initialize();
} }
if (!this.report.reportData.length) { if (!this.report.reportData.length) {
await this.report.setReportData(); await this.report.setReportData();
} }
await this.report.setDefaultFilters();
}, },
}, },
}); });

View File

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

View File

@ -143,12 +143,12 @@ function getCompleteSidebar(): SidebarConfig {
name: 'general-ledger', name: 'general-ledger',
route: '/report/GeneralLedger', route: '/report/GeneralLedger',
}, },
/*
{ {
label: t`Profit And Loss`, label: t`Profit And Loss`,
name: 'profit-and-loss', name: 'profit-and-loss',
route: '/report/profit-and-loss', route: '/report/ProfitAndLoss',
}, },
/*
{ {
label: t`Balance Sheet`, label: t`Balance Sheet`,
name: 'balance-sheet', name: 'balance-sheet',