mirror of
https://github.com/frappe/books.git
synced 2025-01-22 22:58:28 +00:00
incr: get cash flow to display
This commit is contained in:
parent
ebb42c28ea
commit
1e35635170
@ -67,7 +67,7 @@ export class BespokeQueries {
|
||||
.select('name')
|
||||
.where('accountType', 'in', ['Cash', 'Bank'])
|
||||
.andWhere('isGroup', false);
|
||||
const dateAsMonthYear = db.knex!.raw('strftime("%m-%Y", ??)', 'date');
|
||||
const dateAsMonthYear = db.knex!.raw('strftime("%Y-%m", ??)', 'date');
|
||||
return await db.knex!('AccountingLedgerEntry')
|
||||
.where('reverted', false)
|
||||
.sum({
|
||||
|
@ -1,52 +0,0 @@
|
||||
import { Fyo } from 'fyo';
|
||||
import { DateTime } from 'luxon';
|
||||
import {
|
||||
getFiscalYear,
|
||||
getPeriodList,
|
||||
} from 'reports/FinancialStatements/financialStatements';
|
||||
import { FinancialStatementOptions } from 'reports/types';
|
||||
|
||||
class Cashflow {
|
||||
fyo: Fyo;
|
||||
constructor(fyo: Fyo) {
|
||||
this.fyo = fyo;
|
||||
}
|
||||
async run(options: FinancialStatementOptions) {
|
||||
const { fromDate, toDate, periodicity } = options;
|
||||
const res = await this.fyo.db.getCashflow(fromDate, toDate);
|
||||
const fiscalYear = await getFiscalYear(this.fyo);
|
||||
const periodList = getPeriodList(
|
||||
fromDate,
|
||||
toDate,
|
||||
periodicity!,
|
||||
fiscalYear
|
||||
);
|
||||
|
||||
const data = periodList.map((periodKey) => {
|
||||
const monthYear = this.getMonthYear(periodKey, 'MMM yyyy');
|
||||
const cashflowForPeriod = res.find((d) => d['month-year'] === monthYear);
|
||||
|
||||
if (cashflowForPeriod) {
|
||||
return { ...cashflowForPeriod, periodKey };
|
||||
}
|
||||
|
||||
return {
|
||||
inflow: 0,
|
||||
outflow: 0,
|
||||
periodKey,
|
||||
'month-year': monthYear,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
periodList,
|
||||
};
|
||||
}
|
||||
|
||||
getMonthYear(periodKey: string, format: string) {
|
||||
return DateTime.fromFormat(periodKey, format).toFormat('MM-yyyy');
|
||||
}
|
||||
}
|
||||
|
||||
export default Cashflow;
|
@ -26,7 +26,7 @@
|
||||
/>
|
||||
|
||||
<!-- x Labels -->
|
||||
<template v-if="xLabels.length > 0">
|
||||
<template v-if="drawLabels && xLabels.length > 0">
|
||||
<text
|
||||
:style="fontStyle"
|
||||
v-for="(i, j) in count"
|
||||
@ -46,7 +46,7 @@
|
||||
</template>
|
||||
|
||||
<!-- y Labels -->
|
||||
<template v-if="yLabelDivisions > 0">
|
||||
<template v-if="drawLabels && yLabelDivisions > 0">
|
||||
<text
|
||||
:style="fontStyle"
|
||||
v-for="(i, j) in yLabelDivisions + 1"
|
||||
@ -113,6 +113,7 @@
|
||||
/>
|
||||
</svg>
|
||||
<Tooltip
|
||||
v-if="showTooltip"
|
||||
ref="tooltip"
|
||||
:offset="15"
|
||||
placement="top"
|
||||
@ -142,6 +143,7 @@ export default {
|
||||
points: { type: Array, default: () => [[]] },
|
||||
drawAxis: { type: Boolean, default: false },
|
||||
drawXGrid: { type: Boolean, default: true },
|
||||
drawLabels: { type: Boolean, default: true },
|
||||
viewBoxHeight: { type: Number, default: 500 },
|
||||
aspectRatio: { type: Number, default: 4 },
|
||||
axisPadding: { type: Number, default: 30 },
|
||||
@ -164,6 +166,7 @@ export default {
|
||||
left: { type: Number, default: 55 },
|
||||
extendGridX: { type: Number, default: -20 },
|
||||
tooltipDispDistThreshold: { type: Number, default: 40 },
|
||||
showTooltip: { type: Boolean, default: true },
|
||||
},
|
||||
computed: {
|
||||
fontStyle() {
|
||||
@ -290,6 +293,10 @@ export default {
|
||||
return `rgb(${rgb})`;
|
||||
},
|
||||
update(event) {
|
||||
if (!this.showTooltip) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { x, y } = this.getSvgXY(event);
|
||||
const { xi, yi, cx, cy, d } = this.getPointIndexAndCoords(x, y);
|
||||
|
||||
|
@ -1,110 +1,52 @@
|
||||
<template>
|
||||
<div class="mx-4">
|
||||
<template v-if="hasData">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="font-medium">{{ t`Cashflow` }}</div>
|
||||
<div class="flex text-base">
|
||||
<div class="flex items-center">
|
||||
<span class="w-3 h-3 rounded-sm inline-block bg-blue-500"></span>
|
||||
<span class="ml-2 text-gray-900">{{ t`Inflow` }}</span>
|
||||
</div>
|
||||
<div class="flex items-center ml-6">
|
||||
<span class="w-3 h-3 rounded-sm inline-block bg-gray-500"></span>
|
||||
<span class="ml-2 text-gray-900">{{ t`Outflow` }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Title and Period Selector -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="font-medium">{{ t`Cashflow` }}</div>
|
||||
|
||||
<!-- Chart Legend -->
|
||||
<div class="flex text-base" v-if="hasData">
|
||||
<div class="flex items-center">
|
||||
<span class="w-3 h-3 rounded-sm inline-block bg-blue-500"></span>
|
||||
<span class="ml-2 text-gray-900">{{ t`Inflow` }}</span>
|
||||
</div>
|
||||
<div class="flex items-center ml-6">
|
||||
<span class="w-3 h-3 rounded-sm inline-block bg-gray-500"></span>
|
||||
<span class="ml-2 text-gray-900">{{ t`Outflow` }}</span>
|
||||
</div>
|
||||
<PeriodSelector
|
||||
:value="period"
|
||||
@change="(value) => (period = value)"
|
||||
:options="['This Year', 'This Quarter']"
|
||||
/>
|
||||
</div>
|
||||
<LineChart
|
||||
<div v-else class="w-16 h-5 bg-gray-200 rounded" />
|
||||
|
||||
<PeriodSelector
|
||||
:value="period"
|
||||
@change="(value) => (period = value)"
|
||||
:options="['This Year', 'This Quarter']"
|
||||
v-if="hasData"
|
||||
:colors="chartData.colors"
|
||||
:points="chartData.points"
|
||||
:x-labels="chartData.xLabels"
|
||||
:format="chartData.format"
|
||||
:format-x="chartData.formatX"
|
||||
:y-max="chartData.yMax"
|
||||
/>
|
||||
</template>
|
||||
<svg
|
||||
v-else
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 889 280"
|
||||
class="my-4"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient x1="50%" y1="100%" x2="50%" y2=".889%" id="a">
|
||||
<stop stop-color="#FFF" stop-opacity="0" offset="0%" />
|
||||
<stop stop-color="#F4F4F6" offset="100%" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<text fill="#112B42" class="font-medium">
|
||||
<tspan y="16">{{ t`Cashflow` }}</tspan>
|
||||
</text>
|
||||
<g fill="#E9E9ED">
|
||||
<path d="M371 2h12v12h-12zM391 2h53v12h-53z" />
|
||||
<g>
|
||||
<path d="M453 2h12v12h-12zM473 2h53v12h-53z" />
|
||||
</g>
|
||||
</g>
|
||||
<path
|
||||
fill="#E9E9ED"
|
||||
d="M0 41h19v12H0zM4 121h15v12H4zM2 81h17v12H2zM4 201h15v12H4zM3 161h16v12H3z"
|
||||
/>
|
||||
<path
|
||||
d="M37.25 211.25h849.5v1H37.25zM37.25 167.25h849.5v1H37.25zM37.25 127.25h849.5v1H37.25zM37.25 87.25h849.5v1H37.25zM37.25 47.25h849.5v1H37.25z"
|
||||
stroke="#F6F7F9"
|
||||
stroke-width=".5"
|
||||
/>
|
||||
<g fill="#E9E9ED">
|
||||
<path
|
||||
d="M49 228h31v12H49zM122 228h31v12h-31zM195 228h31v12h-31zM268 228h31v12h-31zM341 228h31v12h-31zM414 228h31v12h-31zM487 228h31v12h-31zM560 228h31v12h-31zM633 228h31v12h-31zM706 228h31v12h-31zM779 228h31v12h-31zM852 228h31v12h-31z"
|
||||
/>
|
||||
</g>
|
||||
<g fill-rule="nonzero">
|
||||
<path
|
||||
fill="url(#a)"
|
||||
opacity=".5"
|
||||
d="M12 34l78 73 73 12 74-37 73 36.167L383 126l73-55.5L529.223 98 602 0l73 75 73 2 73 34 29 41v25H0V25z"
|
||||
transform="translate(37 35)"
|
||||
/>
|
||||
<path
|
||||
stroke="#E9E9ED"
|
||||
stroke-width="3"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M37 60l12 9 78 73 73 12 74-37 73 36.167L420 161l73-55.5 73.223 27.5L639 35l73 75 73 2 73 34 29 42"
|
||||
/>
|
||||
<g>
|
||||
<path
|
||||
fill="url(#a)"
|
||||
opacity=".5"
|
||||
d="M12 44.599l78 31.345 73 5.152 74-26.192L310 .738l73 21.578 73 37.955 73.223 11.808L602 30l73 32.203 73 .86 73 14.598L850 58v48H0V40.734z"
|
||||
transform="translate(37 106)"
|
||||
/>
|
||||
<path
|
||||
stroke="#E9E9ED"
|
||||
stroke-width="3"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M37 146.734l12 3.865 78 31.345 73 5.152 74-26.192 73-54.166 73 21.578 73 37.955 73.223 11.808L639 136l73 32.203 73 .86 73 14.598L887 164"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<div v-else class="w-20 h-5 bg-gray-200 rounded" />
|
||||
</div>
|
||||
|
||||
<!-- Line Chart -->
|
||||
<LineChart
|
||||
v-if="chartData.points.length"
|
||||
:colors="chartData.colors"
|
||||
:points="chartData.points"
|
||||
:x-labels="chartData.xLabels"
|
||||
:format="chartData.format"
|
||||
:format-x="chartData.formatX"
|
||||
:y-max="chartData.yMax"
|
||||
:draw-labels="hasData"
|
||||
:show-tooltip="hasData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { AccountTypeEnum } from 'models/baseModels/Account/types';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import LineChart from 'src/components/Charts/LineChart.vue';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { formatXLabels, getYMax } from 'src/utils/chart';
|
||||
import { getDatesAndPeriodicity } from 'src/utils/misc';
|
||||
import Cashflow from '../../../reports/Cashflow/Cashflow';
|
||||
import PeriodSelector from './PeriodSelector';
|
||||
|
||||
export default {
|
||||
@ -117,25 +59,29 @@ export default {
|
||||
period: 'This Year',
|
||||
data: [],
|
||||
periodList: [],
|
||||
hasData: false,
|
||||
}),
|
||||
watch: {
|
||||
period: 'setData',
|
||||
},
|
||||
async activated() {
|
||||
await this.setData();
|
||||
if (!this.hasData) {
|
||||
await this.setHasData();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasData() {
|
||||
let totalInflow = this.data.reduce((sum, d) => d.inflow + sum, 0);
|
||||
let totalOutflow = this.data.reduce((sum, d) => d.outflow + sum, 0);
|
||||
return !(totalInflow === 0 && totalOutflow === 0);
|
||||
},
|
||||
chartData() {
|
||||
const xLabels = this.periodList;
|
||||
const points = ['inflow', 'outflow'].map((k) =>
|
||||
this.data.map((d) => d[k])
|
||||
);
|
||||
const colors = ['#2490EF', '#B7BFC6'];
|
||||
let data = this.data;
|
||||
let colors = ['#2490EF', '#B7BFC6'];
|
||||
if (!this.hasData) {
|
||||
data = dummyData;
|
||||
colors = ['#E9EBED', '#B7BFC6'];
|
||||
}
|
||||
|
||||
const xLabels = data.map((cf) => cf['month-year']);
|
||||
const points = ['inflow', 'outflow'].map((k) => data.map((d) => d[k]));
|
||||
|
||||
const format = (value) => fyo.format(value ?? 0, 'Currency');
|
||||
const yMax = getYMax(points);
|
||||
return { points, xLabels, colors, format, yMax, formatX: formatXLabels };
|
||||
@ -143,19 +89,44 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async setData() {
|
||||
let { fromDate, toDate, periodicity } = await getDatesAndPeriodicity(
|
||||
this.period
|
||||
);
|
||||
|
||||
const { data, periodList } = await new Cashflow(this.fyo).run({
|
||||
fromDate,
|
||||
toDate,
|
||||
periodicity,
|
||||
const { fromDate, toDate } = await getDatesAndPeriodicity(this.period);
|
||||
this.data = await fyo.db.getCashflow(fromDate, toDate);
|
||||
},
|
||||
async setHasData() {
|
||||
const accounts = await fyo.db.getAllRaw('Account', {
|
||||
filters: {
|
||||
accountType: ['in', [AccountTypeEnum.Cash, AccountTypeEnum.Bank]],
|
||||
},
|
||||
});
|
||||
|
||||
this.data = data;
|
||||
this.periodList = periodList;
|
||||
const accountNames = accounts.map((a) => a.name);
|
||||
const count = await fyo.db.count(ModelNameEnum.AccountingLedgerEntry, {
|
||||
filters: { account: ['in', accountNames] },
|
||||
});
|
||||
this.hasData = count > 0;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const dummyData = [
|
||||
{
|
||||
inflow: 100,
|
||||
outflow: 250,
|
||||
'month-year': '2021-05',
|
||||
},
|
||||
{
|
||||
inflow: 350,
|
||||
outflow: 100,
|
||||
'month-year': '2021-06',
|
||||
},
|
||||
{
|
||||
inflow: 50,
|
||||
outflow: 300,
|
||||
'month-year': '2021-07',
|
||||
},
|
||||
{
|
||||
inflow: 320,
|
||||
outflow: 100,
|
||||
'month-year': '2021-08',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
@ -3,14 +3,11 @@
|
||||
<PageHeader :title="t`Dashboard`" />
|
||||
|
||||
<div class="mx-4 overflow-y-scroll no-scrollbar">
|
||||
<!--
|
||||
<Cashflow class="mt-5" />
|
||||
<hr class="border-t mt-10" />
|
||||
-->
|
||||
|
||||
<UnpaidInvoices class="mt-10" />
|
||||
<!--
|
||||
<hr class="border-t mt-10" />
|
||||
<!--
|
||||
<div class="flex justify-between mx-auto mt-10 ml-4 mr-4 gap-10">
|
||||
<ProfitAndLoss class="w-1/2" />
|
||||
<Expenses class="w-1/2" />
|
||||
@ -22,7 +19,7 @@
|
||||
|
||||
<script>
|
||||
import PageHeader from 'src/components/PageHeader';
|
||||
// import Cashflow from './Cashflow';
|
||||
import Cashflow from './Cashflow';
|
||||
// import Expenses from './Expenses';
|
||||
// import ProfitAndLoss from './ProfitAndLoss';
|
||||
import UnpaidInvoices from './UnpaidInvoices';
|
||||
@ -32,8 +29,8 @@ export default {
|
||||
components: {
|
||||
PageHeader,
|
||||
UnpaidInvoices,
|
||||
/*
|
||||
Cashflow,
|
||||
/*
|
||||
ProfitAndLoss,
|
||||
Expenses,
|
||||
*/
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export function prefixFormat(value: number): string {
|
||||
/*
|
||||
1,000000,000,000,000,000 = 1 P (Pentillion)
|
||||
@ -43,7 +45,7 @@ function getVal(minOrMaxVal: number): number {
|
||||
return Math.floor(minOrMaxVal / rc) * rc;
|
||||
}
|
||||
|
||||
export function getYMax(points: Array<Array<number>>): number {
|
||||
export function getYMax(points: number[][]): number {
|
||||
const maxVal = Math.max(...points.flat());
|
||||
if (maxVal === 0) {
|
||||
return 0;
|
||||
@ -52,7 +54,7 @@ export function getYMax(points: Array<Array<number>>): number {
|
||||
return getVal(maxVal);
|
||||
}
|
||||
|
||||
export function getYMin(points: Array<Array<number>>): number {
|
||||
export function getYMin(points: number[][]): number {
|
||||
const minVal = Math.min(...points.flat());
|
||||
if (minVal === 0) {
|
||||
return minVal;
|
||||
@ -62,10 +64,5 @@ export function getYMin(points: Array<Array<number>>): number {
|
||||
}
|
||||
|
||||
export function formatXLabels(label: string) {
|
||||
// Format: Mmm YYYY -> Mm YY
|
||||
const splits = label.split(' ');
|
||||
const month = splits[0];
|
||||
const year = splits[1].slice(2);
|
||||
|
||||
return `${month} ${year}`;
|
||||
return DateTime.fromISO(label).toFormat('MMM yy');
|
||||
}
|
||||
|
@ -3,14 +3,13 @@ import { getSingleValue } from 'fyo/utils';
|
||||
import { DateTime } from 'luxon';
|
||||
import { SetupWizard } from 'models/baseModels/SetupWizard/SetupWizard';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import { Periodicity } from 'reports/types';
|
||||
import SetupWizardSchema from 'schemas/app/SetupWizard.json';
|
||||
import { Schema } from 'schemas/types';
|
||||
import { fyo } from 'src/initFyo';
|
||||
|
||||
export function getDatesAndPeriodicity(
|
||||
period: 'This Year' | 'This Quarter' | 'This Month'
|
||||
): { fromDate: string; toDate: string; periodicity: Periodicity } {
|
||||
): { fromDate: string; toDate: string } {
|
||||
const toDate: DateTime = DateTime.now();
|
||||
let fromDate: DateTime;
|
||||
|
||||
@ -27,7 +26,6 @@ export function getDatesAndPeriodicity(
|
||||
return {
|
||||
fromDate: fromDate.toISO(),
|
||||
toDate: toDate.toISO(),
|
||||
periodicity: 'Monthly',
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user