2
0
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:
18alantom 2022-05-16 23:49:22 +05:30
parent ebb42c28ea
commit 1e35635170
7 changed files with 106 additions and 188 deletions

View File

@ -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({

View File

@ -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;

View File

@ -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);

View File

@ -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>

View File

@ -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,
*/

View File

@ -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');
}

View File

@ -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',
};
}