2
0
mirror of https://github.com/frappe/books.git synced 2024-11-09 23:30:56 +00:00

fix: Report

- Tree structured reports
- Profit and Loss
This commit is contained in:
Faris Ansari 2019-12-03 17:15:07 +05:30
parent f48a15d97d
commit 1757f8f266
7 changed files with 168 additions and 49 deletions

View File

@ -258,7 +258,7 @@ async function getLedgerEntries(fromDate, toDate, accounts) {
async function getAccounts(rootType) { async function getAccounts(rootType) {
let accounts = await frappe.db.getAll({ let accounts = await frappe.db.getAll({
doctype: 'Account', doctype: 'Account',
fields: ['name', 'parentAccount'], fields: ['name', 'parentAccount', 'isGroup'],
filters: { filters: {
rootType rootType
} }

View File

@ -114,17 +114,20 @@ const viewConfig = {
{ {
label: 'Debit', label: 'Debit',
fieldtype: 'Currency', fieldtype: 'Currency',
fieldname: 'debit' fieldname: 'debit',
width: 0.5
}, },
{ {
label: 'Credit', label: 'Credit',
fieldtype: 'Currency', fieldtype: 'Currency',
fieldname: 'credit' fieldname: 'credit',
width: 0.5
}, },
{ {
label: 'Balance', label: 'Balance',
fieldtype: 'Currency', fieldtype: 'Currency',
fieldname: 'balance' fieldname: 'balance',
width: 0.5
}, },
{ {
label: 'Reference Type', label: 'Reference Type',

View File

@ -3,43 +3,80 @@ const { unique } = require('frappejs/utils');
const { getData } = require('../FinancialStatements/FinancialStatements'); const { getData } = require('../FinancialStatements/FinancialStatements');
class ProfitAndLoss { class ProfitAndLoss {
async run({ fromDate, toDate, periodicity }) { async run({ fromDate, toDate, periodicity }) {
let income = await getData({
rootType: 'Income',
balanceMustBe: 'Credit',
fromDate,
toDate,
periodicity
});
let income = await getData({ let expense = await getData({
rootType: 'Income', rootType: 'Expense',
balanceMustBe: 'Credit', balanceMustBe: 'Debit',
fromDate, fromDate,
toDate, toDate,
periodicity periodicity
}); });
let expense = await getData({ let incomeTotalRow = income.totalRow;
rootType: 'Expense', incomeTotalRow.account = {
balanceMustBe: 'Debit', template: `<span class="font-semibold">${income.totalRow.account}</span>`
fromDate, };
toDate,
periodicity
});
const rows = [ let expenseTotalRow = expense.totalRow;
...income.accounts, income.totalRow, [], expenseTotalRow.account = {
...expense.accounts, expense.totalRow, [] template: `<span class="font-semibold">${expense.totalRow.account}</span>`
]; };
const columns = unique([...income.periodList, ...expense.periodList]) let rows = [
...income.accounts,
let profitRow = { incomeTotalRow,
account: 'Total Profit' {
account: {
template: '<span>&nbsp;</span>'
} }
},
for (let column of columns) { ...expense.accounts,
profitRow[column] = (income.totalRow[column] || 0.0) - (expense.totalRow[column] || 0.0); expenseTotalRow,
{
account: {
template: '<span>&nbsp;</span>'
} }
}
];
rows.push(profitRow); rows = rows.map(row => {
if (row.indent === 0) {
row.account = {
template: `<span class="font-semibold">${row.account}</span>`
};
}
return row;
});
return { rows, columns }; const columns = unique([...income.periodList, ...expense.periodList]);
let profitRow = {
account: 'Total Profit'
};
for (let column of columns) {
profitRow[column] =
(income.totalRow[column] || 0.0) - (expense.totalRow[column] || 0.0);
rows.forEach(row => {
if (!row.isGroup) {
row[column] = row[column] || 0.0;
}
});
} }
rows.push(profitRow);
return { rows, columns };
}
} }
module.exports = ProfitAndLoss; module.exports = ProfitAndLoss;

View File

@ -45,7 +45,7 @@ module.exports = {
], ],
getColumns(data) { getColumns(data) {
const columns = [ const columns = [
{ label: 'Account', fieldtype: 'Data', fieldname: 'account', width: 340 } { label: 'Account', fieldtype: 'Data', fieldname: 'account', width: 2 }
]; ];
if (data && data.columns) { if (data && data.columns) {

View File

@ -19,6 +19,10 @@ export default {
type: Array, type: Array,
default: () => [] default: () => []
}, },
gridTemplateColumns: {
type: String,
default: null
},
gap: String gap: String
}, },
computed: { computed: {
@ -30,6 +34,9 @@ export default {
if (this.ratio.length) { if (this.ratio.length) {
obj['grid-template-columns'] = this.ratio.map(r => `${r}fr`).join(' '); obj['grid-template-columns'] = this.ratio.map(r => `${r}fr`).join(' ');
} }
if (this.gridTemplateColumns) {
obj['grid-template-columns'] = this.gridTemplateColumns;
}
if (this.gap) { if (this.gap) {
obj['grid-gap'] = this.gap; obj['grid-gap'] = this.gap;
} }

View File

@ -20,11 +20,7 @@
<div class="px-8 mt-4"> <div class="px-8 mt-4">
<div> <div>
<div ref="header" class="overflow-hidden"> <div ref="header" class="overflow-hidden">
<Row <Row gap="2rem" :grid-template-columns="gridTemplateColumns">
:columnCount="columns.length"
gap="2rem"
:column-width="columnWidth"
>
<div <div
class="text-gray-600 text-base truncate py-4" class="text-gray-600 text-base truncate py-4"
:class="{ :class="{
@ -42,23 +38,31 @@
<WithScroll @scroll="onBodyScroll"> <WithScroll @scroll="onBodyScroll">
<div class="flex-1 overflow-auto" style="height: calc(100vh - 12rem)"> <div class="flex-1 overflow-auto" style="height: calc(100vh - 12rem)">
<Row <Row
v-show="row.isShown"
v-for="(row, i) in rows" v-for="(row, i) in rows"
:columnCount="columns.length"
gap="2rem"
:key="i" :key="i"
:column-width="columnWidth" gap="2rem"
:grid-template-columns="gridTemplateColumns"
> >
<div <div
class="text-gray-900 text-base truncate py-4" class="text-gray-900 text-base truncate py-4"
:class="{ :class="getCellClasses(row, column)"
'text-right': ['Int', 'Float', 'Currency'].includes(
column.fieldtype
)
}"
v-for="column in columns" v-for="column in columns"
:key="column.label" :key="column.label"
@click="toggleChildren(row, i)"
> >
<component :is="cellComponent(row[column.fieldname], column)" /> <div class="inline-flex">
<feather-icon
v-if="row.isBranch && !row.isLeaf && column === columns[0]"
class="w-4 h-4 mr-2 flex-shrink-0"
:name="row.expanded ? 'chevron-down' : 'chevron-right'"
/>
<span class="truncate">
<component
:is="cellComponent(row[column.fieldname], column)"
/>
</span>
</div>
</div> </div>
</Row> </Row>
</div> </div>
@ -136,7 +140,42 @@ export default {
rows = []; rows = [];
} }
this.rows = rows; this.rows = this.addTreeMeta(rows);
},
addTreeMeta(rows) {
return rows.map(row => {
if ('indent' in row) {
row.isBranch = true;
row.expanded = true;
row.isLeaf = !row.isGroup;
}
row.isShown = true;
return row;
});
},
toggleChildren(row, rowIndex) {
if (!row.isBranch) return;
let flag;
if (row.expanded) {
row.expanded = false;
flag = false;
} else {
row.expanded = true;
flag = true;
}
let _rows = this.rows.slice(rowIndex + 1);
for (let _row of _rows) {
if (row.isBranch && _row.indent > row.indent) {
_row.expanded = flag;
_row.isShown = flag;
continue;
}
break;
}
}, },
onFilterChange(df, value) { onFilterChange(df, value) {
@ -172,12 +211,30 @@ export default {
return column.component(cellValue, column); return column.component(cellValue, column);
} }
// default cell component // default cell component
let formattedValue = frappe.format(cellValue, column); let formattedValue =
cellValue != null ? frappe.format(cellValue, column) : '';
return { return {
render(h) { render(h) {
return h('span', formattedValue); return h('span', formattedValue);
} }
}; };
},
getCellClasses(row, column) {
let padding = ['pl-0', 'pl-6', 'pl-12', 'pl-18', 'pl-20'];
let treeCellClasses;
if (row.isBranch && column === this.columns[0]) {
treeCellClasses = [
padding[row.indent],
'hover:bg-gray-100 cursor-pointer'
];
}
return [
{
'text-right': ['Int', 'Float', 'Currency'].includes(column.fieldtype)
},
treeCellClasses
];
} }
}, },
computed: { computed: {
@ -186,6 +243,20 @@ export default {
}, },
columnWidth() { columnWidth() {
return 'minmax(7rem, 1fr)'; return 'minmax(7rem, 1fr)';
},
gridTemplateColumns() {
return this.columns
.map(col => {
let multiplier = col.width;
if (!multiplier) {
multiplier = 1;
}
let minWidth = `${7 * multiplier}rem`;
let maxWidth = `${1 * multiplier}fr`;
return `minmax(${minWidth}, ${maxWidth})`;
})
.join(' ');
} }
} }
}; };

View File

@ -29,6 +29,7 @@ module.exports = {
spacing: { spacing: {
'7': '1.75rem', '7': '1.75rem',
'14': '3.5rem', '14': '3.5rem',
'18': '4.5rem',
'72': '18rem', '72': '18rem',
'80': '20rem' '80': '20rem'
}, },