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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
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';
@ -106,5 +107,7 @@ function setOnWindow() {
window.fyo = fyo;
// @ts-ignore
window.DateTime = DateTime;
// @ts-ignore
window.pnl = new ProfitAndLoss(fyo);
}
}

View File

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