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:
parent
f48a15d97d
commit
1757f8f266
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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',
|
||||||
|
@ -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> </span>'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
for (let column of columns) {
|
...expense.accounts,
|
||||||
profitRow[column] = (income.totalRow[column] || 0.0) - (expense.totalRow[column] || 0.0);
|
expenseTotalRow,
|
||||||
|
{
|
||||||
|
account: {
|
||||||
|
template: '<span> </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;
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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(' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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'
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user