mirror of
https://github.com/frappe/books.git
synced 2025-01-03 07:12:21 +00:00
incr: start rewriting reports
- list type report eg GL almost done
This commit is contained in:
parent
c95473c6b0
commit
cc493e9fb1
@ -22,6 +22,7 @@ interface RawLedgerEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface LedgerEntry {
|
interface LedgerEntry {
|
||||||
|
index?: string;
|
||||||
name: number;
|
name: number;
|
||||||
account: string;
|
account: string;
|
||||||
date: Date | null;
|
date: Date | null;
|
||||||
@ -49,8 +50,8 @@ export class GeneralLedger extends Report {
|
|||||||
super(fyo);
|
super(fyo);
|
||||||
|
|
||||||
if (!this.toField) {
|
if (!this.toField) {
|
||||||
this.toField = DateTime.now().toISODate();
|
this.toDate = DateTime.now().toISODate();
|
||||||
this.fromField = DateTime.now().minus({ years: 1 }).toISODate();
|
this.fromDate = DateTime.now().minus({ years: 1 }).toISODate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,13 +61,14 @@ export class GeneralLedger extends Report {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const map = this._getGroupedMap();
|
const map = this._getGroupedMap();
|
||||||
|
this._setIndexOnEntries(map);
|
||||||
const { totalDebit, totalCredit } = this._getTotalsAndSetBalance(map);
|
const { totalDebit, totalCredit } = this._getTotalsAndSetBalance(map);
|
||||||
const consolidated = this._consolidateEntries(map);
|
const consolidated = this._consolidateEntries(map);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Push a blank row if last row isn't blank
|
* Push a blank row if last row isn't blank
|
||||||
*/
|
*/
|
||||||
if (consolidated.at(-1)!.name !== -3) {
|
if (consolidated.at(-1)?.name !== -3) {
|
||||||
this._pushBlankEntry(consolidated);
|
this._pushBlankEntry(consolidated);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,42 +92,52 @@ export class GeneralLedger extends Report {
|
|||||||
this.reportData = this._convertEntriesToReportData(consolidated);
|
this.reportData = this._convertEntriesToReportData(consolidated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_setIndexOnEntries(map: GroupedMap) {
|
||||||
|
let i = 1;
|
||||||
|
for (const key of map.keys()) {
|
||||||
|
for (const entry of map.get(key)!) {
|
||||||
|
entry.index = String(i);
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_convertEntriesToReportData(entries: LedgerEntry[]): ReportData {
|
_convertEntriesToReportData(entries: LedgerEntry[]): ReportData {
|
||||||
const reportData = [];
|
const reportData = [];
|
||||||
const fieldnames = this.columns.map((f) => f.fieldname);
|
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const row = this._getRowFromEntry(entry, fieldnames);
|
const row = this._getRowFromEntry(entry, this.columns);
|
||||||
reportData.push(row);
|
reportData.push(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
return reportData;
|
return reportData;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getRowFromEntry(entry: LedgerEntry, fieldnames: string[]) {
|
_getRowFromEntry(entry: LedgerEntry, columns: ColumnField[]) {
|
||||||
if (entry.name === -3) {
|
if (entry.name === -3) {
|
||||||
return Array(fieldnames.length).fill({ value: '' });
|
return Array(columns.length).fill({ value: '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const row = [];
|
const row = [];
|
||||||
for (const n of fieldnames) {
|
for (const col of columns) {
|
||||||
let value = entry[n as keyof LedgerEntry];
|
const align = col.align ?? 'left';
|
||||||
|
const width = col.width ?? 1;
|
||||||
|
const fieldname = col.fieldname;
|
||||||
|
|
||||||
|
let value = entry[fieldname as keyof LedgerEntry];
|
||||||
if (value === null || value === undefined) {
|
if (value === null || value === undefined) {
|
||||||
row.push({ value: '' });
|
value = '';
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let align = 'left';
|
|
||||||
if (value instanceof Date) {
|
if (value instanceof Date) {
|
||||||
value = this.fyo.format(value, FieldTypeEnum.Date);
|
value = this.fyo.format(value, FieldTypeEnum.Date);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === 'number') {
|
if (typeof value === 'number' && fieldname !== 'index') {
|
||||||
align = 'right';
|
|
||||||
value = this.fyo.format(value, FieldTypeEnum.Currency);
|
value = this.fyo.format(value, FieldTypeEnum.Currency);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === 'boolean' && n === 'reverted' && value) {
|
if (typeof value === 'boolean' && fieldname === 'reverted') {
|
||||||
value = t`Reverted`;
|
value = value ? t`Reverted` : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
row.push({
|
row.push({
|
||||||
@ -133,6 +145,7 @@ export class GeneralLedger extends Report {
|
|||||||
bold: entry.name === -2,
|
bold: entry.name === -2,
|
||||||
value,
|
value,
|
||||||
align,
|
align,
|
||||||
|
width,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,7 +279,7 @@ export class GeneralLedger extends Report {
|
|||||||
'reverts',
|
'reverts',
|
||||||
];
|
];
|
||||||
|
|
||||||
const filters = this._getQueryFilters();
|
const filters = {} ?? this._getQueryFilters();
|
||||||
const entries = (await this.fyo.db.getAllRaw(
|
const entries = (await this.fyo.db.getAllRaw(
|
||||||
ModelNameEnum.AccountingLedgerEntry,
|
ModelNameEnum.AccountingLedgerEntry,
|
||||||
{
|
{
|
||||||
@ -338,16 +351,16 @@ export class GeneralLedger extends Report {
|
|||||||
{ label: t`Journal Entries`, value: 'JournalEntry' },
|
{ label: t`Journal Entries`, value: 'JournalEntry' },
|
||||||
],
|
],
|
||||||
|
|
||||||
label: t`Reference Type`,
|
label: t`Ref Type`,
|
||||||
fieldname: 'referenceType',
|
fieldname: 'referenceType',
|
||||||
placeholder: t`Reference Type`,
|
placeholder: t`Ref Type`,
|
||||||
default: 'All',
|
default: 'All',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldtype: 'DynamicLink',
|
fieldtype: 'DynamicLink',
|
||||||
placeholder: t`Reference Name`,
|
label: t`Ref Name`,
|
||||||
references: 'referenceType',
|
references: 'referenceType',
|
||||||
label: t`Reference Name`,
|
placeholder: t`Ref Name`,
|
||||||
fieldname: 'referenceName',
|
fieldname: 'referenceName',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -377,19 +390,7 @@ export class GeneralLedger extends Report {
|
|||||||
fieldname: 'toDate',
|
fieldname: 'toDate',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldtype: 'Check',
|
fieldtype: 'Select',
|
||||||
default: false,
|
|
||||||
label: t`Cancelled`,
|
|
||||||
fieldname: 'reverted',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldtype: 'Check',
|
|
||||||
default: false,
|
|
||||||
label: t`Ascending`,
|
|
||||||
fieldname: 'ascending',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldtype: 'Check',
|
|
||||||
default: 'none',
|
default: 'none',
|
||||||
label: t`Group By`,
|
label: t`Group By`,
|
||||||
fieldname: 'groupBy',
|
fieldname: 'groupBy',
|
||||||
@ -400,11 +401,30 @@ export class GeneralLedger extends Report {
|
|||||||
{ label: t`Reference`, value: 'referenceName' },
|
{ label: t`Reference`, value: 'referenceName' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldtype: 'Check',
|
||||||
|
default: false,
|
||||||
|
label: t`Include Cancelled`,
|
||||||
|
fieldname: 'reverted',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: 'Check',
|
||||||
|
default: false,
|
||||||
|
label: t`Ascending Order`,
|
||||||
|
fieldname: 'ascending',
|
||||||
|
},
|
||||||
] as Field[];
|
] as Field[];
|
||||||
}
|
}
|
||||||
|
|
||||||
getColumns(): ColumnField[] {
|
getColumns(): ColumnField[] {
|
||||||
let columns = [
|
let columns = [
|
||||||
|
{
|
||||||
|
label: t`#`,
|
||||||
|
fieldtype: 'Int',
|
||||||
|
fieldname: 'index',
|
||||||
|
align: 'right',
|
||||||
|
width: 0.5,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: t`Account`,
|
label: t`Account`,
|
||||||
fieldtype: 'Link',
|
fieldtype: 'Link',
|
||||||
@ -415,41 +435,43 @@ export class GeneralLedger extends Report {
|
|||||||
label: t`Date`,
|
label: t`Date`,
|
||||||
fieldtype: 'Date',
|
fieldtype: 'Date',
|
||||||
fieldname: 'date',
|
fieldname: 'date',
|
||||||
width: 0.75,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t`Debit`,
|
label: t`Debit`,
|
||||||
fieldtype: 'Currency',
|
fieldtype: 'Currency',
|
||||||
fieldname: 'debit',
|
fieldname: 'debit',
|
||||||
|
align: 'right',
|
||||||
width: 1.25,
|
width: 1.25,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t`Credit`,
|
label: t`Credit`,
|
||||||
fieldtype: 'Currency',
|
fieldtype: 'Currency',
|
||||||
fieldname: 'credit',
|
fieldname: 'credit',
|
||||||
|
align: 'right',
|
||||||
width: 1.25,
|
width: 1.25,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t`Balance`,
|
label: t`Balance`,
|
||||||
fieldtype: 'Currency',
|
fieldtype: 'Currency',
|
||||||
fieldname: 'balance',
|
fieldname: 'balance',
|
||||||
|
align: 'right',
|
||||||
width: 1.25,
|
width: 1.25,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: t`Reference Type`,
|
|
||||||
fieldtype: 'Data',
|
|
||||||
fieldname: 'referenceType',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t`Reference Name`,
|
|
||||||
fieldtype: 'Data',
|
|
||||||
fieldname: 'referenceName',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: t`Party`,
|
label: t`Party`,
|
||||||
fieldtype: 'Link',
|
fieldtype: 'Link',
|
||||||
fieldname: 'party',
|
fieldname: 'party',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: t`Ref Name`,
|
||||||
|
fieldtype: 'Data',
|
||||||
|
fieldname: 'referenceName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t`Ref Type`,
|
||||||
|
fieldtype: 'Data',
|
||||||
|
fieldname: 'referenceType',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: t`Reverted`,
|
label: t`Reverted`,
|
||||||
fieldtype: 'Check',
|
fieldtype: 'Check',
|
||||||
@ -467,4 +489,12 @@ export class GeneralLedger extends Report {
|
|||||||
getActions(): Action[] {
|
getActions(): Action[] {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Order by date and then the number
|
||||||
|
* TODO: Something is wrong with dummy data, there are entries from 2022 Dec
|
||||||
|
* TODO: Add pagination
|
||||||
|
* TODO: Always visible scrollbar
|
||||||
|
* TODO: Extract out list view report to a different component
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
import { GeneralLedger } from './GeneralLedger/GeneralLedger';
|
import { GeneralLedger } from './GeneralLedger/GeneralLedger';
|
||||||
export { GeneralLedger };
|
export const reports = { GeneralLedger };
|
||||||
|
@ -7,12 +7,14 @@ export interface ReportCell {
|
|||||||
bold?: boolean;
|
bold?: boolean;
|
||||||
italics?: boolean;
|
italics?: boolean;
|
||||||
align?: 'left' | 'right' | 'center';
|
align?: 'left' | 'right' | 'center';
|
||||||
|
width?: number;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ReportRow = ReportCell[];
|
export type ReportRow = ReportCell[];
|
||||||
export type ReportData = ReportRow[];
|
export type ReportData = ReportRow[];
|
||||||
export interface ColumnField extends BaseField {
|
export interface ColumnField extends BaseField {
|
||||||
|
align?: 'left' | 'right' | 'center';
|
||||||
width?: number;
|
width?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ export default {
|
|||||||
},
|
},
|
||||||
async getFilters() {
|
async getFilters() {
|
||||||
const { schemaName, fieldname } = this.df;
|
const { schemaName, fieldname } = this.df;
|
||||||
const getFilters = fyo.models[schemaName].filters[fieldname];
|
const getFilters = fyo.models[schemaName]?.filters?.[fieldname];
|
||||||
|
|
||||||
if (getFilters === undefined) {
|
if (getFilters === undefined) {
|
||||||
return {};
|
return {};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col max-w-full">
|
<div class="flex flex-col w-full h-full">
|
||||||
<PageHeader :title="report.title">
|
<PageHeader :title="title">
|
||||||
|
<!--
|
||||||
<DropdownWithActions
|
<DropdownWithActions
|
||||||
v-for="group of actionGroups"
|
v-for="group of actionGroups"
|
||||||
:key="group.label"
|
:key="group.label"
|
||||||
@ -11,428 +12,164 @@
|
|||||||
{{ group.label }}
|
{{ group.label }}
|
||||||
</DropdownWithActions>
|
</DropdownWithActions>
|
||||||
<DropdownWithActions :actions="actions" />
|
<DropdownWithActions :actions="actions" />
|
||||||
|
|
||||||
|
-->
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<div class="flex px-8 mt-2 text-base" v-if="report.filterFields">
|
|
||||||
<div
|
<!-- Filters -->
|
||||||
class="w-40 ml-2 first:ml-0"
|
<div v-if="report" class="mx-4 grid grid-cols-5 gap-2">
|
||||||
:class="
|
<FormControl
|
||||||
df.fieldtype === 'Check' &&
|
v-for="field in report.filters"
|
||||||
'flex justify-between items-center bg-gray-100 px-2 rounded'
|
:show-label="field.fieldtype === 'Check'"
|
||||||
"
|
:key="field.fieldname + '-filter'"
|
||||||
v-for="df in report.filterFields"
|
class="bg-gray-100 rounded"
|
||||||
:key="df.fieldname"
|
:class="field.fieldtype === 'Check' ? 'flex pl-3' : ''"
|
||||||
>
|
input-class="bg-transparent px-3 py-2 text-base"
|
||||||
<div v-if="df.fieldtype === 'Check'" class="text-sm mr-2">
|
:df="field"
|
||||||
{{ df.label }}
|
:value="report.get(field.fieldname)"
|
||||||
</div>
|
:read-only="loading"
|
||||||
<FormControl
|
@change="async (value) => await report.set(field.fieldname, value)"
|
||||||
size="small"
|
/>
|
||||||
input-class="bg-gray-100"
|
|
||||||
:df="df"
|
|
||||||
:value="filters[df.fieldname]"
|
|
||||||
@change="(value) => onFilterChange(df, value)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="px-8 mt-4">
|
|
||||||
<div>
|
<!-- Report Outer Container -->
|
||||||
<div ref="header" class="overflow-hidden">
|
<div
|
||||||
<Row gap="2rem" :grid-template-columns="gridTemplateColumns">
|
v-if="report"
|
||||||
<div
|
class="mx-4 mt-4 overflow-x-scroll inline-block overflow-y-hidden"
|
||||||
class="py-4 text-base truncate"
|
>
|
||||||
:class="[
|
<div class="inline-block">
|
||||||
getColumnAlignClass(column),
|
<!-- Title Row -->
|
||||||
loading ? 'text-gray-100' : 'text-gray-600',
|
|
||||||
]"
|
|
||||||
v-for="column in columns"
|
|
||||||
:key="column.label"
|
|
||||||
>
|
|
||||||
<span :class="{ 'bg-gray-100': loading }">
|
|
||||||
{{ column.label }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
<WithScroll
|
|
||||||
@scroll="onBodyScroll"
|
|
||||||
class="flex-1 overflow-auto"
|
|
||||||
:style="`height: ${height}`"
|
|
||||||
>
|
|
||||||
<Row
|
|
||||||
v-show="row.isShown"
|
|
||||||
v-for="(row, i) in rows.slice(sliceIndex.from, sliceIndex.to)"
|
|
||||||
:key="i"
|
|
||||||
gap="2rem"
|
|
||||||
:grid-template-columns="gridTemplateColumns"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="py-4 text-base overflow-scroll no-scrollbar"
|
|
||||||
:class="getCellClasses(row, column)"
|
|
||||||
v-for="column in columns"
|
|
||||||
:key="column.label"
|
|
||||||
@click="toggleChildren(row, i)"
|
|
||||||
>
|
|
||||||
<div class="inline-flex">
|
|
||||||
<feather-icon
|
|
||||||
v-if="row.isBranch && !row.isLeaf && column === columns[0]"
|
|
||||||
class="flex-shrink-0 w-4 h-4 mr-2"
|
|
||||||
:name="row.expanded ? 'chevron-down' : 'chevron-right'"
|
|
||||||
/>
|
|
||||||
<span class="truncate" :class="{ 'bg-gray-100': loading }">
|
|
||||||
<component
|
|
||||||
:is="cellComponent(row[column.fieldname], column)"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Row>
|
|
||||||
</WithScroll>
|
|
||||||
<div
|
<div
|
||||||
v-if="usePagination"
|
class="flex items-center border-b"
|
||||||
class="flex w-full justify-center mt-2.5 text-base"
|
:style="{ height: `${hconst}px` }"
|
||||||
|
ref="titleRow"
|
||||||
>
|
>
|
||||||
<div class="flex justify-center items-center gap-1 text-gray-800">
|
<p
|
||||||
<feather-icon
|
v-for="(col, c) in report.columns"
|
||||||
name="chevron-left"
|
:key="c + '-col'"
|
||||||
class="text-gray-600 w-4 h-4 cursor-pointer"
|
:style="getCellStyle(col, c)"
|
||||||
v-show="pageNo > 1"
|
class="
|
||||||
@click="pageNo -= 1"
|
text-gray-600 text-base
|
||||||
/>
|
px-3
|
||||||
<div class="w-4 h-4" v-show="pageNo <= 1" />
|
flex-shrink-0
|
||||||
<div class="flex gap-1 bg-gray-100 rounded pr-2">
|
overflow-x-scroll
|
||||||
<input
|
whitespace-nowrap
|
||||||
type="number"
|
"
|
||||||
class="
|
>
|
||||||
w-6
|
{{ col.label }}
|
||||||
text-right
|
</p>
|
||||||
outline-none
|
</div>
|
||||||
bg-transparent
|
|
||||||
focus:text-gray-900
|
<!-- Report Rows Continer -->
|
||||||
"
|
<div
|
||||||
v-model="pageNo"
|
class="overflow-y-scroll"
|
||||||
min="1"
|
:style="{ height: `${hconst * maxRows + 5}px` }"
|
||||||
max="maxPages"
|
>
|
||||||
/>
|
<!-- Report Rows -->
|
||||||
<p class="text-gray-600">/</p>
|
<div
|
||||||
<p class="w-5">{{ maxPages }}</p>
|
v-for="(row, r) in report.reportData"
|
||||||
|
:key="r + '-row'"
|
||||||
|
class="border-b flex items-center"
|
||||||
|
:style="{ height: `${hconst}px` }"
|
||||||
|
>
|
||||||
|
<!-- Report Cell -->
|
||||||
|
<div
|
||||||
|
v-for="(cell, c) in row"
|
||||||
|
:key="`${c}-${r}-cell`"
|
||||||
|
:style="getCellStyle(cell, c)"
|
||||||
|
class="
|
||||||
|
text-gray-900 text-base
|
||||||
|
px-3
|
||||||
|
flex-shrink-0
|
||||||
|
overflow-x-scroll
|
||||||
|
whitespace-nowrap
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ cell.value }}
|
||||||
</div>
|
</div>
|
||||||
<div class="w-4 h-4" v-show="pageNo >= maxPages" />
|
|
||||||
<feather-icon
|
|
||||||
name="chevron-right"
|
|
||||||
class="text-gray-600 w-4 h-4 cursor-pointer"
|
|
||||||
v-show="pageNo < maxPages"
|
|
||||||
@click="pageNo += 1"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="h-10 mx-4 mb-4 p-2 bg-red-100 text-base border-t">
|
||||||
|
add pagination here
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { getReportData } from 'reports/index';
|
import { reports } from 'reports';
|
||||||
import reportViewConfig from 'reports/view';
|
|
||||||
import Button from 'src/components/Button';
|
|
||||||
import FormControl from 'src/components/Controls/FormControl.vue';
|
import FormControl from 'src/components/Controls/FormControl.vue';
|
||||||
import DropdownWithActions from 'src/components/DropdownWithActions.vue';
|
import PageHeader from 'src/components/PageHeader.vue';
|
||||||
import FeatherIcon from 'src/components/FeatherIcon.vue';
|
|
||||||
import PageHeader from 'src/components/PageHeader';
|
|
||||||
import Row from 'src/components/Row';
|
|
||||||
import WithScroll from 'src/components/WithScroll';
|
|
||||||
import { fyo } from 'src/initFyo';
|
import { fyo } from 'src/initFyo';
|
||||||
import { h, markRaw, nextTick } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
export default {
|
export default defineComponent({
|
||||||
name: 'Report',
|
props: {
|
||||||
props: ['reportName', 'defaultFilters'],
|
reportName: String,
|
||||||
components: {
|
|
||||||
PageHeader,
|
|
||||||
Button,
|
|
||||||
Row,
|
|
||||||
FormControl,
|
|
||||||
WithScroll,
|
|
||||||
DropdownWithActions,
|
|
||||||
FeatherIcon,
|
|
||||||
},
|
|
||||||
provide() {
|
|
||||||
return {
|
|
||||||
doc: this.filters,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
let filters = {};
|
|
||||||
for (let df of reportViewConfig[this.reportName].filterFields) {
|
|
||||||
filters[df.fieldname] = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loading: true,
|
wconst: 8,
|
||||||
filters,
|
hconst: 48,
|
||||||
pageNo: 1,
|
loading: false,
|
||||||
pageLen: 25,
|
report: null,
|
||||||
reportData: {
|
|
||||||
rows: [],
|
|
||||||
columns: [],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async activated() {
|
components: { PageHeader, FormControl },
|
||||||
this.reportData.columns = this.report.getColumns({ filters: this.filters });
|
async mounted() {
|
||||||
await this.setDefaultFilters();
|
await this.setReportData();
|
||||||
await this.fetchReportData();
|
|
||||||
},
|
},
|
||||||
methods: {
|
async activated() {
|
||||||
onBodyScroll({ scrollLeft }) {
|
await this.setReportData();
|
||||||
nextTick(() => {
|
if (fyo.store.isDevelopment) {
|
||||||
this.$refs.header.scrollLeft = scrollLeft;
|
window.rep = this;
|
||||||
});
|
}
|
||||||
},
|
|
||||||
async fetchReportData() {
|
|
||||||
let data = await getReportData(this.report.method, this.filters);
|
|
||||||
|
|
||||||
let rows;
|
|
||||||
if (data.rows) {
|
|
||||||
rows = data.rows;
|
|
||||||
} else {
|
|
||||||
rows = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.reportData.columns = markRaw(
|
|
||||||
this.report.getColumns({
|
|
||||||
filters: this.filters,
|
|
||||||
data,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!rows) {
|
|
||||||
rows = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.reportData.rows = markRaw(this.addTreeMeta(rows));
|
|
||||||
this.loading = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
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) {
|
|
||||||
this.filters[df.fieldname] = value;
|
|
||||||
this.fetchReportData();
|
|
||||||
},
|
|
||||||
|
|
||||||
async resetFilters() {
|
|
||||||
await this.setDefaultFilters();
|
|
||||||
await this.fetchReportData();
|
|
||||||
},
|
|
||||||
|
|
||||||
async setDefaultFilters() {
|
|
||||||
for (let df of this.report.filterFields) {
|
|
||||||
let defaultValue = null;
|
|
||||||
if (df.default) {
|
|
||||||
if (typeof df.default === 'function') {
|
|
||||||
defaultValue = await df.default();
|
|
||||||
} else {
|
|
||||||
defaultValue = df.default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.filters[df.fieldname] = defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.defaultFilters) {
|
|
||||||
Object.assign(this.filters, this.defaultFilters);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
cellComponent(cellValue, column) {
|
|
||||||
if (typeof cellValue === 'object') {
|
|
||||||
// cellValue has a component definition
|
|
||||||
return cellValue;
|
|
||||||
}
|
|
||||||
if (column.component) {
|
|
||||||
// column has a component definition
|
|
||||||
return column.component(cellValue, column);
|
|
||||||
}
|
|
||||||
|
|
||||||
// default cell component
|
|
||||||
let formattedValue =
|
|
||||||
cellValue != null && cellValue !== ''
|
|
||||||
? fyo.format(cellValue, column)
|
|
||||||
: '';
|
|
||||||
return {
|
|
||||||
render() {
|
|
||||||
return h('span', formattedValue);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getColumnAlignClass(column) {
|
|
||||||
return {
|
|
||||||
'text-right': ['Int', 'Float', 'Currency'].includes(column.fieldtype),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
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 [
|
|
||||||
this.getColumnAlignClass(column),
|
|
||||||
treeCellClasses,
|
|
||||||
this.loading ? 'text-gray-100' : 'text-gray-900',
|
|
||||||
];
|
|
||||||
},
|
|
||||||
getReportData() {
|
|
||||||
return { rows: this.rows, columns: this.columns, filters: this.filters };
|
|
||||||
},
|
|
||||||
getCurriedAction(action) {
|
|
||||||
return (...args) => action(this.getReportData, ...args);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
usePagination() {
|
maxRows() {
|
||||||
return (
|
return 18 - Math.ceil(this.report.filters.length / 5);
|
||||||
this.reportName === 'general-ledger' && this.rows.length > this.pageLen
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
height() {
|
title() {
|
||||||
if (this.usePagination && this.platform === 'Windows') {
|
return reports[this.reportName]?.title ?? t`Report`;
|
||||||
return 'calc(100vh - 14.5rem)';
|
|
||||||
} else if (this.usePagination) {
|
|
||||||
return 'calc(100vh - 13rem)';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'calc(100vh - 12rem)';
|
|
||||||
},
|
|
||||||
sliceIndex() {
|
|
||||||
if (!this.usePagination) {
|
|
||||||
return {
|
|
||||||
from: 0,
|
|
||||||
to: this.rows.length,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
from: (this.pageNo - 1) * this.pageLen,
|
|
||||||
to: this.pageNo * this.pageLen,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
maxPages() {
|
|
||||||
return Math.ceil(this.rows.length / this.pageLen);
|
|
||||||
},
|
|
||||||
actions() {
|
|
||||||
return [
|
|
||||||
...(this.report.actions
|
|
||||||
?.filter((action) => !action.group)
|
|
||||||
.map((action) =>
|
|
||||||
Object.assign({}, action, {
|
|
||||||
action: this.getCurriedAction(action.action),
|
|
||||||
})
|
|
||||||
) ?? []),
|
|
||||||
{
|
|
||||||
label: this.t`Reset Filters`,
|
|
||||||
action: this.resetFilters,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
actionGroups() {
|
|
||||||
const groups =
|
|
||||||
this.report.actions
|
|
||||||
?.filter((action) => action.group)
|
|
||||||
.reduce((acc, action) => {
|
|
||||||
acc[action.group] ??= { type: action.type, actions: [] };
|
|
||||||
const actionWithoutGroup = Object.assign({}, action);
|
|
||||||
actionWithoutGroup.action = this.getCurriedAction(action.action);
|
|
||||||
delete actionWithoutGroup.group;
|
|
||||||
acc[action.group].actions.push(actionWithoutGroup);
|
|
||||||
return acc;
|
|
||||||
}, {}) ?? {};
|
|
||||||
|
|
||||||
return Object.keys(groups).map((label) => ({ label, ...groups[label] }));
|
|
||||||
},
|
|
||||||
columns() {
|
|
||||||
return this.loading
|
|
||||||
? this.blankStateData.columns
|
|
||||||
: this.reportData.columns;
|
|
||||||
},
|
|
||||||
rows() {
|
|
||||||
return this.loading ? this.blankStateData.rows : this.reportData.rows;
|
|
||||||
},
|
|
||||||
blankStateData() {
|
|
||||||
let columns = Array.from(new Array(6)).map((v, i) => {
|
|
||||||
return {
|
|
||||||
fieldtype: 'Data',
|
|
||||||
fieldname: `Test ${i + 1}`,
|
|
||||||
label: `Test ${i + 1}`,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
let rows = Array.from(new Array(14)).map(() => {
|
|
||||||
return columns.reduce((obj, col) => {
|
|
||||||
obj[col.fieldname] = 'Test Data ' + col.fieldname;
|
|
||||||
obj.isShown = true;
|
|
||||||
return obj;
|
|
||||||
}, {});
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
columns,
|
|
||||||
rows,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
report() {
|
|
||||||
return reportViewConfig[this.reportName];
|
|
||||||
},
|
|
||||||
columnWidth() {
|
|
||||||
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 = `${9 * multiplier}rem`;
|
|
||||||
|
|
||||||
return `minmax(${minWidth}, ${maxWidth})`;
|
|
||||||
})
|
|
||||||
.join(' ');
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
methods: {
|
||||||
|
getCellStyle(cell, i) {
|
||||||
|
const styles = {};
|
||||||
|
const width = cell.width ?? 1;
|
||||||
|
const align = cell.align ?? 'left';
|
||||||
|
|
||||||
|
styles['width'] = `${width * this.wconst}rem`;
|
||||||
|
styles['text-align'] = align;
|
||||||
|
|
||||||
|
if (cell.bold) {
|
||||||
|
styles['font-weight'] = 'bold';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell.italics) {
|
||||||
|
styles['font-style'] = 'italic';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i === 0) {
|
||||||
|
styles['padding-left'] = '0px';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i === this.report.columns.length - 1) {
|
||||||
|
styles['padding-right'] = '0px';
|
||||||
|
}
|
||||||
|
|
||||||
|
return styles;
|
||||||
|
},
|
||||||
|
async setReportData() {
|
||||||
|
const Report = reports[this.reportName];
|
||||||
|
if (this.report === null) {
|
||||||
|
this.report = new Report(fyo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.report.reportData.length) {
|
||||||
|
await this.report.setReportData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { ipcRenderer } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
import { ConfigKeys } from 'fyo/core/types';
|
import { ConfigKeys } from 'fyo/core/types';
|
||||||
import { GeneralLedger } from 'reports';
|
|
||||||
import { IPC_ACTIONS } from 'utils/messages';
|
import { IPC_ACTIONS } from 'utils/messages';
|
||||||
import { App as VueApp, createApp } from 'vue';
|
import { App as VueApp, createApp } from 'vue';
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
@ -106,6 +105,3 @@ function setOnWindow() {
|
|||||||
window.fyo = fyo;
|
window.fyo = fyo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
window.GL = GeneralLedger;
|
|
||||||
|
@ -8,7 +8,7 @@ import JournalEntryForm from 'src/pages/JournalEntryForm.vue';
|
|||||||
import ListView from 'src/pages/ListView/ListView.vue';
|
import ListView from 'src/pages/ListView/ListView.vue';
|
||||||
import PrintView from 'src/pages/PrintView/PrintView.vue';
|
import PrintView from 'src/pages/PrintView/PrintView.vue';
|
||||||
import QuickEditForm from 'src/pages/QuickEditForm.vue';
|
import QuickEditForm from 'src/pages/QuickEditForm.vue';
|
||||||
// import Report from 'src/pages/Report.vue';
|
import Report from 'src/pages/Report.vue';
|
||||||
import Settings from 'src/pages/Settings/Settings.vue';
|
import Settings from 'src/pages/Settings/Settings.vue';
|
||||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
||||||
import { fyo } from './initFyo';
|
import { fyo } from './initFyo';
|
||||||
@ -87,14 +87,12 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: PrintView,
|
component: PrintView,
|
||||||
props: true,
|
props: true,
|
||||||
},
|
},
|
||||||
/*
|
|
||||||
{
|
{
|
||||||
path: '/report/:reportName',
|
path: '/report/:reportName',
|
||||||
name: 'Report',
|
name: 'Report',
|
||||||
component: Report,
|
component: Report,
|
||||||
props: true,
|
props: true,
|
||||||
},
|
},
|
||||||
*/
|
|
||||||
{
|
{
|
||||||
path: '/chart-of-accounts',
|
path: '/chart-of-accounts',
|
||||||
name: 'Chart Of Accounts',
|
name: 'Chart Of Accounts',
|
||||||
|
@ -132,18 +132,18 @@ function getCompleteSidebar(): SidebarConfig {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/*
|
|
||||||
{
|
{
|
||||||
label: t`Reports`,
|
label: t`Reports`,
|
||||||
name: t`reports`,
|
name: t`reports`,
|
||||||
icon: 'reports',
|
icon: 'reports',
|
||||||
route: '/report/general-ledger',
|
route: '/report/GeneralLedger',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
label: t`General Ledger`,
|
label: t`General Ledger`,
|
||||||
name: 'general-ledger',
|
name: 'general-ledger',
|
||||||
route: '/report/general-ledger',
|
route: '/report/GeneralLedger',
|
||||||
},
|
},
|
||||||
|
/*
|
||||||
{
|
{
|
||||||
label: t`Profit And Loss`,
|
label: t`Profit And Loss`,
|
||||||
name: 'profit-and-loss',
|
name: 'profit-and-loss',
|
||||||
@ -171,9 +171,9 @@ function getCompleteSidebar(): SidebarConfig {
|
|||||||
route: '/report/gstr-2',
|
route: '/report/gstr-2',
|
||||||
hidden: () => fyo.singles.AccountingSettings!.country !== 'India',
|
hidden: () => fyo.singles.AccountingSettings!.country !== 'India',
|
||||||
},
|
},
|
||||||
|
*/
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
*/
|
|
||||||
{
|
{
|
||||||
label: t`Setup`,
|
label: t`Setup`,
|
||||||
name: t`setup`,
|
name: t`setup`,
|
||||||
|
@ -104,3 +104,22 @@ export function invertMap(map: Record<string, string>): Record<string, string> {
|
|||||||
|
|
||||||
return inverted;
|
return inverted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function time<K, T>(func: (...args: K[]) => T, ...args: K[]): T {
|
||||||
|
const name = func.name;
|
||||||
|
console.time(name);
|
||||||
|
const stuff = func(...args);
|
||||||
|
console.timeEnd(name);
|
||||||
|
return stuff;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function timeAsync<K, T>(
|
||||||
|
func: (...args: K[]) => Promise<T>,
|
||||||
|
...args: K[]
|
||||||
|
): Promise<T> {
|
||||||
|
const name = func.name;
|
||||||
|
console.time(name);
|
||||||
|
const stuff = await func(...args);
|
||||||
|
console.timeEnd(name);
|
||||||
|
return stuff;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user